mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Merge remote-tracking branch 'origin/trunk' into complete-num-add
This commit is contained in:
commit
003408e3ef
147 changed files with 4249 additions and 1589 deletions
|
@ -3,7 +3,7 @@ authors = ["The Roc Contributors"]
|
|||
edition = "2021"
|
||||
license = "UPL-1.0"
|
||||
name = "roc_alias_analysis"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
|
||||
[dependencies]
|
||||
morphic_lib = {path = "../../vendor/morphic_lib"}
|
||||
|
|
|
@ -131,7 +131,7 @@ fn bytes_as_ascii(bytes: &[u8]) -> String {
|
|||
|
||||
pub fn spec_program<'a, I>(
|
||||
opt_level: OptLevel,
|
||||
entry_point: roc_mono::ir::EntryPoint<'a>,
|
||||
opt_entry_point: Option<roc_mono::ir::EntryPoint<'a>>,
|
||||
procs: I,
|
||||
) -> Result<morphic_lib::Solutions>
|
||||
where
|
||||
|
@ -221,19 +221,21 @@ where
|
|||
m.add_func(func_name, spec)?;
|
||||
}
|
||||
|
||||
// the entry point wrapper
|
||||
let roc_main_bytes = func_name_bytes_help(
|
||||
entry_point.symbol,
|
||||
entry_point.layout.arguments.iter().copied(),
|
||||
CapturesNiche::no_niche(),
|
||||
&entry_point.layout.result,
|
||||
);
|
||||
let roc_main = FuncName(&roc_main_bytes);
|
||||
if let Some(entry_point) = opt_entry_point {
|
||||
// the entry point wrapper
|
||||
let roc_main_bytes = func_name_bytes_help(
|
||||
entry_point.symbol,
|
||||
entry_point.layout.arguments.iter().copied(),
|
||||
CapturesNiche::no_niche(),
|
||||
&entry_point.layout.result,
|
||||
);
|
||||
let roc_main = FuncName(&roc_main_bytes);
|
||||
|
||||
let entry_point_function =
|
||||
build_entry_point(entry_point.layout, roc_main, &host_exposed_functions)?;
|
||||
let entry_point_name = FuncName(ENTRY_POINT_NAME);
|
||||
m.add_func(entry_point_name, entry_point_function)?;
|
||||
let entry_point_function =
|
||||
build_entry_point(entry_point.layout, roc_main, &host_exposed_functions)?;
|
||||
let entry_point_name = FuncName(ENTRY_POINT_NAME);
|
||||
m.add_func(entry_point_name, entry_point_function)?;
|
||||
}
|
||||
|
||||
for union_layout in type_definitions {
|
||||
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes();
|
||||
|
@ -264,8 +266,10 @@ where
|
|||
let mut p = ProgramBuilder::new();
|
||||
p.add_mod(MOD_APP, main_module)?;
|
||||
|
||||
let entry_point_name = FuncName(ENTRY_POINT_NAME);
|
||||
p.add_entry_point(EntryPointName(ENTRY_POINT_NAME), MOD_APP, entry_point_name)?;
|
||||
if opt_entry_point.is_some() {
|
||||
let entry_point_name = FuncName(ENTRY_POINT_NAME);
|
||||
p.add_entry_point(EntryPointName(ENTRY_POINT_NAME), MOD_APP, entry_point_name)?;
|
||||
}
|
||||
|
||||
p.build()?
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "arena-pool"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
repository = "https://github.com/rtfeldman/roc"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_build"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -120,6 +120,7 @@ pub fn build_zig_host_native(
|
|||
.env_clear()
|
||||
.env("PATH", env_path)
|
||||
.env("HOME", env_home);
|
||||
|
||||
if let Some(shared_lib_path) = shared_lib_path {
|
||||
command.args(&[
|
||||
"build-exe",
|
||||
|
@ -130,6 +131,7 @@ pub fn build_zig_host_native(
|
|||
} else {
|
||||
command.args(&["build-obj", "-fPIC"]);
|
||||
}
|
||||
|
||||
command.args(&[
|
||||
zig_host_src,
|
||||
emit_bin,
|
||||
|
@ -160,6 +162,7 @@ pub fn build_zig_host_native(
|
|||
} else if matches!(opt_level, OptLevel::Size) {
|
||||
command.args(&["-O", "ReleaseSmall"]);
|
||||
}
|
||||
|
||||
command.output().unwrap()
|
||||
}
|
||||
|
||||
|
@ -425,7 +428,11 @@ pub fn rebuild_host(
|
|||
host_input_path.with_file_name(if shared_lib_path.is_some() {
|
||||
"dynhost"
|
||||
} else {
|
||||
"host.o"
|
||||
match roc_target::OperatingSystem::from(target.operating_system) {
|
||||
roc_target::OperatingSystem::Windows => "host.obj",
|
||||
roc_target::OperatingSystem::Unix => "host.o",
|
||||
roc_target::OperatingSystem::Wasi => "host.o",
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
@ -1095,11 +1102,58 @@ fn link_wasm32(
|
|||
|
||||
fn link_windows(
|
||||
_target: &Triple,
|
||||
_output_path: PathBuf,
|
||||
_input_paths: &[&str],
|
||||
_link_type: LinkType,
|
||||
output_path: PathBuf,
|
||||
input_paths: &[&str],
|
||||
link_type: LinkType,
|
||||
) -> io::Result<(Child, PathBuf)> {
|
||||
todo!("Add windows support to the surgical linker. See issue #2608.")
|
||||
let zig_str_path = find_zig_str_path();
|
||||
|
||||
match link_type {
|
||||
LinkType::Dylib => {
|
||||
let child = Command::new(&zig_executable())
|
||||
.args(&["build-lib"])
|
||||
.args(input_paths)
|
||||
.args([
|
||||
"-lc",
|
||||
&format!("-femit-bin={}", output_path.to_str().unwrap()),
|
||||
"-target",
|
||||
"native",
|
||||
"--pkg-begin",
|
||||
"str",
|
||||
zig_str_path.to_str().unwrap(),
|
||||
"--pkg-end",
|
||||
"--strip",
|
||||
"-O",
|
||||
"Debug",
|
||||
"-dynamic",
|
||||
])
|
||||
.spawn()?;
|
||||
|
||||
Ok((child, output_path))
|
||||
}
|
||||
LinkType::Executable => {
|
||||
let child = Command::new(&zig_executable())
|
||||
.args(&["build-exe"])
|
||||
.args(input_paths)
|
||||
.args([
|
||||
"-lc",
|
||||
&format!("-femit-bin={}", output_path.to_str().unwrap()),
|
||||
"-target",
|
||||
"native",
|
||||
"--pkg-begin",
|
||||
"str",
|
||||
zig_str_path.to_str().unwrap(),
|
||||
"--pkg-end",
|
||||
"--strip",
|
||||
"-O",
|
||||
"Debug",
|
||||
])
|
||||
.spawn()?;
|
||||
|
||||
Ok((child, output_path))
|
||||
}
|
||||
LinkType::None => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn llvm_module_to_dylib(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
pub use roc_gen_llvm::llvm::build::FunctionIterator;
|
||||
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_load::{LoadedModule, MonomorphizedModule};
|
||||
use roc_load::{EntryPoint, LoadedModule, MonomorphizedModule};
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_region::all::LineInfo;
|
||||
|
@ -265,11 +265,18 @@ pub fn gen_from_mono_module_llvm(
|
|||
// expects that would confuse the surgical linker
|
||||
add_default_roc_externs(&env);
|
||||
|
||||
let opt_entry_point = match loaded.entry_point {
|
||||
EntryPoint::Executable { symbol, layout, .. } => {
|
||||
Some(roc_mono::ir::EntryPoint { symbol, layout })
|
||||
}
|
||||
EntryPoint::Test => None,
|
||||
};
|
||||
|
||||
roc_gen_llvm::llvm::build::build_procedures(
|
||||
&env,
|
||||
opt_level,
|
||||
loaded.procedures,
|
||||
loaded.entry_point,
|
||||
opt_entry_point,
|
||||
Some(&app_ll_file),
|
||||
);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_builtins"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -89,6 +89,7 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
|
|||
&bitcode_path,
|
||||
&zig_executable(),
|
||||
&["build", zig_object, "-Drelease=true"],
|
||||
0,
|
||||
);
|
||||
|
||||
println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
|
||||
|
@ -123,6 +124,7 @@ fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
|
|||
&bitcode_path,
|
||||
&zig_executable(),
|
||||
&["build", zig_object, "-Drelease=true"],
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -192,8 +194,12 @@ fn cp_unless_zig_cache(src_dir: &Path, target_dir: &Path) -> io::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn run_command<S, I: Copy, P: AsRef<Path> + Copy>(path: P, command_str: &str, args: I)
|
||||
where
|
||||
fn run_command<S, I: Copy, P: AsRef<Path> + Copy>(
|
||||
path: P,
|
||||
command_str: &str,
|
||||
args: I,
|
||||
flaky_fail_counter: usize,
|
||||
) where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
|
@ -212,10 +218,14 @@ where
|
|||
};
|
||||
|
||||
// flaky test error that only occurs sometimes inside MacOS ci run
|
||||
if error_str.contains("unable to build stage1 zig object: FileNotFound")
|
||||
if error_str.contains("FileNotFound")
|
||||
|| error_str.contains("unable to save cached ZIR code")
|
||||
{
|
||||
run_command(path, command_str, args)
|
||||
if flaky_fail_counter == 10 {
|
||||
panic!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str);
|
||||
} else {
|
||||
run_command(path, command_str, args, flaky_fail_counter + 1)
|
||||
}
|
||||
} else {
|
||||
panic!("{} failed: {}", command_str, error_str);
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ takeFloat = \bytes ->
|
|||
|
||||
when List.get rest 0 is
|
||||
Ok 46 -> # 46 = .
|
||||
{ taken: floatPart, rest: afterAll } = takeDigits rest
|
||||
{ taken: floatPart, rest: afterAll } = takeDigits (List.split rest 1).others
|
||||
builtFloat =
|
||||
List.concat (List.append intPart (asciiByte '.')) floatPart
|
||||
|
||||
|
|
|
@ -330,11 +330,20 @@ splitLast = \haystack, needle ->
|
|||
None ->
|
||||
Err NotFound
|
||||
|
||||
# splitLast when needle isn't in haystack
|
||||
expect Str.splitLast "foo" "z" == Err NotFound
|
||||
|
||||
# splitLast when haystack ends with needle repeated
|
||||
expect Str.splitLast "foo" "o" == Ok { before: "fo", after: "" }
|
||||
|
||||
# splitLast with multi-byte needle
|
||||
expect Str.splitLast "hullabaloo" "ab" == Ok { before: "hull", after: "aloo" }
|
||||
|
||||
lastMatch : Str, Str -> [Some Nat, None]
|
||||
lastMatch = \haystack, needle ->
|
||||
haystackLength = Str.countUtf8Bytes haystack
|
||||
needleLength = Str.countUtf8Bytes needle
|
||||
lastPossibleIndex = Num.subSaturated haystackLength (needleLength + 1)
|
||||
lastPossibleIndex = Num.subSaturated haystackLength needleLength
|
||||
|
||||
lastMatchHelp haystack needle lastPossibleIndex
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ impl FloatWidth {
|
|||
}
|
||||
|
||||
pub const fn alignment_bytes(&self, target_info: TargetInfo) -> u32 {
|
||||
use roc_target::Architecture;
|
||||
use roc_target::Architecture::*;
|
||||
use FloatWidth::*;
|
||||
|
||||
// NOTE: this must never use mem::align_of, because that returns the alignment
|
||||
|
@ -73,8 +73,8 @@ impl FloatWidth {
|
|||
match self {
|
||||
F32 => 4,
|
||||
F64 | F128 => match target_info.architecture {
|
||||
Architecture::X86_64 | Architecture::Aarch64 | Architecture::Wasm32 => 8,
|
||||
Architecture::X86_32 | Architecture::Aarch32 => 4,
|
||||
X86_64 | Aarch64 | Wasm32 => 8,
|
||||
X86_32 | Aarch32 => 4,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_can"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -18,7 +18,7 @@ pub struct Annotation {
|
|||
pub typ: Type,
|
||||
pub introduced_variables: IntroducedVariables,
|
||||
pub references: VecSet<Symbol>,
|
||||
pub aliases: SendMap<Symbol, Alias>,
|
||||
pub aliases: VecMap<Symbol, Alias>,
|
||||
}
|
||||
|
||||
impl Annotation {
|
||||
|
@ -271,7 +271,7 @@ pub fn canonicalize_annotation(
|
|||
) -> Annotation {
|
||||
let mut introduced_variables = IntroducedVariables::default();
|
||||
let mut references = VecSet::default();
|
||||
let mut aliases = SendMap::default();
|
||||
let mut aliases = VecMap::default();
|
||||
|
||||
let (annotation, region) = match annotation {
|
||||
TypeAnnotation::Where(annotation, clauses) => {
|
||||
|
@ -475,7 +475,7 @@ fn can_annotation_help(
|
|||
scope: &mut Scope,
|
||||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
) -> Type {
|
||||
use roc_parse::ast::TypeAnnotation::*;
|
||||
|
@ -976,7 +976,7 @@ fn can_extension_type<'a>(
|
|||
scope: &mut Scope,
|
||||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
opt_ext: &Option<&Loc<TypeAnnotation<'a>>>,
|
||||
ext_problem_kind: roc_problem::can::ExtensionTypeKind,
|
||||
|
@ -1169,7 +1169,7 @@ fn can_assigned_fields<'a>(
|
|||
scope: &mut Scope,
|
||||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
) -> SendMap<Lowercase, RecordField<Type>> {
|
||||
use roc_parse::ast::AssignedField::*;
|
||||
|
@ -1282,7 +1282,7 @@ fn can_tags<'a>(
|
|||
scope: &mut Scope,
|
||||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
) -> Vec<(TagName, Vec<Type>)> {
|
||||
let mut tag_types = Vec::with_capacity(tags.len());
|
||||
|
|
|
@ -79,7 +79,7 @@ impl Def {
|
|||
pub struct Annotation {
|
||||
pub signature: Type,
|
||||
pub introduced_variables: IntroducedVariables,
|
||||
pub aliases: SendMap<Symbol, Alias>,
|
||||
pub aliases: VecMap<Symbol, Alias>,
|
||||
pub region: Region,
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::def::Def;
|
|||
use crate::expr::{AnnotatedMark, ClosureData, Declarations, Expr, Recursive, WhenBranchPattern};
|
||||
use crate::pattern::Pattern;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::{SendMap, VecSet};
|
||||
use roc_collections::{SendMap, VecMap, VecSet};
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
@ -201,7 +201,7 @@ fn build_effect_always(
|
|||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
aliases: VecMap::default(),
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
|
@ -393,7 +393,7 @@ fn build_effect_map(
|
|||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
aliases: VecMap::default(),
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
|
@ -601,7 +601,7 @@ fn build_effect_after(
|
|||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
aliases: VecMap::default(),
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
|
@ -833,7 +833,7 @@ fn build_effect_forever(
|
|||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
aliases: VecMap::default(),
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
|
@ -1090,7 +1090,7 @@ fn build_effect_loop(
|
|||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
aliases: VecMap::default(),
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_collections"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_constrain"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
[package]
|
||||
name = "roc_debug_flags"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
|
||||
[dependencies]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_derive"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
258
crates/compiler/derive/src/decoding.rs
Normal file
258
crates/compiler/derive/src/decoding.rs
Normal file
|
@ -0,0 +1,258 @@
|
|||
//! Derivers for the `Decoding` ability.
|
||||
|
||||
use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Recursive};
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_derive_key::decoding::FlatDecodableKey;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Loc;
|
||||
use roc_types::subs::{
|
||||
Content, FlatType, GetSubsSlice, LambdaSet, OptVariable, SubsSlice, UnionLambdas, Variable,
|
||||
};
|
||||
use roc_types::types::AliasKind;
|
||||
|
||||
use crate::util::Env;
|
||||
use crate::{synth_var, DerivedBody};
|
||||
|
||||
pub(crate) fn derive_decoder(
|
||||
env: &mut Env<'_>,
|
||||
key: FlatDecodableKey,
|
||||
def_symbol: Symbol,
|
||||
) -> DerivedBody {
|
||||
let (body, body_type) = match key {
|
||||
FlatDecodableKey::List() => decoder_list(env, def_symbol),
|
||||
};
|
||||
|
||||
let specialization_lambda_sets =
|
||||
env.get_specialization_lambda_sets(body_type, Symbol::DECODE_DECODER);
|
||||
|
||||
DerivedBody {
|
||||
body,
|
||||
body_type,
|
||||
specialization_lambda_sets,
|
||||
}
|
||||
}
|
||||
|
||||
fn decoder_list(env: &mut Env<'_>, _def_symbol: Symbol) -> (Expr, Variable) {
|
||||
// Build
|
||||
//
|
||||
// def_symbol : Decoder (List elem) fmt | elem has Decoding, fmt has DecoderFormatting
|
||||
// def_symbol = Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
|
||||
//
|
||||
// TODO try to reduce to `Decode.list Decode.decoder`
|
||||
|
||||
use Expr::*;
|
||||
|
||||
// Decode.list Decode.decoder : Decoder (List elem) fmt
|
||||
let (decode_list_call, this_decode_list_ret_var) = {
|
||||
// List elem
|
||||
let elem_var = env.subs.fresh_unnamed_flex_var();
|
||||
|
||||
// Decode.decoder : Decoder elem fmt | elem has Decoding, fmt has EncoderFormatting
|
||||
let (elem_decoder, elem_decoder_var) = {
|
||||
// build `Decode.decoder : Decoder elem fmt` type
|
||||
// Decoder val fmt | val has Decoding, fmt has EncoderFormatting
|
||||
let elem_decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER);
|
||||
|
||||
// set val ~ elem
|
||||
let val_var = match env.subs.get_content_without_compacting(elem_decoder_var) {
|
||||
Content::Alias(Symbol::DECODE_DECODER_OPAQUE, vars, _, AliasKind::Opaque)
|
||||
if vars.type_variables_len == 2 =>
|
||||
{
|
||||
env.subs.get_subs_slice(vars.type_variables())[0]
|
||||
}
|
||||
_ => internal_error!("Decode.decode not an opaque type"),
|
||||
};
|
||||
|
||||
env.unify(val_var, elem_var);
|
||||
|
||||
(
|
||||
AbilityMember(Symbol::DECODE_DECODER, None, elem_decoder_var),
|
||||
elem_decoder_var,
|
||||
)
|
||||
};
|
||||
|
||||
// Build `Decode.list Decode.decoder` type
|
||||
// Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
|
||||
let decode_list_fn_var = env.import_builtin_symbol_var(Symbol::DECODE_LIST);
|
||||
|
||||
// Decoder elem fmt -a-> b
|
||||
let elem_decoder_var_slice = SubsSlice::insert_into_subs(env.subs, [elem_decoder_var]);
|
||||
let this_decode_list_clos_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_list_ret_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_list_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
elem_decoder_var_slice,
|
||||
this_decode_list_clos_var,
|
||||
this_decode_list_ret_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
|
||||
// ~ Decoder elem fmt -a -> b
|
||||
env.unify(decode_list_fn_var, this_decode_list_fn_var);
|
||||
|
||||
let decode_list_member = AbilityMember(Symbol::DECODE_LIST, None, this_decode_list_fn_var);
|
||||
let decode_list_fn = Box::new((
|
||||
decode_list_fn_var,
|
||||
Loc::at_zero(decode_list_member),
|
||||
this_decode_list_clos_var,
|
||||
this_decode_list_ret_var,
|
||||
));
|
||||
|
||||
let decode_list_call = Call(
|
||||
decode_list_fn,
|
||||
vec![(elem_decoder_var, Loc::at_zero(elem_decoder))],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
(decode_list_call, this_decode_list_ret_var)
|
||||
};
|
||||
|
||||
let bytes_sym = env.new_symbol("bytes");
|
||||
let bytes_var = env.subs.fresh_unnamed_flex_var();
|
||||
let fmt_sym = env.new_symbol("fmt");
|
||||
let fmt_var = env.subs.fresh_unnamed_flex_var();
|
||||
|
||||
// Decode.decodeWith bytes (Decode.list Decode.decoder) fmt : DecodeResult (List elem)
|
||||
let (decode_with_call, decode_result_list_elem_var) = {
|
||||
// Decode.decodeWith : List U8, Decoder val fmt, fmt -> DecodeResult val | fmt has DecoderFormatting
|
||||
let decode_with_type = env.import_builtin_symbol_var(Symbol::DECODE_DECODE_WITH);
|
||||
|
||||
// Decode.decodeWith : bytes, Decoder (List elem) fmt, fmt -> DecoderResult (List val)
|
||||
let this_decode_with_var_slice =
|
||||
SubsSlice::insert_into_subs(env.subs, [bytes_var, this_decode_list_ret_var, fmt_var]);
|
||||
let this_decode_with_clos_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_with_ret_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_with_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
this_decode_with_var_slice,
|
||||
this_decode_with_clos_var,
|
||||
this_decode_with_ret_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// List U8, Decoder val fmt, fmt -> DecodeResult val | fmt has DecoderFormatting
|
||||
// ~ bytes, Decoder (List elem) fmt, fmt -> DecoderResult (List val)
|
||||
env.unify(decode_with_type, this_decode_with_fn_var);
|
||||
|
||||
let decode_with_var = Var(Symbol::DECODE_DECODE_WITH);
|
||||
let decode_with_fn = Box::new((
|
||||
this_decode_with_fn_var,
|
||||
Loc::at_zero(decode_with_var),
|
||||
this_decode_with_clos_var,
|
||||
this_decode_with_ret_var,
|
||||
));
|
||||
let decode_with_call = Call(
|
||||
decode_with_fn,
|
||||
vec![
|
||||
// bytes (Decode.list Decode.decoder) fmt
|
||||
(bytes_var, Loc::at_zero(Var(bytes_sym))),
|
||||
(this_decode_list_ret_var, Loc::at_zero(decode_list_call)),
|
||||
(fmt_var, Loc::at_zero(Var(fmt_sym))),
|
||||
],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
(decode_with_call, this_decode_with_ret_var)
|
||||
};
|
||||
|
||||
// \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
|
||||
let (custom_lambda, custom_var) = {
|
||||
let fn_name = env.new_symbol("custom");
|
||||
|
||||
// Create fn_var for ambient capture; we fix it up below.
|
||||
let fn_var = synth_var(env.subs, Content::Error);
|
||||
|
||||
// -[[fn_name]]->
|
||||
let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, [(fn_name, vec![])]);
|
||||
let fn_clos_var = synth_var(
|
||||
env.subs,
|
||||
Content::LambdaSet(LambdaSet {
|
||||
solved: fn_name_labels,
|
||||
recursion_var: OptVariable::NONE,
|
||||
unspecialized: SubsSlice::default(),
|
||||
ambient_function: fn_var,
|
||||
}),
|
||||
);
|
||||
|
||||
// bytes, fmt -[[fn_name]]-> DecoderResult (List elem)
|
||||
let args_slice = SubsSlice::insert_into_subs(env.subs, vec![bytes_var, fmt_var]);
|
||||
env.subs.set_content(
|
||||
fn_var,
|
||||
Content::Structure(FlatType::Func(
|
||||
args_slice,
|
||||
fn_clos_var,
|
||||
decode_result_list_elem_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// \bytes, fmt -[[fn_name]]-> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
|
||||
let clos = Closure(ClosureData {
|
||||
function_type: fn_var,
|
||||
closure_type: fn_clos_var,
|
||||
return_type: decode_result_list_elem_var,
|
||||
name: fn_name,
|
||||
captured_symbols: vec![],
|
||||
recursive: Recursive::NotRecursive,
|
||||
arguments: vec![
|
||||
(
|
||||
bytes_var,
|
||||
AnnotatedMark::known_exhaustive(),
|
||||
Loc::at_zero(Pattern::Identifier(bytes_sym)),
|
||||
),
|
||||
(
|
||||
fmt_var,
|
||||
AnnotatedMark::known_exhaustive(),
|
||||
Loc::at_zero(Pattern::Identifier(fmt_sym)),
|
||||
),
|
||||
],
|
||||
loc_body: Box::new(Loc::at_zero(decode_with_call)),
|
||||
});
|
||||
|
||||
(clos, fn_var)
|
||||
};
|
||||
|
||||
// Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
|
||||
let (decode_custom_call, decoder_var) = {
|
||||
// (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting
|
||||
let decode_custom_type = env.import_builtin_symbol_var(Symbol::DECODE_CUSTOM);
|
||||
|
||||
// (List U8, fmt -> DecodeResult (List elem)) -> Decoder (List elem) fmt
|
||||
let this_decode_custom_args = SubsSlice::insert_into_subs(env.subs, [custom_var]);
|
||||
let this_decode_custom_clos_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_custom_ret_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_decode_custom_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
this_decode_custom_args,
|
||||
this_decode_custom_clos_var,
|
||||
this_decode_custom_ret_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting
|
||||
// ~ (List U8, fmt -> DecodeResult (List elem)) -> Decoder (List elem) fmt
|
||||
env.unify(decode_custom_type, this_decode_custom_fn_var);
|
||||
|
||||
let decode_custom_var = Var(Symbol::DECODE_CUSTOM);
|
||||
let decode_custom_fn = Box::new((
|
||||
this_decode_custom_fn_var,
|
||||
Loc::at_zero(decode_custom_var),
|
||||
this_decode_custom_clos_var,
|
||||
this_decode_custom_ret_var,
|
||||
));
|
||||
let decode_custom_call = Call(
|
||||
decode_custom_fn,
|
||||
vec![(custom_var, Loc::at_zero(custom_lambda))],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
(decode_custom_call, this_decode_custom_ret_var)
|
||||
};
|
||||
|
||||
(decode_custom_call, decoder_var)
|
||||
}
|
|
@ -2,173 +2,24 @@
|
|||
|
||||
use std::iter::once;
|
||||
|
||||
use roc_can::abilities::SpecializationLambdaSets;
|
||||
use roc_can::expr::{
|
||||
AnnotatedMark, ClosureData, Expr, Field, Recursive, WhenBranch, WhenBranchPattern,
|
||||
};
|
||||
use roc_can::module::ExposedByModule;
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_collections::SendMap;
|
||||
use roc_derive_key::encoding::FlatEncodableKey;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{
|
||||
instantiate_rigids, Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable,
|
||||
RecordFields, RedundantMark, Subs, SubsSlice, UnionLambdas, UnionTags, Variable,
|
||||
VariableSubsSlice,
|
||||
Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields,
|
||||
RedundantMark, SubsSlice, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
|
||||
};
|
||||
use roc_types::types::RecordField;
|
||||
|
||||
use crate::{synth_var, DerivedBody, DERIVED_SYNTH};
|
||||
|
||||
pub(crate) struct Env<'a> {
|
||||
/// NB: This **must** be subs for the derive module!
|
||||
pub subs: &'a mut Subs,
|
||||
pub exposed_types: &'a ExposedByModule,
|
||||
pub derived_ident_ids: &'a mut IdentIds,
|
||||
}
|
||||
|
||||
impl Env<'_> {
|
||||
fn new_symbol(&mut self, name_hint: &str) -> Symbol {
|
||||
if cfg!(any(
|
||||
debug_assertions,
|
||||
test,
|
||||
feature = "debug-derived-symbols"
|
||||
)) {
|
||||
let mut i = 0;
|
||||
let debug_name = loop {
|
||||
i += 1;
|
||||
let name = if i == 1 {
|
||||
name_hint.to_owned()
|
||||
} else {
|
||||
format!("{}{}", name_hint, i)
|
||||
};
|
||||
if self.derived_ident_ids.get_id(&name).is_none() {
|
||||
break name;
|
||||
}
|
||||
};
|
||||
|
||||
let ident_id = self.derived_ident_ids.get_or_insert(&debug_name);
|
||||
|
||||
Symbol::new(DERIVED_SYNTH, ident_id)
|
||||
} else {
|
||||
self.unique_symbol()
|
||||
}
|
||||
}
|
||||
|
||||
fn unique_symbol(&mut self) -> Symbol {
|
||||
let ident_id = self.derived_ident_ids.gen_unique();
|
||||
Symbol::new(DERIVED_SYNTH, ident_id)
|
||||
}
|
||||
|
||||
fn import_encode_symbol(&mut self, symbol: Symbol) -> Variable {
|
||||
debug_assert_eq!(symbol.module_id(), ModuleId::ENCODE);
|
||||
|
||||
let encode_types = &self
|
||||
.exposed_types
|
||||
.get(&ModuleId::ENCODE)
|
||||
.unwrap()
|
||||
.exposed_types_storage_subs;
|
||||
let storage_var = encode_types.stored_vars_by_symbol.get(&symbol).unwrap();
|
||||
let imported = encode_types
|
||||
.storage_subs
|
||||
.export_variable_to_directly_to_use_site(self.subs, *storage_var);
|
||||
|
||||
instantiate_rigids(self.subs, imported.variable);
|
||||
|
||||
imported.variable
|
||||
}
|
||||
|
||||
fn unify(&mut self, left: Variable, right: Variable) {
|
||||
use roc_unify::unify::{unify, Env, Mode, Unified};
|
||||
|
||||
let unified = unify(&mut Env::new(self.subs), left, right, Mode::EQ);
|
||||
|
||||
match unified {
|
||||
Unified::Success {
|
||||
vars: _,
|
||||
must_implement_ability: _,
|
||||
lambda_sets_to_specialize,
|
||||
extra_metadata: _,
|
||||
} => {
|
||||
if !lambda_sets_to_specialize.is_empty() {
|
||||
internal_error!("Did not expect derivers to need to specialize unspecialized lambda sets, but we got some: {:?}", lambda_sets_to_specialize)
|
||||
}
|
||||
}
|
||||
Unified::Failure(..) | Unified::BadType(..) => {
|
||||
internal_error!("Unification failed in deriver - that's a deriver bug!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_specialization_lambda_sets(
|
||||
&mut self,
|
||||
specialization_type: Variable,
|
||||
ability_member: Symbol,
|
||||
) -> SpecializationLambdaSets {
|
||||
use roc_unify::unify::{unify_introduced_ability_specialization, Env, Mode, Unified};
|
||||
|
||||
let member_signature = self.import_encode_symbol(ability_member);
|
||||
|
||||
let unified = unify_introduced_ability_specialization(
|
||||
&mut Env::new(self.subs),
|
||||
member_signature,
|
||||
specialization_type,
|
||||
Mode::EQ,
|
||||
);
|
||||
|
||||
match unified {
|
||||
Unified::Success {
|
||||
vars: _,
|
||||
must_implement_ability: _,
|
||||
lambda_sets_to_specialize: _lambda_sets_to_specialize,
|
||||
extra_metadata: specialization_lsets,
|
||||
} => {
|
||||
let specialization_lsets: SpecializationLambdaSets = specialization_lsets
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|((spec_member, region), var)| {
|
||||
debug_assert_eq!(spec_member, ability_member);
|
||||
(region, var)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Since we're doing `{foo} ~ a | a has Encoding`, we may see "lambda sets to
|
||||
// specialize" for e.g. `{foo}:toEncoder:1`, but these are actually just the
|
||||
// specialization lambda sets, so we don't need to do any extra work!
|
||||
//
|
||||
// If there are other lambda sets to specialize in here, that's unexpected, because
|
||||
// that means we would have been deriving something like `toEncoder {foo: bar}`,
|
||||
// and now seen that we needed `toEncoder bar` where `bar` is a concrete type. But
|
||||
// we only expect `bar` to polymorphic at this stage!
|
||||
//
|
||||
// TODO: it would be better if `unify` could prune these for us. See also
|
||||
// https://github.com/rtfeldman/roc/issues/3207; that is a blocker for this TODO.
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
for (spec_var, lambda_sets) in _lambda_sets_to_specialize.drain() {
|
||||
for lambda_set in lambda_sets {
|
||||
let belongs_to_specialized_lambda_sets =
|
||||
specialization_lsets.iter().any(|(_, var)| {
|
||||
self.subs.get_root_key_without_compacting(*var)
|
||||
== self.subs.get_root_key_without_compacting(lambda_set)
|
||||
});
|
||||
debug_assert!(belongs_to_specialized_lambda_sets,
|
||||
"Did not expect derivers to need to specialize unspecialized lambda sets, but we got one: {:?} for {:?}", lambda_set, spec_var)
|
||||
}
|
||||
}
|
||||
}
|
||||
specialization_lsets
|
||||
}
|
||||
Unified::Failure(..) | Unified::BadType(..) => {
|
||||
internal_error!("Unification failed in deriver - that's a deriver bug!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::util::Env;
|
||||
use crate::{synth_var, DerivedBody};
|
||||
|
||||
pub(crate) fn derive_to_encoder(
|
||||
env: &mut Env<'_>,
|
||||
|
@ -253,7 +104,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
|
|||
|
||||
// build `toEncoder elem` type
|
||||
// val -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let to_encoder_fn_var = env.import_encode_symbol(Symbol::ENCODE_TO_ENCODER);
|
||||
let to_encoder_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TO_ENCODER);
|
||||
|
||||
// elem -[clos]-> t1
|
||||
let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
|
||||
|
@ -333,7 +184,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
|
|||
|
||||
// build `Encode.list lst (\elem -> Encode.toEncoder elem)` type
|
||||
// List e, (e -> Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let encode_list_fn_var = env.import_encode_symbol(Symbol::ENCODE_LIST);
|
||||
let encode_list_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_LIST);
|
||||
|
||||
// List elem, to_elem_encoder_fn_var -[clos]-> t1
|
||||
let this_encode_list_args_slice =
|
||||
|
@ -469,7 +320,7 @@ fn to_encoder_record(
|
|||
|
||||
// build `toEncoder rcd.a` type
|
||||
// val -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let to_encoder_fn_var = env.import_encode_symbol(Symbol::ENCODE_TO_ENCODER);
|
||||
let to_encoder_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TO_ENCODER);
|
||||
|
||||
// (typeof rcd.a) -[clos]-> t1
|
||||
let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
|
||||
|
@ -549,7 +400,7 @@ fn to_encoder_record(
|
|||
|
||||
// build `Encode.record [ { key: .., value: ..}, .. ]` type
|
||||
// List { key : Str, value : Encoder fmt } -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let encode_record_fn_var = env.import_encode_symbol(Symbol::ENCODE_RECORD);
|
||||
let encode_record_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_RECORD);
|
||||
|
||||
// fields_list_var -[clos]-> t1
|
||||
let fields_list_var_slice =
|
||||
|
@ -687,7 +538,8 @@ fn to_encoder_tag_union(
|
|||
.map(|(&sym, &sym_var)| {
|
||||
// build `toEncoder v1` type
|
||||
// expected: val -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let to_encoder_fn_var = env.import_encode_symbol(Symbol::ENCODE_TO_ENCODER);
|
||||
let to_encoder_fn_var =
|
||||
env.import_builtin_symbol_var(Symbol::ENCODE_TO_ENCODER);
|
||||
|
||||
// wanted: t1 -[clos]-> t'
|
||||
let var_slice_of_sym_var =
|
||||
|
@ -747,7 +599,7 @@ fn to_encoder_tag_union(
|
|||
|
||||
// build `Encode.tag "A" [ ... ]` type
|
||||
// expected: Str, List (Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let encode_tag_fn_var = env.import_encode_symbol(Symbol::ENCODE_TAG);
|
||||
let encode_tag_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TAG);
|
||||
|
||||
// wanted: Str, List whole_encoders_var -[clos]-> t'
|
||||
let this_encode_tag_args_var_slice = VariableSubsSlice::insert_into_subs(
|
||||
|
@ -904,7 +756,7 @@ fn wrap_in_encode_custom(
|
|||
|
||||
// build `Encode.appendWith bytes encoder fmt` type
|
||||
// expected: Encode.appendWith : List U8, Encoder fmt, fmt -[appendWith]-> List U8 | fmt has EncoderFormatting
|
||||
let append_with_fn_var = env.import_encode_symbol(Symbol::ENCODE_APPEND_WITH);
|
||||
let append_with_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_APPEND_WITH);
|
||||
|
||||
// wanted: Encode.appendWith : List U8, encoder_var, fmt -[clos]-> List U8 | fmt has EncoderFormatting
|
||||
let this_append_with_args_var_slice =
|
||||
|
@ -995,7 +847,7 @@ fn wrap_in_encode_custom(
|
|||
// Encode.custom \bytes, fmt -> Encode.appendWith bytes encoder fmt
|
||||
//
|
||||
// expected: Encode.custom : (List U8, fmt -> List U8) -> Encoder fmt | fmt has EncoderFormatting
|
||||
let custom_fn_var = env.import_encode_symbol(Symbol::ENCODE_CUSTOM);
|
||||
let custom_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_CUSTOM);
|
||||
|
||||
// wanted: Encode.custom : fn_var -[clos]-> t'
|
||||
let this_custom_args_var_slice = VariableSubsSlice::insert_into_subs(env.subs, [fn_var]);
|
||||
|
|
|
@ -14,9 +14,13 @@ use roc_region::all::Loc;
|
|||
use roc_types::subs::{
|
||||
copy_import_to, Content, Descriptor, Mark, OptVariable, Rank, Subs, Variable,
|
||||
};
|
||||
use util::Env;
|
||||
|
||||
mod decoding;
|
||||
mod encoding;
|
||||
|
||||
mod util;
|
||||
|
||||
pub(crate) const DERIVED_SYNTH: ModuleId = ModuleId::DERIVED_SYNTH;
|
||||
|
||||
pub fn synth_var(subs: &mut Subs, content: Content) -> Variable {
|
||||
|
@ -56,20 +60,23 @@ fn build_derived_body(
|
|||
derived_symbol: Symbol,
|
||||
derive_key: DeriveKey,
|
||||
) -> (Def, SpecializationLambdaSets) {
|
||||
let mut env = Env {
|
||||
subs: derived_subs,
|
||||
exposed_types: exposed_by_module,
|
||||
derived_ident_ids,
|
||||
};
|
||||
|
||||
let DerivedBody {
|
||||
body,
|
||||
body_type,
|
||||
specialization_lambda_sets,
|
||||
} = match derive_key {
|
||||
DeriveKey::ToEncoder(to_encoder_key) => {
|
||||
let mut env = encoding::Env {
|
||||
subs: derived_subs,
|
||||
exposed_types: exposed_by_module,
|
||||
derived_ident_ids,
|
||||
};
|
||||
encoding::derive_to_encoder(&mut env, to_encoder_key, derived_symbol)
|
||||
}
|
||||
DeriveKey::Decoding => todo!(),
|
||||
DeriveKey::Decoder(decoder_key) => {
|
||||
decoding::derive_decoder(&mut env, decoder_key, derived_symbol)
|
||||
}
|
||||
};
|
||||
|
||||
let def = Def {
|
||||
|
@ -174,18 +181,18 @@ impl DerivedModule {
|
|||
&mut self,
|
||||
gen_subs: &mut Subs,
|
||||
should_load_def: impl Fn(Symbol) -> bool,
|
||||
) -> VecMap<Symbol, Expr> {
|
||||
) -> VecMap<Symbol, (Expr, Variable)> {
|
||||
self.map
|
||||
.values()
|
||||
.filter_map(|(symbol, def, _)| {
|
||||
if should_load_def(*symbol) {
|
||||
let (_new_expr_var, new_expr) = roc_can::copy::deep_copy_expr_across_subs(
|
||||
let (new_expr_var, new_expr) = roc_can::copy::deep_copy_expr_across_subs(
|
||||
&mut self.subs,
|
||||
gen_subs,
|
||||
def.expr_var,
|
||||
&def.loc_expr.value,
|
||||
);
|
||||
Some((*symbol, new_expr))
|
||||
Some((*symbol, (new_expr, new_expr_var)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
155
crates/compiler/derive/src/util.rs
Normal file
155
crates/compiler/derive/src/util.rs
Normal file
|
@ -0,0 +1,155 @@
|
|||
use roc_can::{abilities::SpecializationLambdaSets, module::ExposedByModule};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::{IdentIds, Symbol};
|
||||
use roc_types::subs::{instantiate_rigids, Subs, Variable};
|
||||
|
||||
use crate::DERIVED_SYNTH;
|
||||
|
||||
/// An environment representing the Derived_synth module, for use in building derived
|
||||
/// implementations.
|
||||
pub(crate) struct Env<'a> {
|
||||
/// NB: This **must** be subs for the derive module!
|
||||
pub subs: &'a mut Subs,
|
||||
pub exposed_types: &'a ExposedByModule,
|
||||
pub derived_ident_ids: &'a mut IdentIds,
|
||||
}
|
||||
|
||||
impl Env<'_> {
|
||||
pub fn new_symbol(&mut self, name_hint: &str) -> Symbol {
|
||||
if cfg!(any(
|
||||
debug_assertions,
|
||||
test,
|
||||
feature = "debug-derived-symbols"
|
||||
)) {
|
||||
let mut i = 0;
|
||||
let debug_name = loop {
|
||||
i += 1;
|
||||
let name = if i == 1 {
|
||||
name_hint.to_owned()
|
||||
} else {
|
||||
format!("{}{}", name_hint, i)
|
||||
};
|
||||
if self.derived_ident_ids.get_id(&name).is_none() {
|
||||
break name;
|
||||
}
|
||||
};
|
||||
|
||||
let ident_id = self.derived_ident_ids.get_or_insert(&debug_name);
|
||||
|
||||
Symbol::new(DERIVED_SYNTH, ident_id)
|
||||
} else {
|
||||
self.unique_symbol()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unique_symbol(&mut self) -> Symbol {
|
||||
let ident_id = self.derived_ident_ids.gen_unique();
|
||||
Symbol::new(DERIVED_SYNTH, ident_id)
|
||||
}
|
||||
|
||||
pub fn import_builtin_symbol_var(&mut self, symbol: Symbol) -> Variable {
|
||||
let module_id = symbol.module_id();
|
||||
debug_assert!(module_id.is_builtin());
|
||||
|
||||
let module_types = &self
|
||||
.exposed_types
|
||||
.get(&module_id)
|
||||
.unwrap()
|
||||
.exposed_types_storage_subs;
|
||||
let storage_var = module_types.stored_vars_by_symbol.get(&symbol).unwrap();
|
||||
let imported = module_types
|
||||
.storage_subs
|
||||
.export_variable_to_directly_to_use_site(self.subs, *storage_var);
|
||||
|
||||
instantiate_rigids(self.subs, imported.variable);
|
||||
|
||||
imported.variable
|
||||
}
|
||||
|
||||
pub fn unify(&mut self, left: Variable, right: Variable) {
|
||||
use roc_unify::unify::{unify, Env, Mode, Unified};
|
||||
|
||||
let unified = unify(&mut Env::new(self.subs), left, right, Mode::EQ);
|
||||
|
||||
match unified {
|
||||
Unified::Success {
|
||||
vars: _,
|
||||
must_implement_ability: _,
|
||||
lambda_sets_to_specialize,
|
||||
extra_metadata: _,
|
||||
} => {
|
||||
if !lambda_sets_to_specialize.is_empty() {
|
||||
internal_error!("Did not expect derivers to need to specialize unspecialized lambda sets, but we got some: {:?}", lambda_sets_to_specialize)
|
||||
}
|
||||
}
|
||||
Unified::Failure(..) | Unified::BadType(..) => {
|
||||
internal_error!("Unification failed in deriver - that's a deriver bug!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_specialization_lambda_sets(
|
||||
&mut self,
|
||||
specialization_type: Variable,
|
||||
ability_member: Symbol,
|
||||
) -> SpecializationLambdaSets {
|
||||
use roc_unify::unify::{unify_introduced_ability_specialization, Env, Mode, Unified};
|
||||
|
||||
let member_signature = self.import_builtin_symbol_var(ability_member);
|
||||
|
||||
let unified = unify_introduced_ability_specialization(
|
||||
&mut Env::new(self.subs),
|
||||
member_signature,
|
||||
specialization_type,
|
||||
Mode::EQ,
|
||||
);
|
||||
|
||||
match unified {
|
||||
Unified::Success {
|
||||
vars: _,
|
||||
must_implement_ability: _,
|
||||
lambda_sets_to_specialize: _lambda_sets_to_specialize,
|
||||
extra_metadata: specialization_lsets,
|
||||
} => {
|
||||
let specialization_lsets: SpecializationLambdaSets = specialization_lsets
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|((spec_member, region), var)| {
|
||||
debug_assert_eq!(spec_member, ability_member);
|
||||
(region, var)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Since we're doing `{foo} ~ a | a has Encoding`, we may see "lambda sets to
|
||||
// specialize" for e.g. `{foo}:toEncoder:1`, but these are actually just the
|
||||
// specialization lambda sets, so we don't need to do any extra work!
|
||||
//
|
||||
// If there are other lambda sets to specialize in here, that's unexpected, because
|
||||
// that means we would have been deriving something like `toEncoder {foo: bar}`,
|
||||
// and now seen that we needed `toEncoder bar` where `bar` is a concrete type. But
|
||||
// we only expect `bar` to polymorphic at this stage!
|
||||
//
|
||||
// TODO: it would be better if `unify` could prune these for us. See also
|
||||
// https://github.com/rtfeldman/roc/issues/3207; that is a blocker for this TODO.
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
for (spec_var, lambda_sets) in _lambda_sets_to_specialize.drain() {
|
||||
for lambda_set in lambda_sets {
|
||||
let belongs_to_specialized_lambda_sets =
|
||||
specialization_lsets.iter().any(|(_, var)| {
|
||||
self.subs.get_root_key_without_compacting(*var)
|
||||
== self.subs.get_root_key_without_compacting(lambda_set)
|
||||
});
|
||||
debug_assert!(belongs_to_specialized_lambda_sets,
|
||||
"Did not expect derivers to need to specialize unspecialized lambda sets, but we got one: {:?} for {:?}", lambda_set, spec_var)
|
||||
}
|
||||
}
|
||||
}
|
||||
specialization_lsets
|
||||
}
|
||||
Unified::Failure(..) | Unified::BadType(..) => {
|
||||
internal_error!("Unification failed in deriver - that's a deriver bug!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_derive_key"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
84
crates/compiler/derive_key/src/decoding.rs
Normal file
84
crates/compiler/derive_key/src/decoding.rs
Normal file
|
@ -0,0 +1,84 @@
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||
|
||||
use crate::DeriveError;
|
||||
|
||||
#[derive(Hash)]
|
||||
pub enum FlatDecodable {
|
||||
Immediate(Symbol),
|
||||
Key(FlatDecodableKey),
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
|
||||
pub enum FlatDecodableKey {
|
||||
List(/* takes one variable */),
|
||||
}
|
||||
|
||||
impl FlatDecodableKey {
|
||||
pub(crate) fn debug_name(&self) -> String {
|
||||
match self {
|
||||
FlatDecodableKey::List() => "list".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FlatDecodable {
|
||||
pub(crate) fn from_var(subs: &Subs, var: Variable) -> Result<FlatDecodable, DeriveError> {
|
||||
use DeriveError::*;
|
||||
use FlatDecodable::*;
|
||||
match *subs.get_content_without_compacting(var) {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
FlatType::Apply(sym, _) => match sym {
|
||||
Symbol::LIST_LIST => Ok(Key(FlatDecodableKey::List())),
|
||||
Symbol::STR_STR => Ok(Immediate(Symbol::DECODE_STRING)),
|
||||
_ => Err(Underivable),
|
||||
},
|
||||
FlatType::Record(_fields, _ext) => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(_name_index, _, _) => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::EmptyRecord => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::EmptyTagUnion => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
//
|
||||
FlatType::Erroneous(_) => Err(Underivable),
|
||||
FlatType::Func(..) => Err(Underivable),
|
||||
},
|
||||
Content::Alias(sym, _, real_var, _) => match sym {
|
||||
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Ok(Immediate(Symbol::DECODE_U8)),
|
||||
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Ok(Immediate(Symbol::DECODE_U16)),
|
||||
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Ok(Immediate(Symbol::DECODE_U32)),
|
||||
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Ok(Immediate(Symbol::DECODE_U64)),
|
||||
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Ok(Immediate(Symbol::DECODE_U128)),
|
||||
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Ok(Immediate(Symbol::DECODE_I8)),
|
||||
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Ok(Immediate(Symbol::DECODE_I16)),
|
||||
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Ok(Immediate(Symbol::DECODE_I32)),
|
||||
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Ok(Immediate(Symbol::DECODE_I64)),
|
||||
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Ok(Immediate(Symbol::DECODE_I128)),
|
||||
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Ok(Immediate(Symbol::DECODE_DEC)),
|
||||
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Ok(Immediate(Symbol::DECODE_F32)),
|
||||
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Ok(Immediate(Symbol::DECODE_F64)),
|
||||
// NB: I believe it is okay to unwrap opaques here because derivers are only used
|
||||
// by the backend, and the backend treats opaques like structural aliases.
|
||||
_ => Self::from_var(subs, real_var),
|
||||
},
|
||||
Content::RangedNumber(_) => Err(Underivable),
|
||||
//
|
||||
Content::RecursionVar { .. } => Err(Underivable),
|
||||
Content::Error => Err(Underivable),
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
|
||||
Content::LambdaSet(_) => Err(Underivable),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,8 +13,10 @@
|
|||
//! For these reasons the content keying is based on a strategy as well, which are the variants of
|
||||
//! [`DeriveKey`].
|
||||
|
||||
pub mod decoding;
|
||||
pub mod encoding;
|
||||
|
||||
use decoding::{FlatDecodable, FlatDecodableKey};
|
||||
use encoding::{FlatEncodable, FlatEncodableKey};
|
||||
|
||||
use roc_module::symbol::Symbol;
|
||||
|
@ -33,15 +35,14 @@ pub enum DeriveError {
|
|||
#[repr(u8)]
|
||||
pub enum DeriveKey {
|
||||
ToEncoder(FlatEncodableKey),
|
||||
#[allow(unused)]
|
||||
Decoding,
|
||||
Decoder(FlatDecodableKey),
|
||||
}
|
||||
|
||||
impl DeriveKey {
|
||||
pub fn debug_name(&self) -> String {
|
||||
match self {
|
||||
DeriveKey::ToEncoder(key) => format!("toEncoder_{}", key.debug_name()),
|
||||
DeriveKey::Decoding => todo!(),
|
||||
DeriveKey::Decoder(key) => format!("decoder_{}", key.debug_name()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,11 +58,40 @@ pub enum Derived {
|
|||
Key(DeriveKey),
|
||||
}
|
||||
|
||||
impl Derived {
|
||||
pub fn encoding(subs: &Subs, var: Variable) -> Result<Self, DeriveError> {
|
||||
match encoding::FlatEncodable::from_var(subs, var)? {
|
||||
FlatEncodable::Immediate(imm) => Ok(Derived::Immediate(imm)),
|
||||
FlatEncodable::Key(repr) => Ok(Derived::Key(DeriveKey::ToEncoder(repr))),
|
||||
/// The builtin ability member to derive.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DeriveBuiltin {
|
||||
ToEncoder,
|
||||
Decoder,
|
||||
}
|
||||
|
||||
impl TryFrom<Symbol> for DeriveBuiltin {
|
||||
type Error = Symbol;
|
||||
|
||||
fn try_from(value: Symbol) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Symbol::ENCODE_TO_ENCODER => Ok(DeriveBuiltin::ToEncoder),
|
||||
Symbol::DECODE_DECODER => Ok(DeriveBuiltin::Decoder),
|
||||
_ => Err(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Derived {
|
||||
pub fn builtin(
|
||||
builtin: DeriveBuiltin,
|
||||
subs: &Subs,
|
||||
var: Variable,
|
||||
) -> Result<Self, DeriveError> {
|
||||
match builtin {
|
||||
DeriveBuiltin::ToEncoder => match encoding::FlatEncodable::from_var(subs, var)? {
|
||||
FlatEncodable::Immediate(imm) => Ok(Derived::Immediate(imm)),
|
||||
FlatEncodable::Key(repr) => Ok(Derived::Key(DeriveKey::ToEncoder(repr))),
|
||||
},
|
||||
DeriveBuiltin::Decoder => match decoding::FlatDecodable::from_var(subs, var)? {
|
||||
FlatDecodable::Immediate(imm) => Ok(Derived::Immediate(imm)),
|
||||
FlatDecodable::Key(repr) => Ok(Derived::Key(DeriveKey::Decoder(repr))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_exhaustive"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_fmt"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -288,13 +288,18 @@ fn fmt_expect<'a, 'buf>(
|
|||
is_multiline: bool,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str("expect");
|
||||
|
||||
let return_indent = if is_multiline {
|
||||
buf.newline();
|
||||
indent + INDENT
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
indent
|
||||
};
|
||||
|
||||
buf.push_str("expect");
|
||||
condition.format(buf, return_indent);
|
||||
}
|
||||
|
||||
|
|
|
@ -815,16 +815,24 @@ fn fmt_expect<'a, 'buf>(
|
|||
is_multiline: bool,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str("expect");
|
||||
|
||||
let return_indent = if is_multiline {
|
||||
buf.newline();
|
||||
indent + INDENT
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
indent
|
||||
};
|
||||
|
||||
buf.push_str("expect");
|
||||
condition.format(buf, return_indent);
|
||||
buf.push('\n');
|
||||
continuation.format(buf, return_indent);
|
||||
|
||||
// Always put a blank line after the `expect` line(s)
|
||||
buf.ensure_ends_with_blank_line();
|
||||
|
||||
continuation.format(buf, indent);
|
||||
}
|
||||
|
||||
fn fmt_if<'a, 'buf>(
|
||||
|
|
|
@ -5287,7 +5287,7 @@ mod test_fmt {
|
|||
eq1,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
0
|
||||
"#
|
||||
),
|
||||
|
@ -5399,6 +5399,64 @@ mod test_fmt {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expect_single_line() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
|
||||
expect x == y
|
||||
|
||||
expect y == z
|
||||
|
||||
42
|
||||
"#
|
||||
));
|
||||
|
||||
module_formats_same(indoc!(
|
||||
r#"
|
||||
interface Foo exposes [] imports []
|
||||
|
||||
expect x == y
|
||||
|
||||
expect y == z
|
||||
|
||||
foo = bar
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expect_multiline() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
|
||||
expect
|
||||
foo bar
|
||||
|> baz
|
||||
|
||||
42
|
||||
"#
|
||||
));
|
||||
|
||||
module_formats_same(indoc!(
|
||||
r#"
|
||||
interface Foo exposes [] imports []
|
||||
|
||||
expect
|
||||
foo bar
|
||||
|> baz
|
||||
|
||||
expect
|
||||
blah
|
||||
etc
|
||||
|
||||
foo = bar
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
// this is a parse error atm
|
||||
// #[test]
|
||||
// fn multiline_apply() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "roc_gen_dev"
|
||||
description = "The development backend for the Roc compiler"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "roc_gen_llvm"
|
||||
description = "The LLVM backend for the Roc compiler"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -4220,10 +4220,16 @@ pub fn build_procedures<'a, 'ctx, 'env>(
|
|||
env: &Env<'a, 'ctx, 'env>,
|
||||
opt_level: OptLevel,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
|
||||
entry_point: EntryPoint<'a>,
|
||||
opt_entry_point: Option<EntryPoint<'a>>,
|
||||
debug_output_file: Option<&Path>,
|
||||
) {
|
||||
build_procedures_help(env, opt_level, procedures, entry_point, debug_output_file);
|
||||
build_procedures_help(
|
||||
env,
|
||||
opt_level,
|
||||
procedures,
|
||||
opt_entry_point,
|
||||
debug_output_file,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn build_wasm_test_wrapper<'a, 'ctx, 'env>(
|
||||
|
@ -4236,7 +4242,7 @@ pub fn build_wasm_test_wrapper<'a, 'ctx, 'env>(
|
|||
env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
Some(entry_point),
|
||||
Some(Path::new("/tmp/test.ll")),
|
||||
);
|
||||
|
||||
|
@ -4253,7 +4259,7 @@ pub fn build_procedures_return_main<'a, 'ctx, 'env>(
|
|||
env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
Some(entry_point),
|
||||
Some(Path::new("/tmp/test.ll")),
|
||||
);
|
||||
|
||||
|
@ -4265,13 +4271,13 @@ pub fn build_procedures_expose_expects<'a, 'ctx, 'env>(
|
|||
opt_level: OptLevel,
|
||||
expects: &[Symbol],
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
|
||||
entry_point: EntryPoint<'a>,
|
||||
opt_entry_point: Option<EntryPoint<'a>>,
|
||||
) -> Vec<'a, &'a str> {
|
||||
let mod_solutions = build_procedures_help(
|
||||
env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
opt_entry_point,
|
||||
Some(Path::new("/tmp/test.ll")),
|
||||
);
|
||||
|
||||
|
@ -4333,7 +4339,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
|
|||
env: &Env<'a, 'ctx, 'env>,
|
||||
opt_level: OptLevel,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
|
||||
entry_point: EntryPoint<'a>,
|
||||
opt_entry_point: Option<EntryPoint<'a>>,
|
||||
debug_output_file: Option<&Path>,
|
||||
) -> &'a ModSolutions {
|
||||
let mut layout_ids = roc_mono::layout::LayoutIds::default();
|
||||
|
@ -4341,7 +4347,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
|
|||
|
||||
let it = procedures.iter().map(|x| x.1);
|
||||
|
||||
let solutions = match roc_alias_analysis::spec_program(opt_level, entry_point, it) {
|
||||
let solutions = match roc_alias_analysis::spec_program(opt_level, opt_entry_point, it) {
|
||||
Err(e) => panic!("Error in alias analysis: {}", e),
|
||||
Ok(solutions) => solutions,
|
||||
};
|
||||
|
@ -6951,21 +6957,30 @@ fn build_int_binop<'a, 'ctx, 'env>(
|
|||
// but llvm normalizes to the above ordering in -O3
|
||||
let zero = rhs.get_type().const_zero();
|
||||
let neg_1 = rhs.get_type().const_int(-1i64 as u64, false);
|
||||
let is_signed = int_width.is_signed();
|
||||
|
||||
let special_block = env.context.append_basic_block(parent, "special_block");
|
||||
let default_block = env.context.append_basic_block(parent, "default_block");
|
||||
let cont_block = env.context.append_basic_block(parent, "branchcont");
|
||||
|
||||
bd.build_switch(
|
||||
rhs,
|
||||
default_block,
|
||||
&[(zero, special_block), (neg_1, special_block)],
|
||||
);
|
||||
if is_signed {
|
||||
bd.build_switch(
|
||||
rhs,
|
||||
default_block,
|
||||
&[(zero, special_block), (neg_1, special_block)],
|
||||
)
|
||||
} else {
|
||||
bd.build_switch(rhs, default_block, &[(zero, special_block)])
|
||||
};
|
||||
|
||||
let condition_rem = {
|
||||
bd.position_at_end(default_block);
|
||||
|
||||
let rem = bd.build_int_signed_rem(lhs, rhs, "int_rem");
|
||||
let rem = if is_signed {
|
||||
bd.build_int_signed_rem(lhs, rhs, "int_rem")
|
||||
} else {
|
||||
bd.build_int_unsigned_rem(lhs, rhs, "uint_rem")
|
||||
};
|
||||
let result = bd.build_int_compare(IntPredicate::EQ, rem, zero, "is_zero_rem");
|
||||
|
||||
bd.build_unconditional_branch(cont_block);
|
||||
|
@ -6976,10 +6991,15 @@ fn build_int_binop<'a, 'ctx, 'env>(
|
|||
bd.position_at_end(special_block);
|
||||
|
||||
let is_zero = bd.build_int_compare(IntPredicate::EQ, lhs, zero, "is_zero_lhs");
|
||||
let is_neg_one =
|
||||
bd.build_int_compare(IntPredicate::EQ, rhs, neg_1, "is_neg_one_rhs");
|
||||
|
||||
let result = bd.build_or(is_neg_one, is_zero, "cond");
|
||||
let result = if is_signed {
|
||||
let is_neg_one =
|
||||
bd.build_int_compare(IntPredicate::EQ, rhs, neg_1, "is_neg_one_rhs");
|
||||
|
||||
bd.build_or(is_neg_one, is_zero, "cond")
|
||||
} else {
|
||||
is_zero
|
||||
};
|
||||
|
||||
bd.build_unconditional_branch(cont_block);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::debug_info_init;
|
||||
use crate::llvm::bitcode::call_str_bitcode_fn;
|
||||
use crate::llvm::build::{get_tag_id, store_roc_value, Env};
|
||||
use crate::llvm::build::{get_tag_id, store_roc_value, tag_pointer_clear_tag_id, Env};
|
||||
use crate::llvm::build_list::{self, incrementing_elem_loop};
|
||||
use crate::llvm::convert::{basic_type_from_layout, RocUnion};
|
||||
use inkwell::builder::Builder;
|
||||
|
@ -503,6 +503,9 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
|
|||
.unwrap();
|
||||
|
||||
let layout = Layout::struct_no_name_order(field_layouts);
|
||||
let layout = Layout::struct_no_name_order(
|
||||
env.arena.alloc([layout, union_layout.tag_id_layout()]),
|
||||
);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
|
@ -533,7 +536,304 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
Recursive(tags) => {
|
||||
let id = get_tag_id(env, parent, &union_layout, tag_value);
|
||||
|
||||
let switch_block = env.context.append_basic_block(parent, "switch_block");
|
||||
env.builder.build_unconditional_branch(switch_block);
|
||||
|
||||
let mut cases = Vec::with_capacity_in(tags.len(), env.arena);
|
||||
|
||||
for (tag_id, field_layouts) in tags.iter().enumerate() {
|
||||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
// write the "pointer" of the current offset
|
||||
write_pointer_with_tag_id(env, ptr, offset, extra_offset, union_layout, tag_id);
|
||||
|
||||
let tag_value = tag_pointer_clear_tag_id(env, tag_value.into_pointer_value());
|
||||
|
||||
let raw_data_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(tag_value, RocUnion::TAG_DATA_INDEX, "tag_data")
|
||||
.unwrap();
|
||||
|
||||
let layout = Layout::struct_no_name_order(field_layouts);
|
||||
let layout = if union_layout.stores_tag_id_in_pointer(env.target_info) {
|
||||
layout
|
||||
} else {
|
||||
Layout::struct_no_name_order(
|
||||
env.arena.alloc([layout, union_layout.tag_id_layout()]),
|
||||
)
|
||||
};
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
raw_data_ptr,
|
||||
basic_type.ptr_type(AddressSpace::Generic),
|
||||
"data_ptr",
|
||||
);
|
||||
|
||||
let data = env.builder.build_load(data_ptr, "load_data");
|
||||
|
||||
let (width, _) = union_layout.data_size_and_alignment(env.target_info);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
extra_offset: env.builder.build_int_add(
|
||||
extra_offset,
|
||||
env.ptr_int().const_int(width as _, false),
|
||||
"new_offset",
|
||||
),
|
||||
};
|
||||
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
let answer =
|
||||
build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
|
||||
cases.push((id.get_type().const_int(tag_id as u64, false), block));
|
||||
}
|
||||
|
||||
env.builder.position_at_end(switch_block);
|
||||
|
||||
match cases.pop() {
|
||||
Some((_, default)) => {
|
||||
env.builder.build_switch(id, default, &cases);
|
||||
}
|
||||
None => {
|
||||
// we're serializing an empty tag union; this code is effectively unreachable
|
||||
env.builder.build_unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
NonNullableUnwrapped(fields) => {
|
||||
//
|
||||
|
||||
let tag_value = tag_value.into_pointer_value();
|
||||
|
||||
build_copy(env, ptr, offset, extra_offset.into());
|
||||
|
||||
let layout = Layout::struct_no_name_order(fields);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let (width, _) = union_layout.data_size_and_alignment(env.target_info);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
extra_offset: env.builder.build_int_add(
|
||||
extra_offset,
|
||||
env.ptr_int().const_int(width as _, false),
|
||||
"new_offset",
|
||||
),
|
||||
};
|
||||
|
||||
let raw_data_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(tag_value, RocUnion::TAG_DATA_INDEX, "tag_data")
|
||||
.unwrap();
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
raw_data_ptr,
|
||||
basic_type.ptr_type(AddressSpace::Generic),
|
||||
"data_ptr",
|
||||
);
|
||||
|
||||
let data = env.builder.build_load(data_ptr, "load_data");
|
||||
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
let answer = build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
}
|
||||
NullableWrapped {
|
||||
nullable_id,
|
||||
other_tags,
|
||||
} => {
|
||||
let switch_block = env.context.append_basic_block(parent, "switch_block");
|
||||
let null_block = env.context.append_basic_block(parent, "null_block");
|
||||
|
||||
let id = get_tag_id(env, parent, &union_layout, tag_value);
|
||||
|
||||
let comparison = env
|
||||
.builder
|
||||
.build_is_null(tag_value.into_pointer_value(), "is_null");
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(comparison, null_block, switch_block);
|
||||
|
||||
{
|
||||
let mut cases = Vec::with_capacity_in(other_tags.len(), env.arena);
|
||||
|
||||
for i in 0..other_tags.len() + 1 {
|
||||
if i == nullable_id as _ {
|
||||
continue;
|
||||
}
|
||||
|
||||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
// write the "pointer" of the current offset
|
||||
write_pointer_with_tag_id(env, ptr, offset, extra_offset, union_layout, i);
|
||||
|
||||
let fields = if i >= nullable_id as _ {
|
||||
other_tags[i - 1]
|
||||
} else {
|
||||
other_tags[i]
|
||||
};
|
||||
|
||||
let layout = Layout::struct_no_name_order(fields);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let (width, _) = union_layout.data_size_and_alignment(env.target_info);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
extra_offset: env.builder.build_int_add(
|
||||
extra_offset,
|
||||
env.ptr_int().const_int(width as _, false),
|
||||
"new_offset",
|
||||
),
|
||||
};
|
||||
|
||||
let tag_value = tag_pointer_clear_tag_id(env, tag_value.into_pointer_value());
|
||||
|
||||
let raw_data_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(tag_value, RocUnion::TAG_DATA_INDEX, "tag_data")
|
||||
.unwrap();
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
raw_data_ptr,
|
||||
basic_type.ptr_type(AddressSpace::Generic),
|
||||
"data_ptr",
|
||||
);
|
||||
|
||||
let data = env.builder.build_load(data_ptr, "load_data");
|
||||
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
let answer =
|
||||
build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
|
||||
cases.push((id.get_type().const_int(i as u64, false), block));
|
||||
}
|
||||
|
||||
env.builder.position_at_end(switch_block);
|
||||
|
||||
match cases.pop() {
|
||||
Some((_, default)) => {
|
||||
env.builder.build_switch(id, default, &cases);
|
||||
}
|
||||
None => {
|
||||
// we're serializing an empty tag union; this code is effectively unreachable
|
||||
env.builder.build_unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
env.builder.position_at_end(null_block);
|
||||
|
||||
let value = env.ptr_int().const_zero();
|
||||
build_copy(env, ptr, offset, value.into());
|
||||
|
||||
env.builder.build_return(Some(&extra_offset));
|
||||
}
|
||||
}
|
||||
NullableUnwrapped { other_fields, .. } => {
|
||||
let other_block = env.context.append_basic_block(parent, "other_block");
|
||||
let null_block = env.context.append_basic_block(parent, "null_block");
|
||||
|
||||
let comparison = env
|
||||
.builder
|
||||
.build_is_null(tag_value.into_pointer_value(), "is_null");
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(comparison, null_block, other_block);
|
||||
|
||||
{
|
||||
env.builder.position_at_end(null_block);
|
||||
|
||||
let value = env.ptr_int().const_zero();
|
||||
build_copy(env, ptr, offset, value.into());
|
||||
|
||||
env.builder.build_return(Some(&extra_offset));
|
||||
}
|
||||
|
||||
{
|
||||
env.builder.position_at_end(other_block);
|
||||
|
||||
// write the "pointer" af the current offset
|
||||
build_copy(env, ptr, offset, extra_offset.into());
|
||||
|
||||
let layout = Layout::struct_no_name_order(other_fields);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
extra_offset: env.builder.build_int_add(
|
||||
extra_offset,
|
||||
env.ptr_int()
|
||||
.const_int(layout.stack_size(env.target_info) as _, false),
|
||||
"new_offset",
|
||||
),
|
||||
};
|
||||
|
||||
let raw_data_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(
|
||||
tag_value.into_pointer_value(),
|
||||
RocUnion::TAG_DATA_INDEX,
|
||||
"tag_data",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let data_ptr = env.builder.build_pointer_cast(
|
||||
raw_data_ptr,
|
||||
basic_type.ptr_type(AddressSpace::Generic),
|
||||
"data_ptr",
|
||||
);
|
||||
|
||||
let data = env.builder.build_load(data_ptr, "load_data");
|
||||
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
let answer =
|
||||
build_clone(env, layout_ids, ptr, cursors, data, layout, when_recursive);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_pointer_with_tag_id<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
ptr: PointerValue<'ctx>,
|
||||
offset: IntValue<'ctx>,
|
||||
extra_offset: IntValue<'ctx>,
|
||||
union_layout: UnionLayout<'a>,
|
||||
tag_id: usize,
|
||||
) {
|
||||
if union_layout.stores_tag_id_in_pointer(env.target_info) {
|
||||
// first, store tag id as u32
|
||||
let tag_id_intval = env.context.i32_type().const_int(tag_id as _, false);
|
||||
build_copy(env, ptr, offset, tag_id_intval.into());
|
||||
|
||||
// increment offset by 4
|
||||
let four = env.ptr_int().const_int(4, false);
|
||||
let offset = env.builder.build_int_add(offset, four, "");
|
||||
|
||||
// cast to u32
|
||||
let extra_offset = env
|
||||
.builder
|
||||
.build_int_cast(extra_offset, env.context.i32_type(), "");
|
||||
|
||||
build_copy(env, ptr, offset, extra_offset.into());
|
||||
} else {
|
||||
build_copy(env, ptr, offset, extra_offset.into());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
[package]
|
||||
name = "roc_gen_wasm"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
|
||||
[dependencies]
|
||||
bitvec = "1"
|
||||
|
|
|
@ -715,7 +715,7 @@ impl<'a> WasmBackend<'a> {
|
|||
let mut current_stmt = stmt;
|
||||
while let Stmt::Let(sym, expr, layout, following) = current_stmt {
|
||||
if DEBUG_SETTINGS.let_stmt_ir {
|
||||
println!("let {:?} = {}", sym, expr.to_pretty(200)); // ignore `following`! Too confusing otherwise.
|
||||
print!("\nlet {:?} = {}", sym, expr.to_pretty(200));
|
||||
}
|
||||
|
||||
let kind = match following {
|
||||
|
@ -1479,7 +1479,14 @@ impl<'a> WasmBackend<'a> {
|
|||
// length of the list
|
||||
self.code_builder.get_local(stack_local_id);
|
||||
self.code_builder.i32_const(elems.len() as i32);
|
||||
self.code_builder.i32_store(Align::Bytes4, stack_offset + 4);
|
||||
self.code_builder
|
||||
.i32_store(Align::Bytes4, stack_offset + 4 * Builtin::WRAPPER_LEN);
|
||||
|
||||
// capacity of the list
|
||||
self.code_builder.get_local(stack_local_id);
|
||||
self.code_builder.i32_const(elems.len() as i32);
|
||||
self.code_builder
|
||||
.i32_store(Align::Bytes4, stack_offset + 4 * Builtin::WRAPPER_CAPACITY);
|
||||
|
||||
let mut elem_offset = 0;
|
||||
|
||||
|
@ -1521,12 +1528,14 @@ impl<'a> WasmBackend<'a> {
|
|||
if let StoredValue::StackMemory { location, .. } = storage {
|
||||
let (local_id, offset) = location.local_and_offset(self.storage.stack_frame_pointer);
|
||||
|
||||
// This is a minor cheat.
|
||||
// What we want to write to stack memory is { elements: null, length: 0 }
|
||||
// But instead of two 32-bit stores, we can do a single 64-bit store.
|
||||
// Store 12 bytes of zeros { elements: null, length: 0, capacity: 0 }
|
||||
debug_assert_eq!(Builtin::LIST_WORDS, 3);
|
||||
self.code_builder.get_local(local_id);
|
||||
self.code_builder.i64_const(0);
|
||||
self.code_builder.i64_store(Align::Bytes4, offset);
|
||||
self.code_builder.get_local(local_id);
|
||||
self.code_builder.i32_const(0);
|
||||
self.code_builder.i32_store(Align::Bytes4, offset + 8);
|
||||
} else {
|
||||
internal_error!("Unexpected storage for {:?}", sym)
|
||||
}
|
||||
|
@ -1566,13 +1575,24 @@ impl<'a> WasmBackend<'a> {
|
|||
StoredValue::Local { local_id, .. } => {
|
||||
// Tag is stored as a heap pointer.
|
||||
if let Some(reused) = maybe_reused {
|
||||
// Reuse an existing heap allocation
|
||||
// Reuse an existing heap allocation, if one is available (not NULL at runtime)
|
||||
self.storage.load_symbols(&mut self.code_builder, &[reused]);
|
||||
self.code_builder.if_();
|
||||
{
|
||||
self.storage.load_symbols(&mut self.code_builder, &[reused]);
|
||||
self.code_builder.set_local(local_id);
|
||||
}
|
||||
self.code_builder.else_();
|
||||
{
|
||||
self.allocate_with_refcount(Some(data_size), data_alignment, 1);
|
||||
self.code_builder.set_local(local_id);
|
||||
}
|
||||
self.code_builder.end();
|
||||
} else {
|
||||
// Call the allocator to get a memory address.
|
||||
self.allocate_with_refcount(Some(data_size), data_alignment, 1);
|
||||
self.code_builder.set_local(local_id);
|
||||
}
|
||||
self.code_builder.set_local(local_id);
|
||||
(local_id, 0)
|
||||
}
|
||||
StoredValue::VirtualMachineStack { .. } => {
|
||||
|
@ -1618,7 +1638,7 @@ impl<'a> WasmBackend<'a> {
|
|||
self.code_builder.i64_store(id_align, id_offset);
|
||||
}
|
||||
}
|
||||
} else if stores_tag_id_in_pointer {
|
||||
} else if stores_tag_id_in_pointer && tag_id != 0 {
|
||||
self.code_builder.get_local(local_id);
|
||||
self.code_builder.i32_const(tag_id as i32);
|
||||
self.code_builder.i32_or();
|
||||
|
|
|
@ -256,7 +256,7 @@ pub struct WasmDebugSettings {
|
|||
|
||||
pub const DEBUG_SETTINGS: WasmDebugSettings = WasmDebugSettings {
|
||||
proc_start_end: false && cfg!(debug_assertions),
|
||||
user_procs_ir: false && cfg!(debug_assertions), // Note: we also have `ROC_PRINT_IR_AFTER_SPECIALIZATION=1 cargo test-gen-wasm`
|
||||
user_procs_ir: false && cfg!(debug_assertions), // Note: we also have `ROC_PRINT_IR_AFTER_REFCOUNT=1 cargo test-gen-wasm`
|
||||
helper_procs_ir: false && cfg!(debug_assertions),
|
||||
let_stmt_ir: false && cfg!(debug_assertions),
|
||||
instructions: false && cfg!(debug_assertions),
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::backend::{ProcLookupData, ProcSource, WasmBackend};
|
|||
use crate::layout::{CallConv, StackMemoryFormat, WasmLayout};
|
||||
use crate::storage::{AddressValue, StackMemoryLocation, StoredValue};
|
||||
use crate::wasm_module::{Align, LocalId, ValueType};
|
||||
use crate::TARGET_INFO;
|
||||
use crate::{PTR_TYPE, TARGET_INFO};
|
||||
|
||||
/// Number types used for Wasm code gen
|
||||
/// Unlike other enums, this contains no details about layout or storage.
|
||||
|
@ -368,25 +368,27 @@ impl<'a> LowLevelCall<'a> {
|
|||
location.local_and_offset(backend.storage.stack_frame_pointer);
|
||||
backend.code_builder.get_local(fp);
|
||||
backend.code_builder.i32_load(Align::Bytes4, offset);
|
||||
} else {
|
||||
internal_error!("Lists are always stored in stack memory");
|
||||
}
|
||||
|
||||
// Target element heap pointer
|
||||
// Get pointer to target element and save it to a local var
|
||||
backend.code_builder.i32_add(); // base + index*size
|
||||
let elem_local = backend.storage.create_anonymous_local(PTR_TYPE);
|
||||
backend.code_builder.set_local(elem_local);
|
||||
|
||||
// Copy to stack
|
||||
// Copy element value from heap to stack
|
||||
backend.storage.copy_value_from_memory(
|
||||
&mut backend.code_builder,
|
||||
self.ret_symbol,
|
||||
AddressValue::Loaded,
|
||||
AddressValue::NotLoaded(elem_local),
|
||||
0,
|
||||
);
|
||||
|
||||
// Increment refcount
|
||||
if self.ret_layout.is_refcounted() {
|
||||
let inc_fn = backend.get_refcount_fn_index(self.ret_layout, HelperOp::Inc);
|
||||
backend
|
||||
.storage
|
||||
.load_symbols(&mut backend.code_builder, &[self.ret_symbol]);
|
||||
backend.code_builder.get_local(elem_local);
|
||||
backend.code_builder.i32_const(1);
|
||||
backend.code_builder.call(inc_fn, 2, false);
|
||||
}
|
||||
|
@ -2350,7 +2352,13 @@ fn list_map_n<'a>(
|
|||
// If we have lists of different lengths, we may need to decrement
|
||||
let num_wasm_args = if arg_elem_layouts.len() > 1 {
|
||||
for el in arg_elem_layouts.iter() {
|
||||
let idx = backend.get_refcount_fn_index(*el, HelperOp::Dec);
|
||||
// The dec function will be passed a pointer to the element within the list, not the element itself!
|
||||
// Here we wrap the layout in a Struct to ensure we get the right code gen
|
||||
let el_ptr = Layout::Struct {
|
||||
field_order_hash: FieldOrderHash::from_ordered_fields(&[]),
|
||||
field_layouts: backend.env.arena.alloc([*el]),
|
||||
};
|
||||
let idx = backend.get_refcount_fn_index(el_ptr, HelperOp::Dec);
|
||||
let ptr = backend.get_fn_ptr(idx);
|
||||
backend.code_builder.i32_const(ptr);
|
||||
}
|
||||
|
|
|
@ -621,7 +621,7 @@ impl<'a> Storage<'a> {
|
|||
to_symbol: Symbol,
|
||||
from_addr: AddressValue,
|
||||
from_offset: u32,
|
||||
) -> u32 {
|
||||
) {
|
||||
let to_storage = self.get(&to_symbol).to_owned();
|
||||
match to_storage {
|
||||
StoredValue::StackMemory {
|
||||
|
@ -656,7 +656,6 @@ impl<'a> Storage<'a> {
|
|||
alignment_bytes,
|
||||
},
|
||||
);
|
||||
size
|
||||
}
|
||||
|
||||
StoredValue::VirtualMachineStack {
|
||||
|
@ -690,8 +689,6 @@ impl<'a> Storage<'a> {
|
|||
if let StoredValue::Local { local_id, .. } = to_storage {
|
||||
code_builder.set_local(local_id);
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_ident"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_late_solve"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_load"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
pub use roc_load_internal::file::Threading;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use roc_can::module::ExposedByModule;
|
||||
use roc_collections::all::MutMap;
|
||||
|
@ -11,7 +9,8 @@ use std::path::PathBuf;
|
|||
|
||||
pub use roc_load_internal::docs;
|
||||
pub use roc_load_internal::file::{
|
||||
Expectations, LoadResult, LoadStart, LoadedModule, LoadingProblem, MonomorphizedModule, Phase,
|
||||
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadResult, LoadStart, LoadedModule,
|
||||
LoadingProblem, MonomorphizedModule, Phase, Threading,
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
@ -19,23 +18,11 @@ fn load<'a>(
|
|||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let cached_subs = read_cached_subs();
|
||||
|
||||
roc_load_internal::file::load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
goal_phase,
|
||||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
threading,
|
||||
)
|
||||
roc_load_internal::file::load(arena, load_start, exposed_types, cached_subs, load_config)
|
||||
}
|
||||
|
||||
/// Load using only a single thread; used when compiling to webassembly
|
||||
|
@ -43,9 +30,9 @@ pub fn load_single_threaded<'a>(
|
|||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
exec_mode: ExecutionMode,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let cached_subs = read_cached_subs();
|
||||
|
||||
|
@ -53,10 +40,10 @@ pub fn load_single_threaded<'a>(
|
|||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
goal_phase,
|
||||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
exec_mode,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -67,23 +54,13 @@ pub fn load_and_monomorphize_from_str<'a>(
|
|||
src: &'a str,
|
||||
src_dir: PathBuf,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_str(arena, filename, src, src_dir)?;
|
||||
|
||||
match load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::MakeSpecializations,
|
||||
target_info,
|
||||
render,
|
||||
threading,
|
||||
)? {
|
||||
match load(arena, load_start, exposed_types, load_config)? {
|
||||
Monomorphized(module) => Ok(module),
|
||||
TypeChecked(_) => unreachable!(""),
|
||||
}
|
||||
|
@ -93,23 +70,13 @@ pub fn load_and_monomorphize(
|
|||
arena: &Bump,
|
||||
filename: PathBuf,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<MonomorphizedModule<'_>, LoadingProblem<'_>> {
|
||||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_path(arena, filename, render)?;
|
||||
let load_start = LoadStart::from_path(arena, filename, load_config.render)?;
|
||||
|
||||
match load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::MakeSpecializations,
|
||||
target_info,
|
||||
render,
|
||||
threading,
|
||||
)? {
|
||||
match load(arena, load_start, exposed_types, load_config)? {
|
||||
Monomorphized(module) => Ok(module),
|
||||
TypeChecked(_) => unreachable!(""),
|
||||
}
|
||||
|
@ -119,23 +86,13 @@ pub fn load_and_typecheck(
|
|||
arena: &Bump,
|
||||
filename: PathBuf,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<LoadedModule, LoadingProblem<'_>> {
|
||||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_path(arena, filename, render)?;
|
||||
let load_start = LoadStart::from_path(arena, filename, load_config.render)?;
|
||||
|
||||
match load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::SolveTypes,
|
||||
target_info,
|
||||
render,
|
||||
threading,
|
||||
)? {
|
||||
match load(arena, load_start, exposed_types, load_config)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
}
|
||||
|
@ -161,9 +118,9 @@ pub fn load_and_typecheck_str<'a>(
|
|||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::SolveTypes,
|
||||
target_info,
|
||||
render,
|
||||
ExecutionMode::Check,
|
||||
)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_load_internal"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -30,8 +30,8 @@ use roc_module::symbol::{
|
|||
PackageQualified, Symbol,
|
||||
};
|
||||
use roc_mono::ir::{
|
||||
CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs,
|
||||
ProcsBase, UpdateModeIds,
|
||||
CapturedSymbols, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, ProcsBase,
|
||||
UpdateModeIds,
|
||||
};
|
||||
use roc_mono::layout::{CapturesNiche, LambdaName, Layout, LayoutCache, LayoutProblem};
|
||||
use roc_parse::ast::{self, Defs, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation};
|
||||
|
@ -117,6 +117,30 @@ macro_rules! log {
|
|||
($($arg:tt)*) => (dbg_do!(ROC_PRINT_LOAD_LOG, println!($($arg)*)))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoadConfig {
|
||||
pub target_info: TargetInfo,
|
||||
pub render: RenderTarget,
|
||||
pub threading: Threading,
|
||||
pub exec_mode: ExecutionMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ExecutionMode {
|
||||
Test,
|
||||
Check,
|
||||
Executable,
|
||||
}
|
||||
|
||||
impl ExecutionMode {
|
||||
fn goal_phase(&self) -> Phase {
|
||||
match self {
|
||||
ExecutionMode::Test | ExecutionMode::Executable => Phase::MakeSpecializations,
|
||||
ExecutionMode::Check => Phase::SolveTypes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct storing various intermediate stages by their ModuleId
|
||||
#[derive(Debug)]
|
||||
struct ModuleCache<'a> {
|
||||
|
@ -421,6 +445,7 @@ fn start_phase<'a>(
|
|||
|
||||
BuildTask::BuildPendingSpecializations {
|
||||
layout_cache,
|
||||
execution_mode: state.exec_mode,
|
||||
module_id,
|
||||
module_timing,
|
||||
solved_subs,
|
||||
|
@ -670,7 +695,6 @@ pub struct MonomorphizedModule<'a> {
|
|||
pub interns: Interns,
|
||||
pub subs: Subs,
|
||||
pub output_path: Box<Path>,
|
||||
pub platform_path: Box<Path>,
|
||||
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
|
||||
pub type_problems: MutMap<ModuleId, Vec<TypeError>>,
|
||||
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
|
@ -682,6 +706,16 @@ pub struct MonomorphizedModule<'a> {
|
|||
pub expectations: VecMap<ModuleId, Expectations>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EntryPoint<'a> {
|
||||
Executable {
|
||||
symbol: Symbol,
|
||||
layout: ProcLayout<'a>,
|
||||
platform_path: Box<Path>,
|
||||
},
|
||||
Test,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Expectations {
|
||||
pub subs: roc_types::subs::Subs,
|
||||
|
@ -848,7 +882,6 @@ struct State<'a> {
|
|||
pub root_id: ModuleId,
|
||||
pub root_subs: Option<Subs>,
|
||||
pub platform_data: Option<PlatformData>,
|
||||
pub goal_phase: Phase,
|
||||
pub exposed_types: ExposedByModule,
|
||||
pub output_path: Option<&'a str>,
|
||||
pub platform_path: PlatformPath<'a>,
|
||||
|
@ -859,6 +892,7 @@ struct State<'a> {
|
|||
pub procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
pub toplevel_expects: VecMap<Symbol, Region>,
|
||||
pub exposed_to_host: ExposedToHost,
|
||||
pub goal_phase: Phase,
|
||||
|
||||
/// This is the "final" list of IdentIds, after canonicalization and constraint gen
|
||||
/// have completed for a given module.
|
||||
|
@ -886,6 +920,7 @@ struct State<'a> {
|
|||
pub layout_caches: std::vec::Vec<LayoutCache<'a>>,
|
||||
|
||||
pub render: RenderTarget,
|
||||
pub exec_mode: ExecutionMode,
|
||||
|
||||
/// All abilities across all modules.
|
||||
pub world_abilities: WorldAbilities,
|
||||
|
@ -903,16 +938,17 @@ impl<'a> State<'a> {
|
|||
fn new(
|
||||
root_id: ModuleId,
|
||||
target_info: TargetInfo,
|
||||
goal_phase: Phase,
|
||||
exposed_types: ExposedByModule,
|
||||
arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: SharedIdentIdsByModule,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
number_of_workers: usize,
|
||||
exec_mode: ExecutionMode,
|
||||
) -> Self {
|
||||
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
|
||||
|
||||
let goal_phase = exec_mode.goal_phase();
|
||||
let dependencies = Dependencies::new(goal_phase);
|
||||
|
||||
Self {
|
||||
|
@ -940,6 +976,7 @@ impl<'a> State<'a> {
|
|||
layout_caches: std::vec::Vec::with_capacity(number_of_workers),
|
||||
cached_subs: Arc::new(Mutex::new(cached_subs)),
|
||||
render,
|
||||
exec_mode,
|
||||
make_specializations_pass: MakeSpecializationsPass::Pass(1),
|
||||
world_abilities: Default::default(),
|
||||
}
|
||||
|
@ -1054,6 +1091,7 @@ enum BuildTask<'a> {
|
|||
},
|
||||
BuildPendingSpecializations {
|
||||
module_timing: ModuleTiming,
|
||||
execution_mode: ExecutionMode,
|
||||
layout_cache: LayoutCache<'a>,
|
||||
solved_subs: Solved<Subs>,
|
||||
imported_module_thunks: &'a [Symbol],
|
||||
|
@ -1146,16 +1184,14 @@ pub fn load_and_typecheck_str<'a>(
|
|||
// where we want to regenerate the cached data
|
||||
let cached_subs = MutMap::default();
|
||||
|
||||
match load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::SolveTypes,
|
||||
let load_config = LoadConfig {
|
||||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
threading,
|
||||
)? {
|
||||
exec_mode: ExecutionMode::Check,
|
||||
};
|
||||
|
||||
match load(arena, load_start, exposed_types, cached_subs, load_config)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
}
|
||||
|
@ -1364,11 +1400,8 @@ pub fn load<'a>(
|
|||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
enum Threads {
|
||||
Single,
|
||||
|
@ -1385,7 +1418,7 @@ pub fn load<'a>(
|
|||
Err(_) => Threads::Single,
|
||||
Ok(0) => unreachable!("NonZeroUsize"),
|
||||
Ok(1) => Threads::Single,
|
||||
Ok(reported) => match threading {
|
||||
Ok(reported) => match load_config.threading {
|
||||
Threading::Single => Threads::Single,
|
||||
Threading::AllAvailable => Threads::Many(reported),
|
||||
Threading::AtMost(at_most) => Threads::Many(Ord::min(reported, at_most)),
|
||||
|
@ -1399,20 +1432,20 @@ pub fn load<'a>(
|
|||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
goal_phase,
|
||||
target_info,
|
||||
load_config.target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
load_config.render,
|
||||
load_config.exec_mode,
|
||||
),
|
||||
Threads::Many(threads) => load_multi_threaded(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
goal_phase,
|
||||
target_info,
|
||||
load_config.target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
load_config.render,
|
||||
threads,
|
||||
load_config.exec_mode,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -1423,10 +1456,10 @@ pub fn load_single_threaded<'a>(
|
|||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
exec_mode: ExecutionMode,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let LoadStart {
|
||||
arc_modules,
|
||||
|
@ -1447,13 +1480,13 @@ pub fn load_single_threaded<'a>(
|
|||
let mut state = State::new(
|
||||
root_id,
|
||||
target_info,
|
||||
goal_phase,
|
||||
exposed_types,
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
render,
|
||||
number_of_workers,
|
||||
exec_mode,
|
||||
);
|
||||
|
||||
// We'll add tasks to this, and then worker threads will take tasks from it.
|
||||
|
@ -1624,11 +1657,11 @@ fn load_multi_threaded<'a>(
|
|||
arena: &'a Bump,
|
||||
load_start: LoadStart<'a>,
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
available_threads: usize,
|
||||
exec_mode: ExecutionMode,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let LoadStart {
|
||||
arc_modules,
|
||||
|
@ -1664,13 +1697,13 @@ fn load_multi_threaded<'a>(
|
|||
let mut state = State::new(
|
||||
root_id,
|
||||
target_info,
|
||||
goal_phase,
|
||||
exposed_types,
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
render,
|
||||
num_workers,
|
||||
exec_mode,
|
||||
);
|
||||
|
||||
// an arena for every worker, stored in an arena-allocated bumpalo vec to make the lifetimes work
|
||||
|
@ -2749,6 +2782,7 @@ fn finish_specialization(
|
|||
output_path,
|
||||
platform_path,
|
||||
platform_data,
|
||||
exec_mode,
|
||||
..
|
||||
} = state;
|
||||
|
||||
|
@ -2765,53 +2799,60 @@ fn finish_specialization(
|
|||
.map(|(id, (path, src))| (id, (path, src.into())))
|
||||
.collect();
|
||||
|
||||
let path_to_platform = {
|
||||
use PlatformPath::*;
|
||||
let package_name = match platform_path {
|
||||
Valid(To::ExistingPackage(shorthand)) => {
|
||||
match (*state.arc_shorthands).lock().get(shorthand) {
|
||||
Some(p_or_p) => *p_or_p,
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
Valid(To::NewPackage(p_or_p)) => p_or_p,
|
||||
other => {
|
||||
let buf = to_missing_platform_report(state.root_id, other);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
};
|
||||
|
||||
package_name.into()
|
||||
};
|
||||
|
||||
let platform_path = Path::new(path_to_platform).into();
|
||||
|
||||
let entry_point = {
|
||||
let symbol = match platform_data {
|
||||
None => {
|
||||
debug_assert_eq!(exposed_to_host.values.len(), 1);
|
||||
*exposed_to_host.values.iter().next().unwrap().0
|
||||
}
|
||||
Some(PlatformData { provides, .. }) => provides,
|
||||
};
|
||||
match exec_mode {
|
||||
ExecutionMode::Test => EntryPoint::Test,
|
||||
ExecutionMode::Executable => {
|
||||
let path_to_platform = {
|
||||
use PlatformPath::*;
|
||||
let package_name = match platform_path {
|
||||
Valid(To::ExistingPackage(shorthand)) => {
|
||||
match (*state.arc_shorthands).lock().get(shorthand) {
|
||||
Some(p_or_p) => *p_or_p,
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
Valid(To::NewPackage(p_or_p)) => p_or_p,
|
||||
other => {
|
||||
let buf = to_missing_platform_report(state.root_id, other);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
};
|
||||
|
||||
match procedures.keys().find(|(s, _)| *s == symbol) {
|
||||
Some((_, layout)) => EntryPoint {
|
||||
layout: *layout,
|
||||
symbol,
|
||||
},
|
||||
None => {
|
||||
// the entry point is not specialized. This can happen if the repl output
|
||||
// is a function value
|
||||
EntryPoint {
|
||||
layout: roc_mono::ir::ProcLayout {
|
||||
arguments: &[],
|
||||
result: Layout::struct_no_name_order(&[]),
|
||||
captures_niche: CapturesNiche::no_niche(),
|
||||
package_name.into()
|
||||
};
|
||||
|
||||
let platform_path = Path::new(path_to_platform).into();
|
||||
let symbol = match platform_data {
|
||||
None => {
|
||||
debug_assert_eq!(exposed_to_host.values.len(), 1);
|
||||
*exposed_to_host.values.iter().next().unwrap().0
|
||||
}
|
||||
Some(PlatformData { provides, .. }) => provides,
|
||||
};
|
||||
|
||||
match procedures.keys().find(|(s, _)| *s == symbol) {
|
||||
Some((_, layout)) => EntryPoint::Executable {
|
||||
layout: *layout,
|
||||
symbol,
|
||||
platform_path,
|
||||
},
|
||||
symbol,
|
||||
None => {
|
||||
// the entry point is not specialized. This can happen if the repl output
|
||||
// is a function value
|
||||
EntryPoint::Executable {
|
||||
layout: roc_mono::ir::ProcLayout {
|
||||
arguments: &[],
|
||||
result: Layout::struct_no_name_order(&[]),
|
||||
captures_niche: CapturesNiche::no_niche(),
|
||||
},
|
||||
symbol,
|
||||
platform_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ExecutionMode::Check => unreachable!(),
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2824,7 +2865,7 @@ fn finish_specialization(
|
|||
can_problems,
|
||||
type_problems,
|
||||
output_path,
|
||||
platform_path,
|
||||
expectations,
|
||||
exposed_to_host,
|
||||
module_id: state.root_id,
|
||||
subs,
|
||||
|
@ -2834,7 +2875,6 @@ fn finish_specialization(
|
|||
sources,
|
||||
timings: state.timings,
|
||||
toplevel_expects,
|
||||
expectations,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -4479,7 +4519,7 @@ fn canonicalize_and_constrain<'a>(
|
|||
Vacant(vacant) => {
|
||||
let should_include_builtin = matches!(
|
||||
name.module_id(),
|
||||
ModuleId::ENCODE | ModuleId::DICT | ModuleId::SET
|
||||
ModuleId::ENCODE | ModuleId::DECODE | ModuleId::DICT | ModuleId::SET
|
||||
);
|
||||
|
||||
if !name.is_builtin() || should_include_builtin {
|
||||
|
@ -4698,6 +4738,7 @@ fn make_specializations<'a>(
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_pending_specializations<'a>(
|
||||
arena: &'a Bump,
|
||||
execution_mode: ExecutionMode,
|
||||
solved_subs: Solved<Subs>,
|
||||
imported_module_thunks: &'a [Symbol],
|
||||
home: ModuleId,
|
||||
|
@ -4955,6 +4996,12 @@ fn build_pending_specializations<'a>(
|
|||
// the declarations of this group will be treaded individually by later iterations
|
||||
}
|
||||
Expectation => {
|
||||
// skip expectations if we're not going to run them
|
||||
match execution_mode {
|
||||
ExecutionMode::Test => { /* fall through */ }
|
||||
ExecutionMode::Check | ExecutionMode::Executable => continue,
|
||||
}
|
||||
|
||||
// mark this symbol as a top-level thunk before any other work on the procs
|
||||
module_thunks.push(symbol);
|
||||
|
||||
|
@ -5078,7 +5125,7 @@ fn load_derived_partial_procs<'a>(
|
|||
|
||||
// TODO: we can be even lazier here if we move `add_def_to_module` to happen in mono. Also, the
|
||||
// timings would be more accurate.
|
||||
for (derived_symbol, derived_expr) in derives_to_add.into_iter() {
|
||||
for (derived_symbol, (derived_expr, derived_expr_var)) in derives_to_add.into_iter() {
|
||||
let mut mono_env = roc_mono::ir::Env {
|
||||
arena,
|
||||
subs,
|
||||
|
@ -5117,7 +5164,22 @@ fn load_derived_partial_procs<'a>(
|
|||
return_type,
|
||||
)
|
||||
}
|
||||
_ => internal_error!("Expected only functions to be derived"),
|
||||
_ => {
|
||||
// mark this symbols as a top-level thunk before any other work on the procs
|
||||
new_module_thunks.push(derived_symbol);
|
||||
|
||||
PartialProc {
|
||||
annotation: derived_expr_var,
|
||||
// This is a 0-arity thunk, so it has no arguments.
|
||||
pattern_symbols: &[],
|
||||
// This is a top-level definition, so it cannot capture anything
|
||||
captured_symbols: CapturedSymbols::None,
|
||||
body: derived_expr,
|
||||
body_var: derived_expr_var,
|
||||
// This is a 0-arity thunk, so it cannot be recursive
|
||||
is_self_recursive: false,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
procs_base
|
||||
|
@ -5212,6 +5274,7 @@ fn run_task<'a>(
|
|||
)),
|
||||
BuildPendingSpecializations {
|
||||
module_id,
|
||||
execution_mode,
|
||||
ident_ids,
|
||||
decls,
|
||||
module_timing,
|
||||
|
@ -5224,6 +5287,7 @@ fn run_task<'a>(
|
|||
derived_module,
|
||||
} => Ok(build_pending_specializations(
|
||||
arena,
|
||||
execution_mode,
|
||||
solved_subs,
|
||||
imported_module_thunks,
|
||||
module_id,
|
||||
|
|
|
@ -17,8 +17,8 @@ mod helpers;
|
|||
use crate::helpers::fixtures_dir;
|
||||
use bumpalo::Bump;
|
||||
use roc_can::module::ExposedByModule;
|
||||
use roc_load_internal::file::Threading;
|
||||
use roc_load_internal::file::{LoadResult, LoadStart, LoadedModule, LoadingProblem, Phase};
|
||||
use roc_load_internal::file::{ExecutionMode, LoadConfig, Threading};
|
||||
use roc_load_internal::file::{LoadResult, LoadStart, LoadedModule, LoadingProblem};
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_problem::can::Problem;
|
||||
|
@ -41,16 +41,19 @@ fn load_and_typecheck(
|
|||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_path(arena, filename, RenderTarget::Generic)?;
|
||||
let load_config = LoadConfig {
|
||||
target_info,
|
||||
render: RenderTarget::Generic,
|
||||
threading: Threading::Single,
|
||||
exec_mode: ExecutionMode::Check,
|
||||
};
|
||||
|
||||
match roc_load_internal::file::load(
|
||||
arena,
|
||||
load_start,
|
||||
exposed_types,
|
||||
Phase::SolveTypes,
|
||||
target_info,
|
||||
Default::default(), // these tests will re-compile the builtins
|
||||
RenderTarget::Generic,
|
||||
Threading::Single,
|
||||
load_config,
|
||||
)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_module"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
edition = "2021"
|
||||
license = "UPL-1.0"
|
||||
|
|
|
@ -47,8 +47,10 @@ const SYMBOL_HAS_NICHE: () =
|
|||
#[cfg(debug_assertions)]
|
||||
const PRETTY_PRINT_DEBUG_SYMBOLS: bool = true;
|
||||
|
||||
pub const DERIVABLE_ABILITIES: &[(Symbol, &[Symbol])] =
|
||||
&[(Symbol::ENCODE_ENCODING, &[Symbol::ENCODE_TO_ENCODER])];
|
||||
pub const DERIVABLE_ABILITIES: &[(Symbol, &[Symbol])] = &[
|
||||
(Symbol::ENCODE_ENCODING, &[Symbol::ENCODE_TO_ENCODER]),
|
||||
(Symbol::DECODE_DECODING, &[Symbol::DECODE_DECODER]),
|
||||
];
|
||||
|
||||
/// In Debug builds only, Symbol has a name() method that lets
|
||||
/// you look up its name in a global intern table. This table is
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_mono"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
@ -12,6 +12,7 @@ roc_region = { path = "../region" }
|
|||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_derive_key = { path = "../derive_key" }
|
||||
roc_derive = { path = "../derive" }
|
||||
roc_late_solve = { path = "../late_solve" }
|
||||
roc_std = { path = "../../roc_std", default-features = false }
|
||||
|
|
|
@ -144,6 +144,7 @@ pub fn refcount_reset_proc_body<'a>(
|
|||
let rc = root.create_symbol(ident_ids, "rc");
|
||||
let refcount_1 = root.create_symbol(ident_ids, "refcount_1");
|
||||
let is_unique = root.create_symbol(ident_ids, "is_unique");
|
||||
let addr = root.create_symbol(ident_ids, "addr");
|
||||
|
||||
let union_layout = match layout {
|
||||
Layout::Union(u) => u,
|
||||
|
@ -201,6 +202,39 @@ pub fn refcount_reset_proc_body<'a>(
|
|||
)
|
||||
};
|
||||
|
||||
let alloc_addr_stmt = {
|
||||
let alignment = root.create_symbol(ident_ids, "alignment");
|
||||
let alignment_expr = Expr::Literal(Literal::Int(
|
||||
(layout.alignment_bytes(root.target_info) as i128).to_ne_bytes(),
|
||||
));
|
||||
let alloc_addr = root.create_symbol(ident_ids, "alloc_addr");
|
||||
let alloc_addr_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::NumSubWrap,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: root.arena.alloc([addr, alignment]),
|
||||
});
|
||||
|
||||
Stmt::Let(
|
||||
alignment,
|
||||
alignment_expr,
|
||||
root.layout_isize,
|
||||
root.arena.alloc(
|
||||
//
|
||||
Stmt::Let(
|
||||
alloc_addr,
|
||||
alloc_addr_expr,
|
||||
root.layout_isize,
|
||||
root.arena.alloc(
|
||||
//
|
||||
Stmt::Ret(alloc_addr),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
let rc_contents_stmt = refcount_union_contents(
|
||||
root,
|
||||
ident_ids,
|
||||
|
@ -211,7 +245,7 @@ pub fn refcount_reset_proc_body<'a>(
|
|||
structure,
|
||||
tag_id_sym,
|
||||
tag_id_layout,
|
||||
Stmt::Ret(structure),
|
||||
alloc_addr_stmt,
|
||||
);
|
||||
|
||||
tag_id_stmt(root.arena.alloc(
|
||||
|
@ -300,13 +334,14 @@ pub fn refcount_reset_proc_body<'a>(
|
|||
|
||||
// Refcount pointer
|
||||
let rc_ptr_stmt = {
|
||||
rc_ptr_from_data_ptr(
|
||||
rc_ptr_from_data_ptr_help(
|
||||
root,
|
||||
ident_ids,
|
||||
structure,
|
||||
rc_ptr,
|
||||
union_layout.stores_tag_id_in_pointer(root.target_info),
|
||||
root.arena.alloc(rc_stmt),
|
||||
addr,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -380,20 +415,45 @@ pub fn rc_ptr_from_data_ptr<'a>(
|
|||
rc_ptr_sym: Symbol,
|
||||
mask_lower_bits: bool,
|
||||
following: &'a Stmt<'a>,
|
||||
) -> Stmt<'a> {
|
||||
let addr_sym = root.create_symbol(ident_ids, "addr");
|
||||
rc_ptr_from_data_ptr_help(
|
||||
root,
|
||||
ident_ids,
|
||||
structure,
|
||||
rc_ptr_sym,
|
||||
mask_lower_bits,
|
||||
following,
|
||||
addr_sym,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn rc_ptr_from_data_ptr_help<'a>(
|
||||
root: &CodeGenHelp<'a>,
|
||||
ident_ids: &mut IdentIds,
|
||||
structure: Symbol,
|
||||
rc_ptr_sym: Symbol,
|
||||
mask_lower_bits: bool,
|
||||
following: &'a Stmt<'a>,
|
||||
addr_sym: Symbol,
|
||||
) -> Stmt<'a> {
|
||||
use std::ops::Neg;
|
||||
|
||||
// Typecast the structure pointer to an integer
|
||||
// Backends expect a number Layout to choose the right "subtract" instruction
|
||||
let addr_sym = root.create_symbol(ident_ids, "addr");
|
||||
let addr_expr = Expr::Call(Call {
|
||||
let as_int_sym = if mask_lower_bits {
|
||||
root.create_symbol(ident_ids, "as_int")
|
||||
} else {
|
||||
addr_sym
|
||||
};
|
||||
let as_int_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::PtrCast,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: root.arena.alloc([structure]),
|
||||
});
|
||||
let addr_stmt = |next| Stmt::Let(addr_sym, addr_expr, root.layout_isize, next);
|
||||
let as_int_stmt = |next| Stmt::Let(as_int_sym, as_int_expr, root.layout_isize, next);
|
||||
|
||||
// Mask for lower bits (for tag union id)
|
||||
let mask_sym = root.create_symbol(ident_ids, "mask");
|
||||
|
@ -402,15 +462,14 @@ pub fn rc_ptr_from_data_ptr<'a>(
|
|||
));
|
||||
let mask_stmt = |next| Stmt::Let(mask_sym, mask_expr, root.layout_isize, next);
|
||||
|
||||
let masked_sym = root.create_symbol(ident_ids, "masked");
|
||||
let and_expr = Expr::Call(Call {
|
||||
call_type: CallType::LowLevel {
|
||||
op: LowLevel::And,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: root.arena.alloc([addr_sym, mask_sym]),
|
||||
arguments: root.arena.alloc([as_int_sym, mask_sym]),
|
||||
});
|
||||
let and_stmt = |next| Stmt::Let(masked_sym, and_expr, root.layout_isize, next);
|
||||
let and_stmt = |next| Stmt::Let(addr_sym, and_expr, root.layout_isize, next);
|
||||
|
||||
// Pointer size constant
|
||||
let ptr_size_sym = root.create_symbol(ident_ids, "ptr_size");
|
||||
|
@ -426,14 +485,7 @@ pub fn rc_ptr_from_data_ptr<'a>(
|
|||
op: LowLevel::NumSub,
|
||||
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||
},
|
||||
arguments: root.arena.alloc([
|
||||
if mask_lower_bits {
|
||||
masked_sym
|
||||
} else {
|
||||
addr_sym
|
||||
},
|
||||
ptr_size_sym,
|
||||
]),
|
||||
arguments: root.arena.alloc([addr_sym, ptr_size_sym]),
|
||||
});
|
||||
let sub_stmt = |next| Stmt::Let(rc_addr_sym, sub_expr, root.layout_isize, next);
|
||||
|
||||
|
@ -448,7 +500,7 @@ pub fn rc_ptr_from_data_ptr<'a>(
|
|||
let cast_stmt = |next| Stmt::Let(rc_ptr_sym, cast_expr, LAYOUT_PTR, next);
|
||||
|
||||
if mask_lower_bits {
|
||||
addr_stmt(root.arena.alloc(
|
||||
as_int_stmt(root.arena.alloc(
|
||||
//
|
||||
mask_stmt(root.arena.alloc(
|
||||
//
|
||||
|
@ -468,7 +520,7 @@ pub fn rc_ptr_from_data_ptr<'a>(
|
|||
)),
|
||||
))
|
||||
} else {
|
||||
addr_stmt(root.arena.alloc(
|
||||
as_int_stmt(root.arena.alloc(
|
||||
//
|
||||
ptr_size_stmt(root.arena.alloc(
|
||||
//
|
||||
|
|
|
@ -4921,7 +4921,7 @@ pub fn with_hole<'a>(
|
|||
|
||||
let resolved_proc = match resolved_proc {
|
||||
Resolved::Specialization(symbol) => symbol,
|
||||
Resolved::NeedsGenerated => {
|
||||
Resolved::NeedsGenerated(_) => {
|
||||
todo_abilities!("Generate impls for structural types")
|
||||
}
|
||||
};
|
||||
|
@ -5236,8 +5236,30 @@ fn late_resolve_ability_specialization<'a>(
|
|||
|
||||
match specialization {
|
||||
Resolved::Specialization(symbol) => symbol,
|
||||
Resolved::NeedsGenerated => {
|
||||
todo_abilities!("Generate impls for structural types")
|
||||
Resolved::NeedsGenerated(var) => {
|
||||
let derive_key = roc_derive_key::Derived::builtin(
|
||||
member.try_into().expect("derived symbols must be builtins"),
|
||||
env.subs,
|
||||
var,
|
||||
)
|
||||
.expect("specialization var not derivable!");
|
||||
|
||||
match derive_key {
|
||||
roc_derive_key::Derived::Immediate(imm) => {
|
||||
// The immediate is an ability member itself, so it must be resolved!
|
||||
late_resolve_ability_specialization(env, imm, None, specialization_var)
|
||||
}
|
||||
roc_derive_key::Derived::Key(derive_key) => {
|
||||
let mut derived_module = env
|
||||
.derived_module
|
||||
.lock()
|
||||
.expect("derived module unavailable");
|
||||
|
||||
derived_module
|
||||
.get_or_insert(env.exposed_by_module, derive_key)
|
||||
.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -472,10 +472,13 @@ impl<'a> UnionLayout<'a> {
|
|||
tags.len() < target_info.ptr_width() as usize
|
||||
}
|
||||
|
||||
pub const POINTER_MASK_32BIT: usize = 0b0000_0111;
|
||||
pub const POINTER_MASK_64BIT: usize = 0b0000_0011;
|
||||
|
||||
pub fn tag_id_pointer_bits_and_mask(target_info: TargetInfo) -> (usize, usize) {
|
||||
match target_info.ptr_width() {
|
||||
PtrWidth::Bytes8 => (3, 0b0000_0111),
|
||||
PtrWidth::Bytes4 => (2, 0b0000_0011),
|
||||
PtrWidth::Bytes8 => (3, Self::POINTER_MASK_64BIT),
|
||||
PtrWidth::Bytes4 => (2, Self::POINTER_MASK_32BIT),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_parse"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -866,7 +866,7 @@ where
|
|||
// the next character should not be an identifier character
|
||||
// to prevent treating `whence` or `iffy` as keywords
|
||||
match state.bytes().get(width) {
|
||||
Some(next) if *next == b' ' || *next == b'#' || *next == b'\n' => {
|
||||
Some(next) if *next == b' ' || *next == b'#' || *next == b'\n' || *next == b'\r' => {
|
||||
state = state.advance(width);
|
||||
Ok((MadeProgress, (), state))
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_problem"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_region"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_target"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -4,9 +4,31 @@
|
|||
|
||||
use strum_macros::{EnumCount, EnumIter};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum OperatingSystem {
|
||||
Windows,
|
||||
Unix,
|
||||
Wasi,
|
||||
}
|
||||
|
||||
impl From<target_lexicon::OperatingSystem> for OperatingSystem {
|
||||
fn from(target: target_lexicon::OperatingSystem) -> Self {
|
||||
match target {
|
||||
target_lexicon::OperatingSystem::Windows => OperatingSystem::Windows,
|
||||
target_lexicon::OperatingSystem::Wasi => OperatingSystem::Wasi,
|
||||
target_lexicon::OperatingSystem::Linux => OperatingSystem::Unix,
|
||||
target_lexicon::OperatingSystem::MacOSX { .. } => OperatingSystem::Unix,
|
||||
target_lexicon::OperatingSystem::Darwin => OperatingSystem::Unix,
|
||||
target_lexicon::OperatingSystem::Unknown => OperatingSystem::Unix,
|
||||
other => unreachable!("unsupported operating system {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct TargetInfo {
|
||||
pub architecture: Architecture,
|
||||
pub operating_system: OperatingSystem,
|
||||
}
|
||||
|
||||
impl TargetInfo {
|
||||
|
@ -28,18 +50,21 @@ impl TargetInfo {
|
|||
pub const fn default_aarch64() -> Self {
|
||||
TargetInfo {
|
||||
architecture: Architecture::Aarch64,
|
||||
operating_system: OperatingSystem::Unix,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn default_x86_64() -> Self {
|
||||
TargetInfo {
|
||||
architecture: Architecture::X86_64,
|
||||
operating_system: OperatingSystem::Unix,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn default_wasm32() -> Self {
|
||||
TargetInfo {
|
||||
architecture: Architecture::Wasm32,
|
||||
operating_system: OperatingSystem::Wasi,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,14 +72,12 @@ impl TargetInfo {
|
|||
impl From<&target_lexicon::Triple> for TargetInfo {
|
||||
fn from(triple: &target_lexicon::Triple) -> Self {
|
||||
let architecture = Architecture::from(triple.architecture);
|
||||
let operating_system = OperatingSystem::from(triple.operating_system);
|
||||
|
||||
Self { architecture }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Architecture> for TargetInfo {
|
||||
fn from(architecture: Architecture) -> Self {
|
||||
Self { architecture }
|
||||
Self {
|
||||
architecture,
|
||||
operating_system,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_solve"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::expr::PendingDerives;
|
||||
use roc_collections::{VecMap, VecSet};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_error_macros::{internal_error, todo_abilities};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_solve_problem::{TypeError, UnderivableReason, Unfulfilled};
|
||||
use roc_types::num::NumericRange;
|
||||
use roc_types::subs::{instantiate_rigids, Content, FlatType, GetSubsSlice, Rank, Subs, Variable};
|
||||
use roc_types::types::{AliasKind, Category, MemberImpl, PatternCategory};
|
||||
use roc_unify::unify::{Env, MustImplementConstraints};
|
||||
|
@ -253,7 +254,20 @@ impl ObligationCache {
|
|||
// independent queries.
|
||||
|
||||
let opt_can_derive_builtin = match ability {
|
||||
Symbol::ENCODE_ENCODING => Some(self.can_derive_encoding(subs, abilities_store, var)),
|
||||
Symbol::ENCODE_ENCODING => Some(DeriveEncoding::is_derivable(
|
||||
self,
|
||||
abilities_store,
|
||||
subs,
|
||||
var,
|
||||
)),
|
||||
|
||||
Symbol::DECODE_DECODING => Some(DeriveDecoding::is_derivable(
|
||||
self,
|
||||
abilities_store,
|
||||
subs,
|
||||
var,
|
||||
)),
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
@ -262,7 +276,7 @@ impl ObligationCache {
|
|||
// can derive!
|
||||
None
|
||||
}
|
||||
Some(Err(failure_var)) => Some(if failure_var == var {
|
||||
Some(Err(DerivableError::NotDerivable(failure_var))) => Some(if failure_var == var {
|
||||
UnderivableReason::SurfaceNotDerivable
|
||||
} else {
|
||||
let (error_type, _skeletons) = subs.var_to_error_type(failure_var);
|
||||
|
@ -391,16 +405,133 @@ impl ObligationCache {
|
|||
let check_has_fake = self.derive_cache.insert(derive_key, root_result);
|
||||
debug_assert_eq!(check_has_fake, Some(fake_fulfilled));
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a lot of these, consider using a visitor.
|
||||
// It will be very similar for most types (can't derive functions, can't derive unbound type
|
||||
// variables, can only derive opaques if they have an impl, etc).
|
||||
fn can_derive_encoding(
|
||||
&mut self,
|
||||
subs: &mut Subs,
|
||||
#[inline(always)]
|
||||
#[rustfmt::skip]
|
||||
fn is_builtin_number_alias(symbol: Symbol) -> bool {
|
||||
matches!(symbol,
|
||||
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8
|
||||
| Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16
|
||||
| Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32
|
||||
| Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64
|
||||
| Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128
|
||||
| Symbol::NUM_I8 | Symbol::NUM_SIGNED8
|
||||
| Symbol::NUM_I16 | Symbol::NUM_SIGNED16
|
||||
| Symbol::NUM_I32 | Symbol::NUM_SIGNED32
|
||||
| Symbol::NUM_I64 | Symbol::NUM_SIGNED64
|
||||
| Symbol::NUM_I128 | Symbol::NUM_SIGNED128
|
||||
| Symbol::NUM_NAT | Symbol::NUM_NATURAL
|
||||
| Symbol::NUM_F32 | Symbol::NUM_BINARY32
|
||||
| Symbol::NUM_F64 | Symbol::NUM_BINARY64
|
||||
| Symbol::NUM_DEC | Symbol::NUM_DECIMAL,
|
||||
)
|
||||
}
|
||||
|
||||
enum DerivableError {
|
||||
NotDerivable(Variable),
|
||||
}
|
||||
|
||||
struct Descend(bool);
|
||||
|
||||
trait DerivableVisitor {
|
||||
const ABILITY: Symbol;
|
||||
|
||||
#[inline(always)]
|
||||
fn is_derivable_builtin_opaque(_symbol: Symbol) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_flex(var: Variable) -> Result<(), DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_rigid(var: Variable) -> Result<(), DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_flex_able(var: Variable, ability: Symbol) -> Result<(), DerivableError> {
|
||||
if ability != Self::ABILITY {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_rigid_able(var: Variable, ability: Symbol) -> Result<(), DerivableError> {
|
||||
if ability != Self::ABILITY {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursion(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_apply(var: Variable, _symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_func(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_record(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_tag_union(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursive_tag_union(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_function_or_tag_union(var: Variable) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_record(var: Variable) -> Result<(), DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_tag_union(var: Variable) -> Result<(), DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_alias(var: Variable, _symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_ranged_number(var: Variable, _range: NumericRange) -> Result<(), DerivableError> {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_derivable(
|
||||
obligation_cache: &mut ObligationCache,
|
||||
abilities_store: &AbilitiesStore,
|
||||
subs: &Subs,
|
||||
var: Variable,
|
||||
) -> Result<(), Variable> {
|
||||
) -> Result<(), DerivableError> {
|
||||
let mut stack = vec![var];
|
||||
let mut seen_recursion_vars = vec![];
|
||||
|
||||
|
@ -418,102 +549,103 @@ impl ObligationCache {
|
|||
let content = subs.get_content_without_compacting(var);
|
||||
|
||||
use Content::*;
|
||||
use DerivableError::*;
|
||||
use FlatType::*;
|
||||
match content {
|
||||
FlexVar(_) | RigidVar(_) => return Err(var),
|
||||
FlexAbleVar(_, ability) | RigidAbleVar(_, ability) => {
|
||||
if *ability != Symbol::ENCODE_ENCODING {
|
||||
return Err(var);
|
||||
}
|
||||
// Any concrete type this variables is instantiated with will also gain a "does
|
||||
// implement" check so this is okay.
|
||||
}
|
||||
match *content {
|
||||
FlexVar(_) => Self::visit_flex(var)?,
|
||||
RigidVar(_) => Self::visit_rigid(var)?,
|
||||
FlexAbleVar(_, ability) => Self::visit_flex_able(var, ability)?,
|
||||
RigidAbleVar(_, ability) => Self::visit_rigid_able(var, ability)?,
|
||||
RecursionVar {
|
||||
structure,
|
||||
opt_name: _,
|
||||
} => {
|
||||
seen_recursion_vars.push(var);
|
||||
stack.push(*structure);
|
||||
let descend = Self::visit_recursion(var)?;
|
||||
if descend.0 {
|
||||
seen_recursion_vars.push(var);
|
||||
stack.push(structure);
|
||||
}
|
||||
}
|
||||
Structure(flat_type) => match flat_type {
|
||||
Apply(
|
||||
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
|
||||
vars,
|
||||
) => push_var_slice!(*vars),
|
||||
Apply(..) => return Err(var),
|
||||
Func(..) => {
|
||||
return Err(var);
|
||||
}
|
||||
Record(fields, var) => {
|
||||
push_var_slice!(fields.variables());
|
||||
stack.push(*var);
|
||||
}
|
||||
TagUnion(tags, ext_var) => {
|
||||
for i in tags.variables() {
|
||||
push_var_slice!(subs[i]);
|
||||
Apply(symbol, vars) => {
|
||||
let descend = Self::visit_apply(var, symbol)?;
|
||||
if descend.0 {
|
||||
push_var_slice!(vars)
|
||||
}
|
||||
stack.push(*ext_var);
|
||||
}
|
||||
FunctionOrTagUnion(_, _, var) => stack.push(*var),
|
||||
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||
seen_recursion_vars.push(*rec_var);
|
||||
for i in tags.variables() {
|
||||
push_var_slice!(subs[i]);
|
||||
Func(args, _clos, ret) => {
|
||||
let descend = Self::visit_func(var)?;
|
||||
if descend.0 {
|
||||
push_var_slice!(args);
|
||||
stack.push(ret);
|
||||
}
|
||||
stack.push(*ext_var);
|
||||
}
|
||||
EmptyRecord | EmptyTagUnion => {
|
||||
// yes
|
||||
Record(fields, ext) => {
|
||||
let descend = Self::visit_record(var)?;
|
||||
if descend.0 {
|
||||
push_var_slice!(fields.variables());
|
||||
stack.push(ext);
|
||||
}
|
||||
}
|
||||
Erroneous(_) => return Err(var),
|
||||
TagUnion(tags, ext) => {
|
||||
let descend = Self::visit_tag_union(var)?;
|
||||
if descend.0 {
|
||||
for i in tags.variables() {
|
||||
push_var_slice!(subs[i]);
|
||||
}
|
||||
stack.push(ext);
|
||||
}
|
||||
}
|
||||
FunctionOrTagUnion(_tag_name, _fn_name, ext) => {
|
||||
let descend = Self::visit_function_or_tag_union(var)?;
|
||||
if descend.0 {
|
||||
stack.push(ext);
|
||||
}
|
||||
}
|
||||
RecursiveTagUnion(rec, tags, ext) => {
|
||||
let descend = Self::visit_recursive_tag_union(var)?;
|
||||
if descend.0 {
|
||||
seen_recursion_vars.push(rec);
|
||||
for i in tags.variables() {
|
||||
push_var_slice!(subs[i]);
|
||||
}
|
||||
stack.push(ext);
|
||||
}
|
||||
}
|
||||
EmptyRecord => Self::visit_empty_record(var)?,
|
||||
EmptyTagUnion => Self::visit_empty_tag_union(var)?,
|
||||
|
||||
Erroneous(_) => return Err(NotDerivable(var)),
|
||||
},
|
||||
#[rustfmt::skip]
|
||||
Alias(
|
||||
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8
|
||||
| Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16
|
||||
| Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32
|
||||
| Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64
|
||||
| Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128
|
||||
| Symbol::NUM_I8 | Symbol::NUM_SIGNED8
|
||||
| Symbol::NUM_I16 | Symbol::NUM_SIGNED16
|
||||
| Symbol::NUM_I32 | Symbol::NUM_SIGNED32
|
||||
| Symbol::NUM_I64 | Symbol::NUM_SIGNED64
|
||||
| Symbol::NUM_I128 | Symbol::NUM_SIGNED128
|
||||
| Symbol::NUM_NAT | Symbol::NUM_NATURAL
|
||||
| Symbol::NUM_F32 | Symbol::NUM_BINARY32
|
||||
| Symbol::NUM_F64 | Symbol::NUM_BINARY64
|
||||
| Symbol::NUM_DEC | Symbol::NUM_DECIMAL,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
) => {
|
||||
// yes
|
||||
}
|
||||
Alias(
|
||||
Symbol::NUM_NUM | Symbol::NUM_INTEGER | Symbol::NUM_FLOATINGPOINT,
|
||||
_,
|
||||
_alias_variables,
|
||||
real_var,
|
||||
_,
|
||||
) => stack.push(*real_var),
|
||||
Alias(name, _, _, AliasKind::Opaque) => {
|
||||
let opaque = *name;
|
||||
if self
|
||||
.check_opaque_and_read(abilities_store, opaque, Symbol::ENCODE_ENCODING)
|
||||
AliasKind::Opaque,
|
||||
) => {
|
||||
// Numbers: always decay until a ground is hit.
|
||||
stack.push(real_var);
|
||||
}
|
||||
Alias(opaque, _alias_variables, _real_var, AliasKind::Opaque) => {
|
||||
if obligation_cache
|
||||
.check_opaque_and_read(abilities_store, opaque, Self::ABILITY)
|
||||
.is_err()
|
||||
&& !Self::is_derivable_builtin_opaque(opaque)
|
||||
{
|
||||
return Err(var);
|
||||
return Err(NotDerivable(var));
|
||||
}
|
||||
}
|
||||
Alias(_, arguments, real_type_var, _) => {
|
||||
push_var_slice!(arguments.all_variables());
|
||||
stack.push(*real_type_var);
|
||||
Alias(symbol, _alias_variables, real_var, AliasKind::Structural) => {
|
||||
let descend = Self::visit_alias(var, symbol)?;
|
||||
if descend.0 {
|
||||
stack.push(real_var);
|
||||
}
|
||||
}
|
||||
RangedNumber(..) => {
|
||||
// yes, all numbers can
|
||||
}
|
||||
LambdaSet(..) => return Err(var),
|
||||
RangedNumber(range) => Self::visit_ranged_number(var, range)?,
|
||||
|
||||
LambdaSet(..) => return Err(NotDerivable(var)),
|
||||
Error => {
|
||||
return Err(var);
|
||||
return Err(NotDerivable(var));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -522,6 +654,148 @@ impl ObligationCache {
|
|||
}
|
||||
}
|
||||
|
||||
struct DeriveEncoding;
|
||||
impl DerivableVisitor for DeriveEncoding {
|
||||
const ABILITY: Symbol = Symbol::ENCODE_ENCODING;
|
||||
|
||||
#[inline(always)]
|
||||
fn is_derivable_builtin_opaque(symbol: Symbol) -> bool {
|
||||
is_builtin_number_alias(symbol)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursion(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
if matches!(
|
||||
symbol,
|
||||
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
|
||||
) {
|
||||
Ok(Descend(true))
|
||||
} else {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_record(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursive_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_function_or_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_record(_var: Variable) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_tag_union(_var: Variable) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_alias(_var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
if is_builtin_number_alias(symbol) {
|
||||
Ok(Descend(false))
|
||||
} else {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct DeriveDecoding;
|
||||
impl DerivableVisitor for DeriveDecoding {
|
||||
const ABILITY: Symbol = Symbol::DECODE_DECODING;
|
||||
|
||||
#[inline(always)]
|
||||
fn is_derivable_builtin_opaque(symbol: Symbol) -> bool {
|
||||
is_builtin_number_alias(symbol)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursion(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_apply(var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
if matches!(
|
||||
symbol,
|
||||
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
|
||||
) {
|
||||
Ok(Descend(true))
|
||||
} else {
|
||||
Err(DerivableError::NotDerivable(var))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_record(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_recursive_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_function_or_tag_union(_var: Variable) -> Result<Descend, DerivableError> {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_record(_var: Variable) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_empty_tag_union(_var: Variable) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_alias(_var: Variable, symbol: Symbol) -> Result<Descend, DerivableError> {
|
||||
if is_builtin_number_alias(symbol) {
|
||||
Ok(Descend(false))
|
||||
} else {
|
||||
Ok(Descend(true))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_ranged_number(_var: Variable, _range: NumericRange) -> Result<(), DerivableError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines what type implements an ability member of a specialized signature, given the
|
||||
/// [MustImplementAbility] constraints of the signature.
|
||||
pub fn type_implementing_specialization(
|
||||
|
@ -551,8 +825,8 @@ pub fn type_implementing_specialization(
|
|||
pub enum Resolved {
|
||||
/// A user-defined specialization should be used.
|
||||
Specialization(Symbol),
|
||||
/// A specialization must be generated.
|
||||
NeedsGenerated,
|
||||
/// A specialization must be generated for the given type variable.
|
||||
NeedsGenerated(Variable),
|
||||
}
|
||||
|
||||
/// An [`AbilityResolver`] is a shell of an abilities store that answers questions needed for
|
||||
|
@ -636,15 +910,17 @@ pub fn resolve_ability_specialization<R: AbilityResolver>(
|
|||
roc_types::types::MemberImpl::Impl(spec_symbol) => {
|
||||
Resolved::Specialization(spec_symbol)
|
||||
}
|
||||
roc_types::types::MemberImpl::Derived => Resolved::NeedsGenerated,
|
||||
roc_types::types::MemberImpl::Derived => {
|
||||
todo_abilities!("get type from obligated opaque")
|
||||
}
|
||||
// TODO this is not correct. We can replace `Resolved` with `MemberImpl` entirely,
|
||||
// which will make this simpler.
|
||||
roc_types::types::MemberImpl::Error => Resolved::Specialization(Symbol::UNDERSCORE),
|
||||
}
|
||||
}
|
||||
Obligated::Adhoc(_) => {
|
||||
Obligated::Adhoc(variable) => {
|
||||
// TODO: more rules need to be validated here, like is this a builtin ability?
|
||||
Resolved::NeedsGenerated
|
||||
Resolved::NeedsGenerated(variable)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -655,13 +655,16 @@ fn make_specialization_decision<P: Phase>(
|
|||
}
|
||||
}
|
||||
Structure(_) | Alias(_, _, _, _) => {
|
||||
// This is a structural type, find the name of the derived ability function it
|
||||
// should use.
|
||||
match roc_derive_key::Derived::encoding(subs, var) {
|
||||
let builtin = match ability_member.try_into() {
|
||||
Ok(builtin) => builtin,
|
||||
Err(_) => return SpecializeDecision::Drop,
|
||||
};
|
||||
|
||||
// This is a structural type, find the derived ability function it should use.
|
||||
match roc_derive_key::Derived::builtin(builtin, subs, var) {
|
||||
Ok(derived) => match derived {
|
||||
roc_derive_key::Derived::Immediate(imm) => {
|
||||
SpecializeDecision::Specialize(Immediate(imm))
|
||||
// todo!("deal with lambda set extraction from immediates")
|
||||
}
|
||||
roc_derive_key::Derived::Key(derive_key) => {
|
||||
SpecializeDecision::Specialize(Derived(derive_key))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_solve_problem"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_str"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "test_derive"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
51
crates/compiler/test_derive/src/decoding.rs
Normal file
51
crates/compiler/test_derive/src/decoding.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
#![cfg(test)]
|
||||
// Even with #[allow(non_snake_case)] on individual idents, rust-analyzer issues diagnostics.
|
||||
// See https://github.com/rust-lang/rust-analyzer/issues/6541.
|
||||
// For the `v!` macro we use uppercase variables when constructing tag unions.
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::{
|
||||
util::{check_immediate, derive_test},
|
||||
v,
|
||||
};
|
||||
use insta::assert_snapshot;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
use roc_derive_key::DeriveBuiltin::Decoder;
|
||||
|
||||
#[test]
|
||||
fn immediates() {
|
||||
check_immediate(Decoder, v!(U8), Symbol::DECODE_U8);
|
||||
check_immediate(Decoder, v!(U16), Symbol::DECODE_U16);
|
||||
check_immediate(Decoder, v!(U32), Symbol::DECODE_U32);
|
||||
check_immediate(Decoder, v!(U64), Symbol::DECODE_U64);
|
||||
check_immediate(Decoder, v!(U128), Symbol::DECODE_U128);
|
||||
check_immediate(Decoder, v!(I8), Symbol::DECODE_I8);
|
||||
check_immediate(Decoder, v!(I16), Symbol::DECODE_I16);
|
||||
check_immediate(Decoder, v!(I32), Symbol::DECODE_I32);
|
||||
check_immediate(Decoder, v!(I64), Symbol::DECODE_I64);
|
||||
check_immediate(Decoder, v!(I128), Symbol::DECODE_I128);
|
||||
check_immediate(Decoder, v!(DEC), Symbol::DECODE_DEC);
|
||||
check_immediate(Decoder, v!(F32), Symbol::DECODE_F32);
|
||||
check_immediate(Decoder, v!(F64), Symbol::DECODE_F64);
|
||||
check_immediate(Decoder, v!(STR), Symbol::DECODE_STRING);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list() {
|
||||
derive_test(Decoder, v!(Symbol::LIST_LIST v!(STR)), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for List Str
|
||||
# Decoder (List val) fmt | fmt has DecoderFormatting, val has Decoding
|
||||
# List U8, fmt -[[custom(3)]]-> { rest : List U8, result : [Err [TooShort], Ok (List val)] } | fmt has DecoderFormatting, val has Decoding
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[custom(3)]]
|
||||
#Derived.decoder_list =
|
||||
Decode.custom
|
||||
\#Derived.bytes, #Derived.fmt ->
|
||||
Decode.decodeWith #Derived.bytes (Decode.list Decode.decoder) #Derived.fmt
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
|
@ -4,427 +4,22 @@
|
|||
// For the `v!` macro we use uppercase variables when constructing tag unions.
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use insta::assert_snapshot;
|
||||
use pretty_assertions::assert_eq;
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
use crate::pretty_print::{pretty_print_def, Ctx};
|
||||
use roc_can::{
|
||||
abilities::{AbilitiesStore, SpecializationLambdaSets},
|
||||
constraint::Constraints,
|
||||
def::Def,
|
||||
expr::Declarations,
|
||||
module::{
|
||||
ExposedByModule, ExposedForModule, ExposedModuleTypes, ResolvedImplementations,
|
||||
RigidVariables,
|
||||
},
|
||||
use crate::{
|
||||
test_hash_eq, test_hash_neq,
|
||||
util::{check_immediate, derive_test},
|
||||
v,
|
||||
};
|
||||
use roc_collections::VecSet;
|
||||
use roc_constrain::expr::constrain_decls;
|
||||
use roc_debug_flags::dbg_do;
|
||||
use roc_derive::{synth_var, DerivedModule};
|
||||
use roc_derive_key::{DeriveKey, Derived};
|
||||
use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading};
|
||||
use roc_module::{
|
||||
ident::TagName,
|
||||
symbol::{IdentIds, Interns, ModuleId, Symbol},
|
||||
};
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_reporting::report::{type_problem, RocDocAllocator};
|
||||
use roc_types::{
|
||||
pretty_print::{name_and_print_var, DebugPrint},
|
||||
subs::{
|
||||
AliasVariables, Content, ExposedTypesStorageSubs, FlatType, RecordFields, Subs, SubsIndex,
|
||||
SubsSlice, UnionTags, Variable,
|
||||
},
|
||||
types::{AliasKind, RecordField},
|
||||
};
|
||||
|
||||
const DERIVED_MODULE: ModuleId = ModuleId::DERIVED_SYNTH;
|
||||
|
||||
fn encode_path() -> PathBuf {
|
||||
let repo_root = std::env::var("ROC_WORKSPACE_DIR").expect("are you running with `cargo test`?");
|
||||
PathBuf::from(repo_root)
|
||||
.join("compiler")
|
||||
.join("builtins")
|
||||
.join("roc")
|
||||
.join("Encode.roc")
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn assemble_derived_golden(
|
||||
subs: &mut Subs,
|
||||
test_module: ModuleId,
|
||||
interns: &Interns,
|
||||
source_var: Variable,
|
||||
derived_source: &str,
|
||||
typ: Variable,
|
||||
specialization_lsets: SpecializationLambdaSets,
|
||||
) -> String {
|
||||
let mut print_var = |var: Variable, print_only_under_alias| {
|
||||
let snapshot = subs.snapshot();
|
||||
let pretty_type = name_and_print_var(
|
||||
var,
|
||||
subs,
|
||||
test_module,
|
||||
interns,
|
||||
DebugPrint {
|
||||
print_lambda_sets: true,
|
||||
print_only_under_alias,
|
||||
},
|
||||
);
|
||||
subs.rollback_to(snapshot);
|
||||
pretty_type
|
||||
};
|
||||
|
||||
let mut pretty_buf = String::new();
|
||||
|
||||
pretty_buf.push_str(&format!("# derived for {}\n", print_var(source_var, false)));
|
||||
|
||||
let pretty_type = print_var(typ, false);
|
||||
pretty_buf.push_str(&format!("# {}\n", &pretty_type));
|
||||
|
||||
let pretty_type_under_aliases = print_var(typ, true);
|
||||
pretty_buf.push_str(&format!("# {}\n", &pretty_type_under_aliases));
|
||||
|
||||
pretty_buf.push_str("# Specialization lambda sets:\n");
|
||||
let mut specialization_lsets = specialization_lsets.into_iter().collect::<Vec<_>>();
|
||||
specialization_lsets.sort_by_key(|(region, _)| *region);
|
||||
for (region, var) in specialization_lsets {
|
||||
let pretty_lset = print_var(var, false);
|
||||
pretty_buf.push_str(&format!("# @<{}>: {}\n", region, pretty_lset));
|
||||
}
|
||||
|
||||
pretty_buf.push_str(derived_source);
|
||||
|
||||
pretty_buf
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn check_derived_typechecks_and_golden(
|
||||
derived_def: Def,
|
||||
test_module: ModuleId,
|
||||
mut test_subs: Subs,
|
||||
interns: &Interns,
|
||||
exposed_encode_types: ExposedTypesStorageSubs,
|
||||
encode_abilities_store: AbilitiesStore,
|
||||
source_var: Variable,
|
||||
derived_program: &str,
|
||||
specialization_lsets: SpecializationLambdaSets,
|
||||
check_golden: impl Fn(&str),
|
||||
) {
|
||||
// constrain the derived
|
||||
let mut constraints = Constraints::new();
|
||||
let def_var = derived_def.expr_var;
|
||||
let mut decls = Declarations::new();
|
||||
decls.push_def(derived_def);
|
||||
let constr = constrain_decls(&mut constraints, test_module, &decls);
|
||||
|
||||
// the derived depends on stuff from Encode, so
|
||||
// - we need to add those dependencies as imported on the constraint
|
||||
// - we need to add Encode ability info to a local abilities store
|
||||
let encode_values_to_import = exposed_encode_types
|
||||
.stored_vars_by_symbol
|
||||
.keys()
|
||||
.copied()
|
||||
.collect::<VecSet<_>>();
|
||||
let pending_abilities = encode_abilities_store.closure_from_imported(&encode_values_to_import);
|
||||
let mut exposed_by_module = ExposedByModule::default();
|
||||
exposed_by_module.insert(
|
||||
ModuleId::ENCODE,
|
||||
ExposedModuleTypes {
|
||||
exposed_types_storage_subs: exposed_encode_types,
|
||||
resolved_implementations: ResolvedImplementations::default(),
|
||||
},
|
||||
);
|
||||
let exposed_for_module =
|
||||
ExposedForModule::new(encode_values_to_import.iter(), exposed_by_module);
|
||||
let mut def_types = Default::default();
|
||||
let mut rigid_vars = Default::default();
|
||||
let (import_variables, abilities_store) = add_imports(
|
||||
test_module,
|
||||
&mut test_subs,
|
||||
pending_abilities,
|
||||
&exposed_for_module,
|
||||
&mut def_types,
|
||||
&mut rigid_vars,
|
||||
);
|
||||
let constr =
|
||||
constraints.let_import_constraint(rigid_vars, def_types, constr, &import_variables);
|
||||
|
||||
// run the solver, print and fail if we have errors
|
||||
dbg_do!(
|
||||
roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED,
|
||||
std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, "1")
|
||||
);
|
||||
let (mut solved_subs, _, problems, _) = roc_solve::module::run_solve(
|
||||
test_module,
|
||||
&constraints,
|
||||
constr,
|
||||
RigidVariables::default(),
|
||||
test_subs,
|
||||
default_aliases(),
|
||||
abilities_store,
|
||||
Default::default(),
|
||||
&exposed_for_module.exposed_by_module,
|
||||
Default::default(),
|
||||
);
|
||||
let subs = solved_subs.inner_mut();
|
||||
|
||||
if !problems.is_empty() {
|
||||
let filename = PathBuf::from("Test.roc");
|
||||
let lines = LineInfo::new(" ");
|
||||
let src_lines = vec![" "];
|
||||
let mut reports = Vec::new();
|
||||
let alloc = RocDocAllocator::new(&src_lines, test_module, interns);
|
||||
|
||||
for problem in problems.into_iter() {
|
||||
if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) {
|
||||
reports.push(report);
|
||||
}
|
||||
}
|
||||
|
||||
let has_reports = !reports.is_empty();
|
||||
|
||||
let doc = alloc
|
||||
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
|
||||
.append(if has_reports {
|
||||
alloc.line()
|
||||
} else {
|
||||
alloc.nil()
|
||||
});
|
||||
|
||||
let mut buf = String::new();
|
||||
doc.1
|
||||
.render_raw(80, &mut roc_reporting::report::CiWrite::new(&mut buf))
|
||||
.unwrap();
|
||||
|
||||
panic!(
|
||||
"Derived does not typecheck:\n{}\nDerived def:\n{}",
|
||||
buf, derived_program
|
||||
);
|
||||
}
|
||||
|
||||
let golden = assemble_derived_golden(
|
||||
subs,
|
||||
test_module,
|
||||
interns,
|
||||
source_var,
|
||||
derived_program,
|
||||
def_var,
|
||||
specialization_lsets,
|
||||
);
|
||||
|
||||
check_golden(&golden)
|
||||
}
|
||||
|
||||
fn derive_test<S>(synth_input: S, check_golden: impl Fn(&str))
|
||||
where
|
||||
S: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let arena = Bump::new();
|
||||
let source = roc_builtins::roc::module_source(ModuleId::ENCODE);
|
||||
let target_info = roc_target::TargetInfo::default_x86_64();
|
||||
|
||||
let LoadedModule {
|
||||
mut interns,
|
||||
exposed_types_storage: exposed_encode_types,
|
||||
abilities_store,
|
||||
resolved_implementations,
|
||||
..
|
||||
} = roc_load_internal::file::load_and_typecheck_str(
|
||||
&arena,
|
||||
encode_path().file_name().unwrap().into(),
|
||||
source,
|
||||
encode_path().parent().unwrap().to_path_buf(),
|
||||
Default::default(),
|
||||
target_info,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::AllAvailable,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut subs = Subs::new();
|
||||
let ident_ids = IdentIds::default();
|
||||
let source_var = synth_input(&mut subs);
|
||||
let key = get_key(&subs, source_var);
|
||||
|
||||
let mut derived_module = unsafe { DerivedModule::from_components(subs, ident_ids) };
|
||||
|
||||
let mut exposed_by_module = ExposedByModule::default();
|
||||
exposed_by_module.insert(
|
||||
ModuleId::ENCODE,
|
||||
ExposedModuleTypes {
|
||||
exposed_types_storage_subs: exposed_encode_types.clone(),
|
||||
resolved_implementations,
|
||||
},
|
||||
);
|
||||
|
||||
let (_derived_symbol, derived_def, specialization_lsets) =
|
||||
derived_module.get_or_insert(&exposed_by_module, key);
|
||||
let specialization_lsets = specialization_lsets.clone();
|
||||
let derived_def = derived_def.clone();
|
||||
|
||||
let (subs, ident_ids) = derived_module.decompose();
|
||||
|
||||
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);
|
||||
|
||||
check_derived_typechecks_and_golden(
|
||||
derived_def,
|
||||
DERIVED_MODULE,
|
||||
subs,
|
||||
&interns,
|
||||
exposed_encode_types,
|
||||
abilities_store,
|
||||
source_var,
|
||||
&derived_program,
|
||||
specialization_lsets,
|
||||
check_golden,
|
||||
);
|
||||
}
|
||||
|
||||
fn get_key(subs: &Subs, var: Variable) -> DeriveKey {
|
||||
match Derived::encoding(subs, var) {
|
||||
Ok(Derived::Key(key)) => key,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_key<S1, S2>(eq: bool, synth1: S1, synth2: S2)
|
||||
where
|
||||
S1: FnOnce(&mut Subs) -> Variable,
|
||||
S2: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let mut subs = Subs::new();
|
||||
let var1 = synth1(&mut subs);
|
||||
let var2 = synth2(&mut subs);
|
||||
|
||||
let key1 = Derived::encoding(&subs, var1);
|
||||
let key2 = Derived::encoding(&subs, var2);
|
||||
|
||||
if eq {
|
||||
assert_eq!(key1, key2);
|
||||
} else {
|
||||
assert_ne!(key1, key2);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_immediate<S>(synth: S, immediate: Symbol)
|
||||
where
|
||||
S: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let mut subs = Subs::new();
|
||||
let var = synth(&mut subs);
|
||||
|
||||
let key = Derived::encoding(&subs, var);
|
||||
|
||||
assert_eq!(key, Ok(Derived::Immediate(immediate)));
|
||||
}
|
||||
|
||||
// Writing out the types into content is terrible, so let's use a DSL at least for testing
|
||||
macro_rules! v {
|
||||
({ $($field:ident: $make_v:expr,)* $(?$opt_field:ident : $make_opt_v:expr,)* }) => {
|
||||
|subs: &mut Subs| {
|
||||
$(let $field = $make_v(subs);)*
|
||||
$(let $opt_field = $make_opt_v(subs);)*
|
||||
let fields = vec![
|
||||
$( (stringify!($field).into(), RecordField::Required($field)) ,)*
|
||||
$( (stringify!($opt_field).into(), RecordField::Required($opt_field)) ,)*
|
||||
];
|
||||
let fields = RecordFields::insert_into_subs(subs, fields);
|
||||
synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)))
|
||||
}
|
||||
};
|
||||
([ $($tag:ident $($payload:expr)*),* ]) => {
|
||||
|subs: &mut Subs| {
|
||||
$(
|
||||
let $tag = vec![ $( $payload(subs), )* ];
|
||||
)*
|
||||
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
|
||||
synth_var(subs, Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION)))
|
||||
}
|
||||
};
|
||||
([ $($tag:ident $($payload:expr)*),* ] as $rec_var:ident) => {
|
||||
|subs: &mut Subs| {
|
||||
let $rec_var = subs.fresh_unnamed_flex_var();
|
||||
let rec_name_index =
|
||||
SubsIndex::push_new(&mut subs.field_names, stringify!($rec).into());
|
||||
|
||||
$(
|
||||
let $tag = vec![ $( $payload(subs), )* ];
|
||||
)*
|
||||
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
|
||||
let tag_union_var = synth_var(subs, Content::Structure(FlatType::RecursiveTagUnion($rec_var, tags, Variable::EMPTY_TAG_UNION)));
|
||||
|
||||
subs.set_content(
|
||||
$rec_var,
|
||||
Content::RecursionVar {
|
||||
structure: tag_union_var,
|
||||
opt_name: Some(rec_name_index),
|
||||
},
|
||||
);
|
||||
tag_union_var
|
||||
}
|
||||
};
|
||||
(Symbol::$sym:ident $($arg:expr)*) => {
|
||||
|subs: &mut Subs| {
|
||||
let $sym = vec![ $( $arg(subs) ,)* ];
|
||||
let var_slice = SubsSlice::insert_into_subs(subs, $sym);
|
||||
synth_var(subs, Content::Structure(FlatType::Apply(Symbol::$sym, var_slice)))
|
||||
}
|
||||
};
|
||||
(Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {
|
||||
|subs: &mut Subs| {
|
||||
let args = vec![$( $arg(subs) )*];
|
||||
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
|
||||
let real_var = $real_var(subs);
|
||||
synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Structural))
|
||||
}
|
||||
};
|
||||
(@Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {
|
||||
|subs: &mut Subs| {
|
||||
let args = vec![$( $arg(subs) )*];
|
||||
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
|
||||
let real_var = $real_var(subs);
|
||||
synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Opaque))
|
||||
}
|
||||
};
|
||||
(*$rec_var:ident) => {
|
||||
|_: &mut Subs| { $rec_var }
|
||||
};
|
||||
($var:ident) => {
|
||||
|_: &mut Subs| { Variable::$var }
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! test_hash_eq {
|
||||
($($name:ident: $synth1:expr, $synth2:expr)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
check_key(true, $synth1, $synth2)
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
macro_rules! test_hash_neq {
|
||||
($($name:ident: $synth1:expr, $synth2:expr)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
check_key(false, $synth1, $synth2)
|
||||
}
|
||||
)*};
|
||||
}
|
||||
use roc_derive_key::DeriveBuiltin::ToEncoder;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
// {{{ hash tests
|
||||
|
||||
test_hash_eq! {
|
||||
ToEncoder,
|
||||
|
||||
same_record:
|
||||
v!({ a: v!(U8), }), v!({ a: v!(U8), })
|
||||
same_record_fields_diff_types:
|
||||
|
@ -448,9 +43,9 @@ test_hash_eq! {
|
|||
v!(EMPTY_TAG_UNION), v!([])
|
||||
|
||||
same_recursive_tag_union:
|
||||
v!([ Nil, Cons v!(*lst)] as lst), v!([ Nil, Cons v!(*lst)] as lst)
|
||||
v!([ Nil, Cons v!(^lst)] as lst), v!([ Nil, Cons v!(^lst)] as lst)
|
||||
same_tag_union_and_recursive_tag_union_fields:
|
||||
v!([ Nil, Cons v!(STR)]), v!([ Nil, Cons v!(*lst)] as lst)
|
||||
v!([ Nil, Cons v!(STR)]), v!([ Nil, Cons v!(^lst)] as lst)
|
||||
|
||||
list_list_diff_types:
|
||||
v!(Symbol::LIST_LIST v!(STR)), v!(Symbol::LIST_LIST v!(U8))
|
||||
|
@ -476,6 +71,8 @@ test_hash_eq! {
|
|||
}
|
||||
|
||||
test_hash_neq! {
|
||||
ToEncoder,
|
||||
|
||||
different_record_fields:
|
||||
v!({ a: v!(U8), }), v!({ b: v!(U8), })
|
||||
record_empty_vs_nonempty:
|
||||
|
@ -486,7 +83,7 @@ test_hash_neq! {
|
|||
tag_union_empty_vs_nonempty:
|
||||
v!(EMPTY_TAG_UNION), v!([ B v!(U8) ])
|
||||
different_recursive_tag_union_tags:
|
||||
v!([ Nil, Cons v!(*lst) ] as lst), v!([ Nil, Next v!(*lst) ] as lst)
|
||||
v!([ Nil, Cons v!(^lst) ] as lst), v!([ Nil, Next v!(^lst) ] as lst)
|
||||
|
||||
same_alias_diff_real_type:
|
||||
v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::BOOL_BOOL => v!([ False, True, Maybe ]))
|
||||
|
@ -505,25 +102,25 @@ test_hash_neq! {
|
|||
|
||||
#[test]
|
||||
fn immediates() {
|
||||
check_immediate(v!(U8), Symbol::ENCODE_U8);
|
||||
check_immediate(v!(U16), Symbol::ENCODE_U16);
|
||||
check_immediate(v!(U32), Symbol::ENCODE_U32);
|
||||
check_immediate(v!(U64), Symbol::ENCODE_U64);
|
||||
check_immediate(v!(U128), Symbol::ENCODE_U128);
|
||||
check_immediate(v!(I8), Symbol::ENCODE_I8);
|
||||
check_immediate(v!(I16), Symbol::ENCODE_I16);
|
||||
check_immediate(v!(I32), Symbol::ENCODE_I32);
|
||||
check_immediate(v!(I64), Symbol::ENCODE_I64);
|
||||
check_immediate(v!(I128), Symbol::ENCODE_I128);
|
||||
check_immediate(v!(DEC), Symbol::ENCODE_DEC);
|
||||
check_immediate(v!(F32), Symbol::ENCODE_F32);
|
||||
check_immediate(v!(F64), Symbol::ENCODE_F64);
|
||||
check_immediate(v!(STR), Symbol::ENCODE_STRING);
|
||||
check_immediate(ToEncoder, v!(U8), Symbol::ENCODE_U8);
|
||||
check_immediate(ToEncoder, v!(U16), Symbol::ENCODE_U16);
|
||||
check_immediate(ToEncoder, v!(U32), Symbol::ENCODE_U32);
|
||||
check_immediate(ToEncoder, v!(U64), Symbol::ENCODE_U64);
|
||||
check_immediate(ToEncoder, v!(U128), Symbol::ENCODE_U128);
|
||||
check_immediate(ToEncoder, v!(I8), Symbol::ENCODE_I8);
|
||||
check_immediate(ToEncoder, v!(I16), Symbol::ENCODE_I16);
|
||||
check_immediate(ToEncoder, v!(I32), Symbol::ENCODE_I32);
|
||||
check_immediate(ToEncoder, v!(I64), Symbol::ENCODE_I64);
|
||||
check_immediate(ToEncoder, v!(I128), Symbol::ENCODE_I128);
|
||||
check_immediate(ToEncoder, v!(DEC), Symbol::ENCODE_DEC);
|
||||
check_immediate(ToEncoder, v!(F32), Symbol::ENCODE_F32);
|
||||
check_immediate(ToEncoder, v!(F64), Symbol::ENCODE_F64);
|
||||
check_immediate(ToEncoder, v!(STR), Symbol::ENCODE_STRING);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
derive_test(v!(EMPTY_RECORD), |golden| {
|
||||
derive_test(ToEncoder, v!(EMPTY_RECORD), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for {}
|
||||
# {} -[[toEncoder_{}(0)]]-> Encoder fmt | fmt has EncoderFormatting
|
||||
|
@ -543,7 +140,7 @@ fn empty_record() {
|
|||
|
||||
#[test]
|
||||
fn zero_field_record() {
|
||||
derive_test(v!({}), |golden| {
|
||||
derive_test(ToEncoder, v!({}), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for {}
|
||||
# {} -[[toEncoder_{}(0)]]-> Encoder fmt | fmt has EncoderFormatting
|
||||
|
@ -563,7 +160,7 @@ fn zero_field_record() {
|
|||
|
||||
#[test]
|
||||
fn one_field_record() {
|
||||
derive_test(v!({ a: v!(U8), }), |golden| {
|
||||
derive_test(ToEncoder, v!({ a: v!(U8), }), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for { a : U8 }
|
||||
# { a : val } -[[toEncoder_{a}(0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding
|
||||
|
@ -588,23 +185,27 @@ fn one_field_record() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
|
||||
fn two_field_record() {
|
||||
derive_test(v!({ a: v!(U8), b: v!(STR), }), |golden| {
|
||||
derive_test(ToEncoder, v!({ a: v!(U8), b: v!(STR), }), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for { a : U8, b : Str }
|
||||
# { a : val, b : a } -[[toEncoder_{a,b}(0)]]-> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# { a : val, b : a } -[[toEncoder_{a,b}(0)]]-> (List U8, fmt -[[custom(2) { a : val, b : a }]]-> List U8) | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# { a : val, b : val1 } -[[toEncoder_{a,b}(0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# { a : val, b : val1 } -[[toEncoder_{a,b}(0)]]-> (List U8, fmt -[[custom(2) { a : val, b : val1 }]]-> List U8) | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[toEncoder_{a,b}(0)]]
|
||||
# @<2>: [[custom(2) { a : val, b : a }]] | a has Encoding, val has Encoding
|
||||
# @<2>: [[custom(2) { a : val, b : val1 }]] | val has Encoding, val1 has Encoding
|
||||
#Derived.toEncoder_{a,b} =
|
||||
\#Derived.rcd ->
|
||||
Encode.custom \#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith #Derived.bytes (Encode.record [
|
||||
{ value: Encode.toEncoder #Derived.rcd.a, key: "a", },
|
||||
{ value: Encode.toEncoder #Derived.rcd.b, key: "b", },
|
||||
]) #Derived.fmt
|
||||
Encode.custom
|
||||
\#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith
|
||||
#Derived.bytes
|
||||
(Encode.record
|
||||
[
|
||||
{ value: Encode.toEncoder #Derived.rcd.a, key: "a", },
|
||||
{ value: Encode.toEncoder #Derived.rcd.b, key: "b", },
|
||||
])
|
||||
#Derived.fmt
|
||||
"###
|
||||
)
|
||||
})
|
||||
|
@ -614,7 +215,7 @@ fn two_field_record() {
|
|||
#[ignore = "NOTE: this would never actually happen, because [] is uninhabited, and hence toEncoder can never be called with a value of []!
|
||||
Rightfully it induces broken assertions in other parts of the compiler, so we ignore it."]
|
||||
fn empty_tag_union() {
|
||||
derive_test(v!(EMPTY_TAG_UNION), |golden| {
|
||||
derive_test(ToEncoder, v!(EMPTY_TAG_UNION), |golden| {
|
||||
assert_snapshot!(
|
||||
golden,
|
||||
@r#"
|
||||
|
@ -625,7 +226,7 @@ fn empty_tag_union() {
|
|||
|
||||
#[test]
|
||||
fn tag_one_label_zero_args() {
|
||||
derive_test(v!([A]), |golden| {
|
||||
derive_test(ToEncoder, v!([A]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A]
|
||||
# [A] -[[toEncoder_[A 0](0)]]-> Encoder fmt | fmt has EncoderFormatting
|
||||
|
@ -647,87 +248,109 @@ fn tag_one_label_zero_args() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
|
||||
fn tag_one_label_two_args() {
|
||||
derive_test(v!([A v!(U8) v!(STR)]), |golden| {
|
||||
derive_test(ToEncoder, v!([A v!(U8) v!(STR)]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A U8 Str]
|
||||
# [A val a] -[[toEncoder_[A 2](0)]]-> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [A val a] -[[toEncoder_[A 2](0)]]-> (List U8, fmt -[[custom(4) [A val a]]]-> List U8) | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [A val val1] -[[toEncoder_[A 2](0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# [A val val1] -[[toEncoder_[A 2](0)]]-> (List U8, fmt -[[custom(4) [A val val1]]]-> List U8) | fmt has EncoderFormatting, val has Encoding, val1 has Encoding
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[toEncoder_[A 2](0)]]
|
||||
# @<2>: [[custom(4) [A val a]]] | a has Encoding, val has Encoding
|
||||
# @<2>: [[custom(4) [A val val1]]] | val has Encoding, val1 has Encoding
|
||||
#Derived.toEncoder_[A 2] =
|
||||
\#Derived.tag ->
|
||||
Encode.custom \#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith #Derived.bytes (when #Derived.tag is
|
||||
A #Derived.2 #Derived.3 ->
|
||||
Encode.tag "A" [
|
||||
Encode.toEncoder #Derived.2,
|
||||
Encode.toEncoder #Derived.3,
|
||||
]) #Derived.fmt
|
||||
Encode.custom
|
||||
\#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith
|
||||
#Derived.bytes
|
||||
(when #Derived.tag is
|
||||
A #Derived.2 #Derived.3 ->
|
||||
Encode.tag
|
||||
"A"
|
||||
[
|
||||
Encode.toEncoder #Derived.2,
|
||||
Encode.toEncoder #Derived.3,
|
||||
])
|
||||
#Derived.fmt
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
|
||||
fn tag_two_labels() {
|
||||
derive_test(v!([A v!(U8) v!(STR) v!(U16), B v!(STR)]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
derive_test(
|
||||
ToEncoder,
|
||||
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 a b, B c] -[[toEncoder_[A 3,B 1](0)]]-> Encoder fmt | a has Encoding, b has Encoding, c has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [A val a b, B c] -[[toEncoder_[A 3,B 1](0)]]-> (List U8, fmt -[[custom(6) [A val a b, B c]]]-> List U8) | a has Encoding, b has Encoding, c has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [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 a b, B c]]] | a has Encoding, b has Encoding, c has Encoding, val has Encoding
|
||||
# @<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
|
||||
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
|
||||
"###
|
||||
)
|
||||
})
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
|
||||
fn recursive_tag_union() {
|
||||
derive_test(v!([Nil, Cons v!(U8) v!(*lst) ] as lst), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
derive_test(
|
||||
ToEncoder,
|
||||
v!([Nil, Cons v!(U8) v!(^lst) ] as lst),
|
||||
|golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [Cons U8 $rec, Nil] as $rec
|
||||
# [Cons val a, Nil] -[[toEncoder_[Cons 2,Nil 0](0)]]-> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [Cons val a, Nil] -[[toEncoder_[Cons 2,Nil 0](0)]]-> (List U8, fmt -[[custom(4) [Cons val a, Nil]]]-> List U8) | a has Encoding, fmt has EncoderFormatting, val has Encoding
|
||||
# [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 a, Nil]]] | a has Encoding, val has Encoding
|
||||
# @<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
|
||||
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
|
||||
"###
|
||||
)
|
||||
})
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list() {
|
||||
derive_test(v!(Symbol::LIST_LIST v!(STR)), |golden| {
|
||||
derive_test(ToEncoder, v!(Symbol::LIST_LIST v!(STR)), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for List Str
|
||||
# List val -[[toEncoder_list(0)]]-> Encoder fmt | fmt has EncoderFormatting, val has Encoding
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#![cfg(test)]
|
||||
|
||||
mod decoding;
|
||||
mod encoding;
|
||||
|
||||
mod pretty_print;
|
||||
mod util;
|
||||
|
|
467
crates/compiler/test_derive/src/util.rs
Normal file
467
crates/compiler/test_derive/src/util.rs
Normal file
|
@ -0,0 +1,467 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
use crate::pretty_print::{pretty_print_def, Ctx};
|
||||
use roc_can::{
|
||||
abilities::{AbilitiesStore, SpecializationLambdaSets},
|
||||
constraint::Constraints,
|
||||
def::Def,
|
||||
expr::Declarations,
|
||||
module::{
|
||||
ExposedByModule, ExposedForModule, ExposedModuleTypes, ResolvedImplementations,
|
||||
RigidVariables,
|
||||
},
|
||||
};
|
||||
use roc_collections::VecSet;
|
||||
use roc_constrain::expr::constrain_decls;
|
||||
use roc_debug_flags::dbg_do;
|
||||
use roc_derive::DerivedModule;
|
||||
use roc_derive_key::{DeriveBuiltin, DeriveKey, Derived};
|
||||
use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, Symbol};
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_reporting::report::{type_problem, RocDocAllocator};
|
||||
use roc_types::{
|
||||
pretty_print::{name_and_print_var, DebugPrint},
|
||||
subs::{ExposedTypesStorageSubs, Subs, Variable},
|
||||
};
|
||||
|
||||
const DERIVED_MODULE: ModuleId = ModuleId::DERIVED_SYNTH;
|
||||
|
||||
fn module_source_and_path(builtin: DeriveBuiltin) -> (ModuleId, &'static str, PathBuf) {
|
||||
use roc_builtins::roc::module_source;
|
||||
|
||||
let repo_root = std::env::var("ROC_WORKSPACE_DIR").expect("are you running with `cargo test`?");
|
||||
let builtins_path = PathBuf::from(repo_root)
|
||||
.join("compiler")
|
||||
.join("builtins")
|
||||
.join("roc");
|
||||
|
||||
match builtin {
|
||||
DeriveBuiltin::ToEncoder => (
|
||||
ModuleId::ENCODE,
|
||||
module_source(ModuleId::ENCODE),
|
||||
builtins_path.join("Encode.roc"),
|
||||
),
|
||||
DeriveBuiltin::Decoder => (
|
||||
ModuleId::DECODE,
|
||||
module_source(ModuleId::DECODE),
|
||||
builtins_path.join("Decode.roc"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// DSL for creating [`Content`][crate::subs::Content].
|
||||
#[macro_export]
|
||||
macro_rules! v {
|
||||
({ $($field:ident: $make_v:expr,)* $(?$opt_field:ident : $make_opt_v:expr,)* }) => {{
|
||||
#[allow(unused)]
|
||||
use roc_types::types::RecordField;
|
||||
use roc_types::subs::{Subs, RecordFields, Content, FlatType, Variable};
|
||||
|subs: &mut Subs| {
|
||||
$(let $field = $make_v(subs);)*
|
||||
$(let $opt_field = $make_opt_v(subs);)*
|
||||
let fields = vec![
|
||||
$( (stringify!($field).into(), RecordField::Required($field)) ,)*
|
||||
$( (stringify!($opt_field).into(), RecordField::Required($opt_field)) ,)*
|
||||
];
|
||||
let fields = RecordFields::insert_into_subs(subs, fields);
|
||||
roc_derive::synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)))
|
||||
}
|
||||
}};
|
||||
([ $($tag:ident $($payload:expr)*),* ]$( $ext:tt )?) => {{
|
||||
#[allow(unused)]
|
||||
use roc_types::subs::{Subs, UnionTags, Content, FlatType, Variable};
|
||||
#[allow(unused)]
|
||||
use roc_module::ident::TagName;
|
||||
|subs: &mut Subs| {
|
||||
$(
|
||||
let $tag = vec![ $( $payload(subs), )* ];
|
||||
)*
|
||||
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut ext = Variable::EMPTY_TAG_UNION;
|
||||
$( ext = $crate::v!($ext)(subs); )?
|
||||
|
||||
roc_derive::synth_var(subs, Content::Structure(FlatType::TagUnion(tags, ext)))
|
||||
}
|
||||
}};
|
||||
([ $($tag:ident $($payload:expr)*),* ] as $rec_var:ident) => {{
|
||||
use roc_types::subs::{Subs, SubsIndex, Variable, Content, FlatType, UnionTags};
|
||||
use roc_module::ident::TagName;
|
||||
|subs: &mut Subs| {
|
||||
let $rec_var = subs.fresh_unnamed_flex_var();
|
||||
let rec_name_index =
|
||||
SubsIndex::push_new(&mut subs.field_names, stringify!($rec).into());
|
||||
|
||||
$(
|
||||
let $tag = vec![ $( $payload(subs), )* ];
|
||||
)*
|
||||
let tags = UnionTags::insert_into_subs::<_, Vec<Variable>>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]);
|
||||
let tag_union_var = roc_derive::synth_var(subs, Content::Structure(FlatType::RecursiveTagUnion($rec_var, tags, Variable::EMPTY_TAG_UNION)));
|
||||
|
||||
subs.set_content(
|
||||
$rec_var,
|
||||
Content::RecursionVar {
|
||||
structure: tag_union_var,
|
||||
opt_name: Some(rec_name_index),
|
||||
},
|
||||
);
|
||||
tag_union_var
|
||||
}
|
||||
}};
|
||||
(Symbol::$sym:ident $($arg:expr)*) => {{
|
||||
use roc_types::subs::{Subs, SubsSlice, Content, FlatType};
|
||||
use roc_module::symbol::Symbol;
|
||||
|subs: &mut Subs| {
|
||||
let $sym = vec![ $( $arg(subs) ,)* ];
|
||||
let var_slice = SubsSlice::insert_into_subs(subs, $sym);
|
||||
roc_derive::synth_var(subs, Content::Structure(FlatType::Apply(Symbol::$sym, var_slice)))
|
||||
}
|
||||
}};
|
||||
(Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {{
|
||||
use roc_types::subs::{Subs, AliasVariables, Content};
|
||||
use roc_types::types::AliasKind;
|
||||
use roc_module::symbol::Symbol;
|
||||
|subs: &mut Subs| {
|
||||
let args = vec![$( $arg(subs) )*];
|
||||
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
|
||||
let real_var = $real_var(subs);
|
||||
roc_derive::synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Structural))
|
||||
}
|
||||
}};
|
||||
(@Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => {{
|
||||
use roc_types::subs::{Subs, AliasVariables, Content};
|
||||
use roc_types::types::AliasKind;
|
||||
use roc_module::symbol::Symbol;
|
||||
|subs: &mut Subs| {
|
||||
let args = vec![$( $arg(subs) )*];
|
||||
let alias_variables = AliasVariables::insert_into_subs::<Vec<_>, Vec<_>>(subs, args, vec![]);
|
||||
let real_var = $real_var(subs);
|
||||
roc_derive::synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Opaque))
|
||||
}
|
||||
}};
|
||||
(*) => {{
|
||||
use roc_types::subs::{Subs, Content};
|
||||
|subs: &mut Subs| { roc_derive::synth_var(subs, Content::FlexVar(None)) }
|
||||
}};
|
||||
(^$rec_var:ident) => {{
|
||||
use roc_types::subs::{Subs};
|
||||
|_: &mut Subs| { $rec_var }
|
||||
}};
|
||||
($var:ident) => {{
|
||||
use roc_types::subs::{Subs};
|
||||
|_: &mut Subs| { Variable::$var }
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) fn check_key<S1, S2>(builtin: DeriveBuiltin, eq: bool, synth1: S1, synth2: S2)
|
||||
where
|
||||
S1: FnOnce(&mut Subs) -> Variable,
|
||||
S2: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let mut subs = Subs::new();
|
||||
let var1 = synth1(&mut subs);
|
||||
let var2 = synth2(&mut subs);
|
||||
|
||||
let key1 = Derived::builtin(builtin, &subs, var1);
|
||||
let key2 = Derived::builtin(builtin, &subs, var2);
|
||||
|
||||
if eq {
|
||||
assert_eq!(key1, key2);
|
||||
} else {
|
||||
assert_ne!(key1, key2);
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_hash_eq {
|
||||
($builtin:expr, $($name:ident: $synth1:expr, $synth2:expr)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
$crate::util::check_key($builtin,true, $synth1, $synth2)
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_hash_neq {
|
||||
($builtin:expr, $($name:ident: $synth1:expr, $synth2:expr)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
$crate::util::check_key($builtin, false, $synth1, $synth2)
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
pub(crate) fn check_immediate<S>(builtin: DeriveBuiltin, synth: S, immediate: Symbol)
|
||||
where
|
||||
S: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let mut subs = Subs::new();
|
||||
let var = synth(&mut subs);
|
||||
|
||||
let key = Derived::builtin(builtin, &subs, var);
|
||||
|
||||
assert_eq!(key, Ok(Derived::Immediate(immediate)));
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn assemble_derived_golden(
|
||||
subs: &mut Subs,
|
||||
test_module: ModuleId,
|
||||
interns: &Interns,
|
||||
source_var: Variable,
|
||||
derived_source: &str,
|
||||
typ: Variable,
|
||||
specialization_lsets: SpecializationLambdaSets,
|
||||
) -> String {
|
||||
let mut print_var = |var: Variable, print_only_under_alias| {
|
||||
let snapshot = subs.snapshot();
|
||||
let pretty_type = name_and_print_var(
|
||||
var,
|
||||
subs,
|
||||
test_module,
|
||||
interns,
|
||||
DebugPrint {
|
||||
print_lambda_sets: true,
|
||||
print_only_under_alias,
|
||||
},
|
||||
);
|
||||
subs.rollback_to(snapshot);
|
||||
pretty_type
|
||||
};
|
||||
|
||||
let mut pretty_buf = String::new();
|
||||
|
||||
pretty_buf.push_str(&format!("# derived for {}\n", print_var(source_var, false)));
|
||||
|
||||
let pretty_type = print_var(typ, false);
|
||||
pretty_buf.push_str(&format!("# {}\n", &pretty_type));
|
||||
|
||||
let pretty_type_under_aliases = print_var(typ, true);
|
||||
pretty_buf.push_str(&format!("# {}\n", &pretty_type_under_aliases));
|
||||
|
||||
pretty_buf.push_str("# Specialization lambda sets:\n");
|
||||
let mut specialization_lsets = specialization_lsets.into_iter().collect::<Vec<_>>();
|
||||
specialization_lsets.sort_by_key(|(region, _)| *region);
|
||||
for (region, var) in specialization_lsets {
|
||||
let pretty_lset = print_var(var, false);
|
||||
pretty_buf.push_str(&format!("# @<{}>: {}\n", region, pretty_lset));
|
||||
}
|
||||
|
||||
pretty_buf.push_str(derived_source);
|
||||
|
||||
pretty_buf
|
||||
}
|
||||
|
||||
/// The environment of the module containing the builtin ability we're deriving for a type.
|
||||
struct DeriveBuiltinEnv {
|
||||
module_id: ModuleId,
|
||||
exposed_types: ExposedTypesStorageSubs,
|
||||
abilities_store: AbilitiesStore,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn check_derived_typechecks_and_golden(
|
||||
derived_def: Def,
|
||||
test_module: ModuleId,
|
||||
mut test_subs: Subs,
|
||||
interns: &Interns,
|
||||
derive_builtin_env: DeriveBuiltinEnv,
|
||||
source_var: Variable,
|
||||
derived_program: &str,
|
||||
specialization_lsets: SpecializationLambdaSets,
|
||||
check_golden: impl Fn(&str),
|
||||
) {
|
||||
// constrain the derived
|
||||
let mut constraints = Constraints::new();
|
||||
let def_var = derived_def.expr_var;
|
||||
let mut decls = Declarations::new();
|
||||
decls.push_def(derived_def);
|
||||
let constr = constrain_decls(&mut constraints, test_module, &decls);
|
||||
|
||||
// the derived implementation on stuff from the builtin module, so
|
||||
// - we need to add those dependencies as imported on the constraint
|
||||
// - we need to add the builtin ability info to a local abilities store
|
||||
let values_to_import_from_builtin_module = derive_builtin_env
|
||||
.exposed_types
|
||||
.stored_vars_by_symbol
|
||||
.keys()
|
||||
.copied()
|
||||
.collect::<VecSet<_>>();
|
||||
let pending_abilities = derive_builtin_env
|
||||
.abilities_store
|
||||
.closure_from_imported(&values_to_import_from_builtin_module);
|
||||
let mut exposed_by_module = ExposedByModule::default();
|
||||
exposed_by_module.insert(
|
||||
derive_builtin_env.module_id,
|
||||
ExposedModuleTypes {
|
||||
exposed_types_storage_subs: derive_builtin_env.exposed_types,
|
||||
resolved_implementations: ResolvedImplementations::default(),
|
||||
},
|
||||
);
|
||||
let exposed_for_module = ExposedForModule::new(
|
||||
values_to_import_from_builtin_module.iter(),
|
||||
exposed_by_module,
|
||||
);
|
||||
let mut def_types = Default::default();
|
||||
let mut rigid_vars = Default::default();
|
||||
let (import_variables, abilities_store) = add_imports(
|
||||
test_module,
|
||||
&mut test_subs,
|
||||
pending_abilities,
|
||||
&exposed_for_module,
|
||||
&mut def_types,
|
||||
&mut rigid_vars,
|
||||
);
|
||||
let constr =
|
||||
constraints.let_import_constraint(rigid_vars, def_types, constr, &import_variables);
|
||||
|
||||
// run the solver, print and fail if we have errors
|
||||
dbg_do!(
|
||||
roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED,
|
||||
std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, "1")
|
||||
);
|
||||
let (mut solved_subs, _, problems, _) = roc_solve::module::run_solve(
|
||||
test_module,
|
||||
&constraints,
|
||||
constr,
|
||||
RigidVariables::default(),
|
||||
test_subs,
|
||||
default_aliases(),
|
||||
abilities_store,
|
||||
Default::default(),
|
||||
&exposed_for_module.exposed_by_module,
|
||||
Default::default(),
|
||||
);
|
||||
let subs = solved_subs.inner_mut();
|
||||
|
||||
if !problems.is_empty() {
|
||||
let filename = PathBuf::from("Test.roc");
|
||||
let lines = LineInfo::new(" ");
|
||||
let src_lines = vec![" "];
|
||||
let mut reports = Vec::new();
|
||||
let alloc = RocDocAllocator::new(&src_lines, test_module, interns);
|
||||
|
||||
for problem in problems.into_iter() {
|
||||
if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) {
|
||||
reports.push(report);
|
||||
}
|
||||
}
|
||||
|
||||
let has_reports = !reports.is_empty();
|
||||
|
||||
let doc = alloc
|
||||
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
|
||||
.append(if has_reports {
|
||||
alloc.line()
|
||||
} else {
|
||||
alloc.nil()
|
||||
});
|
||||
|
||||
let mut buf = String::new();
|
||||
doc.1
|
||||
.render_raw(80, &mut roc_reporting::report::CiWrite::new(&mut buf))
|
||||
.unwrap();
|
||||
|
||||
panic!(
|
||||
"Derived does not typecheck:\n{}\nDerived def:\n{}",
|
||||
buf, derived_program
|
||||
);
|
||||
}
|
||||
|
||||
let golden = assemble_derived_golden(
|
||||
subs,
|
||||
test_module,
|
||||
interns,
|
||||
source_var,
|
||||
derived_program,
|
||||
def_var,
|
||||
specialization_lsets,
|
||||
);
|
||||
|
||||
check_golden(&golden)
|
||||
}
|
||||
|
||||
fn get_key(builtin: DeriveBuiltin, subs: &Subs, var: Variable) -> DeriveKey {
|
||||
match Derived::builtin(builtin, subs, var) {
|
||||
Ok(Derived::Key(key)) => key,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn derive_test<S>(builtin: DeriveBuiltin, synth_input: S, check_golden: impl Fn(&str))
|
||||
where
|
||||
S: FnOnce(&mut Subs) -> Variable,
|
||||
{
|
||||
let arena = Bump::new();
|
||||
let (builtin_module, source, path) = module_source_and_path(builtin);
|
||||
let target_info = roc_target::TargetInfo::default_x86_64();
|
||||
|
||||
let LoadedModule {
|
||||
mut interns,
|
||||
exposed_types_storage,
|
||||
abilities_store,
|
||||
resolved_implementations,
|
||||
..
|
||||
} = roc_load_internal::file::load_and_typecheck_str(
|
||||
&arena,
|
||||
path.file_name().unwrap().into(),
|
||||
source,
|
||||
path.parent().unwrap().to_path_buf(),
|
||||
Default::default(),
|
||||
target_info,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::AllAvailable,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut subs = Subs::new();
|
||||
let ident_ids = IdentIds::default();
|
||||
let source_var = synth_input(&mut subs);
|
||||
let key = get_key(builtin, &subs, source_var);
|
||||
|
||||
let mut derived_module = unsafe { DerivedModule::from_components(subs, ident_ids) };
|
||||
|
||||
let mut exposed_by_module = ExposedByModule::default();
|
||||
exposed_by_module.insert(
|
||||
builtin_module,
|
||||
ExposedModuleTypes {
|
||||
exposed_types_storage_subs: exposed_types_storage.clone(),
|
||||
resolved_implementations,
|
||||
},
|
||||
);
|
||||
|
||||
let (_derived_symbol, derived_def, specialization_lsets) =
|
||||
derived_module.get_or_insert(&exposed_by_module, key);
|
||||
let specialization_lsets = specialization_lsets.clone();
|
||||
let derived_def = derived_def.clone();
|
||||
|
||||
let (subs, ident_ids) = derived_module.decompose();
|
||||
|
||||
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);
|
||||
|
||||
check_derived_typechecks_and_golden(
|
||||
derived_def,
|
||||
DERIVED_MODULE,
|
||||
subs,
|
||||
&interns,
|
||||
DeriveBuiltinEnv {
|
||||
module_id: builtin_module,
|
||||
exposed_types: exposed_types_storage,
|
||||
abilities_store,
|
||||
},
|
||||
source_var,
|
||||
&derived_program,
|
||||
specialization_lsets,
|
||||
check_golden,
|
||||
);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "test_gen"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -437,7 +437,7 @@ mod encode_immediate {
|
|||
macro_rules! num_immediate {
|
||||
($($num:expr, $typ:ident)*) => {$(
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn $typ() {
|
||||
assert_evals_to!(
|
||||
&format!(indoc!(
|
||||
|
@ -689,6 +689,31 @@ fn encode_derived_list_of_records() {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[ignore = "#3696: Currently hits some weird panic in borrow checking, not sure if it's directly related to abilities."]
|
||||
fn encode_derived_list_of_lists_of_strings() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test"
|
||||
imports [Encode.{ toEncoder }, Json]
|
||||
provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
lst = [["a", "b"], ["c", "d", "e"], ["f"]]
|
||||
encoded = Encode.toBytes lst Json.toUtf8
|
||||
result = Str.fromUtf8 encoded
|
||||
when result is
|
||||
Ok s -> s
|
||||
_ -> "<bad>"
|
||||
"#
|
||||
),
|
||||
RocStr::from(r#"[["a","b"],["c","d","e"],["f"]]"#),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(
|
||||
any(feature = "gen-llvm", feature = "gen-wasm"),
|
||||
|
@ -780,3 +805,156 @@ fn decode_use_stdlib_json_list() {
|
|||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
mod decode_immediate {
|
||||
#[cfg(feature = "gen-llvm")]
|
||||
use crate::helpers::llvm::assert_evals_to;
|
||||
|
||||
#[cfg(feature = "gen-wasm")]
|
||||
use crate::helpers::wasm::assert_evals_to;
|
||||
|
||||
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
|
||||
use indoc::indoc;
|
||||
|
||||
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
|
||||
use roc_std::RocStr;
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn string() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Str.toUtf8 "\"foo\"" |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok s -> s
|
||||
_ -> "<bad>"
|
||||
"#
|
||||
),
|
||||
RocStr::from("foo"),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! num_immediate {
|
||||
($($num:expr, $typ:ident)*) => {$(
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn $typ() {
|
||||
assert_evals_to!(
|
||||
&format!(indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Num.toStr {}{} |> Str.toUtf8 |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok n -> n
|
||||
_ -> 101{}
|
||||
"#
|
||||
), $num, stringify!($typ), stringify!($typ)),
|
||||
$num,
|
||||
$typ
|
||||
)
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
num_immediate! {
|
||||
17, i8
|
||||
17, i16
|
||||
17, i32
|
||||
17, i64
|
||||
17, i128
|
||||
17, u8
|
||||
17, u16
|
||||
17, u32
|
||||
17, u64
|
||||
17, u128
|
||||
17.23, f32
|
||||
17.23, f64
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn dec() {
|
||||
use roc_std::RocDec;
|
||||
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Num.toStr 17.23dec |> Str.toUtf8 |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok n -> n
|
||||
_ -> 101dec
|
||||
"#
|
||||
),
|
||||
RocDec::from_str("17.23").unwrap(),
|
||||
RocDec
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn decode_list_of_strings() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Str.toUtf8 "[\"a\",\"b\",\"c\"]" |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok l -> Str.joinWith l ","
|
||||
_ -> "<bad>"
|
||||
"#
|
||||
),
|
||||
RocStr::from("a,b,c"),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(
|
||||
any(feature = "gen-llvm"), // currently fails on gen-wasm
|
||||
not(feature = "gen-llvm-wasm") // hits a stack limit in wasm3
|
||||
))]
|
||||
fn encode_then_decode_list_of_strings() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode, Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Encode.toBytes ["a", "b", "c"] Json.fromUtf8 |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok l -> Str.joinWith l ","
|
||||
_ -> "something went wrong"
|
||||
"#
|
||||
),
|
||||
RocStr::from("a,b,c"),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[ignore = "#3696: Currently hits some weird panic in borrow checking, not sure if it's directly related to abilities."]
|
||||
fn encode_then_decode_list_of_lists_of_strings() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode, Decode, Json] provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when Encode.toBytes [["a", "b"], ["c", "d", "e"], ["f"]] Json.fromUtf8 |> Decode.fromBytes Json.fromUtf8 is
|
||||
Ok list -> (List.map list \inner -> Str.joinWith inner ",") |> Str.joinWith l ";"
|
||||
_ -> "something went wrong"
|
||||
"#
|
||||
),
|
||||
RocStr::from("a,b;c,d,e;f"),
|
||||
RocStr
|
||||
)
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ use indoc::indoc;
|
|||
#[allow(unused_imports)]
|
||||
use roc_std::{RocList, RocResult, RocStr};
|
||||
|
||||
use core::convert::Infallible;
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
|
||||
fn roc_list_construction() {
|
||||
|
@ -287,6 +285,8 @@ fn list_map_try_ok() {
|
|||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_map_try_err() {
|
||||
use core::convert::Infallible;
|
||||
|
||||
assert_evals_to!(
|
||||
r#"
|
||||
List.mapTry [1, 2, 3] \_ -> Err -1
|
||||
|
@ -3354,3 +3354,18 @@ fn issue_3571_lowlevel_call_function_with_bool_lambda_set() {
|
|||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn issue_3530_uninitialized_capacity_in_list_literal() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
[11,22,33]
|
||||
"#
|
||||
),
|
||||
3,
|
||||
(usize, usize, usize),
|
||||
|(_, _, cap)| cap
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2741,7 +2741,7 @@ fn is_multiple_of_signed() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-wasm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn is_multiple_of_unsigned() {
|
||||
// true
|
||||
assert_evals_to!("Num.isMultipleOf 5u8 1", true, bool);
|
||||
|
|
|
@ -944,7 +944,7 @@ fn when_peano() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
#[should_panic(expected = "Roc failed with message: ")]
|
||||
fn overflow_frees_list() {
|
||||
assert_evals_to!(
|
||||
|
@ -963,8 +963,8 @@ fn overflow_frees_list() {
|
|||
List.get myList index
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
(3, 0),
|
||||
(i64, i8)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1187,7 +1187,7 @@ fn return_wrapped_function_pointer() {
|
|||
"#
|
||||
),
|
||||
1,
|
||||
i64,
|
||||
usize,
|
||||
|_| 1
|
||||
);
|
||||
}
|
||||
|
@ -1209,7 +1209,7 @@ fn return_wrapped_function_pointer_b() {
|
|||
"#
|
||||
),
|
||||
1,
|
||||
i64,
|
||||
usize,
|
||||
|_| 1
|
||||
);
|
||||
}
|
||||
|
@ -1235,7 +1235,7 @@ fn return_wrapped_closure() {
|
|||
"#
|
||||
),
|
||||
1,
|
||||
i64,
|
||||
usize,
|
||||
|_| 1
|
||||
);
|
||||
}
|
||||
|
@ -1343,7 +1343,7 @@ fn linked_list_is_empty_2() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn linked_list_singleton() {
|
||||
// verifies only that valid llvm is produced
|
||||
assert_evals_to!(
|
||||
|
@ -1479,7 +1479,10 @@ fn rbtree_insert() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(any(feature = "gen-llvm"), not(feature = "gen-llvm-wasm")))]
|
||||
#[cfg(all(
|
||||
any(feature = "gen-llvm", feature = "gen-wasm"),
|
||||
not(feature = "gen-llvm-wasm")
|
||||
))]
|
||||
fn rbtree_balance_3() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -1498,8 +1501,8 @@ fn rbtree_balance_3() {
|
|||
"#
|
||||
),
|
||||
false,
|
||||
*const i64,
|
||||
|x: *const i64| x.is_null()
|
||||
usize,
|
||||
|x: usize| x == 0
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1880,7 +1883,7 @@ fn task_always_twice() {
|
|||
"#
|
||||
),
|
||||
0,
|
||||
i64,
|
||||
usize,
|
||||
|_| 0
|
||||
);
|
||||
}
|
||||
|
@ -1910,7 +1913,7 @@ fn wildcard_rigid() {
|
|||
"#
|
||||
),
|
||||
0,
|
||||
i64,
|
||||
usize,
|
||||
|_| 0
|
||||
);
|
||||
}
|
||||
|
@ -1939,7 +1942,7 @@ fn alias_of_alias_with_type_arguments() {
|
|||
"#
|
||||
),
|
||||
0,
|
||||
i64,
|
||||
usize,
|
||||
|_| 0
|
||||
);
|
||||
}
|
||||
|
@ -2202,9 +2205,10 @@ fn nullable_eval_cfold() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn nested_switch() {
|
||||
// exposed bug with passing the right symbol/layout down into switch branch generation
|
||||
// This is also the only test_gen test that exercises Reset/Reuse (as of Aug 2022)
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
|
@ -3420,7 +3424,7 @@ fn polymorphic_lambda_set_multiple_specializations() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn list_map2_conslist() {
|
||||
// this had an RC problem, https://github.com/rtfeldman/roc/issues/2968
|
||||
assert_evals_to!(
|
||||
|
@ -3614,7 +3618,7 @@ fn lambda_capture_niches_have_captured_function_in_closure() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn recursive_call_capturing_function() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -3651,7 +3655,7 @@ fn shared_pattern_variable_in_when_branches() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn symbol_not_bound_in_all_patterns_runs_when_no_bound_symbol_used() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -3662,8 +3666,8 @@ fn symbol_not_bound_in_all_patterns_runs_when_no_bound_symbol_used() {
|
|||
{a: f (A 15u8), b: f (B 15u8)}
|
||||
"#
|
||||
),
|
||||
31u8,
|
||||
u8,
|
||||
(31u8, 31u8),
|
||||
(u8, u8),
|
||||
|x| x,
|
||||
true // allow errors
|
||||
);
|
||||
|
|
|
@ -400,6 +400,28 @@ fn union_linked_list_dec() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-wasm"))]
|
||||
fn union_linked_list_nil_dec() {
|
||||
let no_refcounts: &[crate::helpers::RefCount] = &[];
|
||||
assert_refcounts!(
|
||||
indoc!(
|
||||
r#"
|
||||
LinkedList a : [Nil, Cons a (LinkedList a)]
|
||||
|
||||
linked : LinkedList Str
|
||||
linked = Nil
|
||||
|
||||
when linked is
|
||||
Cons x _ -> x
|
||||
Nil -> ""
|
||||
"#
|
||||
),
|
||||
RocStr,
|
||||
no_refcounts
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-wasm"))]
|
||||
fn union_linked_list_long_dec() {
|
||||
|
|
|
@ -477,7 +477,7 @@ fn nested_pattern_match() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn if_guard_vanilla() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -1363,7 +1363,7 @@ fn issue_2365_monomorphize_tag_with_non_empty_ext_var() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -1392,7 +1392,7 @@ fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn issue_2365_monomorphize_tag_with_non_empty_ext_var_wrapped_nested() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
|
|
@ -31,6 +31,10 @@
|
|||
font-weight: bold;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
select {
|
||||
font-size: 18px;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
small {
|
||||
font-style: italic;
|
||||
}
|
||||
|
@ -69,7 +73,7 @@
|
|||
In <code>gen_wasm/src/lib.rs</code>, set
|
||||
<code>DEBUG_LOG_SETTINGS.keep_test_binary = true</code>
|
||||
</li>
|
||||
<li>Run <code>cargo test-gen-wasm -- my_test --nocapture</code></li>
|
||||
<li>Run <code>cargo test-gen-wasm my_test_function_name</code></li>
|
||||
<li>
|
||||
Look for the path written to the console for
|
||||
<code>final.wasm</code> and select it in the file picker below
|
||||
|
@ -79,45 +83,62 @@
|
|||
<small> Control+Shift+I or Command+Option+I or F12 </small>
|
||||
</li>
|
||||
<li>
|
||||
Click one of the buttons below, depending on what kind of test it is.
|
||||
<br />
|
||||
<small>
|
||||
Only one of them will work. The other will probably crash or
|
||||
something.
|
||||
</small>
|
||||
If your test is from <code>gen_refcount.rs</code>
|
||||
</li>
|
||||
<li>
|
||||
The debugger should pause just before entering the first Wasm call.
|
||||
Step into a couple of Wasm calls until you reach your test code in
|
||||
<code>$#UserApp_main_1</code>
|
||||
<code>$main</code>
|
||||
</li>
|
||||
<li>
|
||||
Chrome DevTools now has a Memory Inspector panel! In the debugger,
|
||||
Chrome DevTools has a handy Memory Inspector panel. In the debugger,
|
||||
find <code>Module -> memories -> $memory</code>. Right click and
|
||||
select "Reveal in Memory Inspector"
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="controls">
|
||||
<div class="row">
|
||||
<label>
|
||||
<input type="checkbox" id="refcount-test">
|
||||
Check box if test is from <code>gen_refcount.rs</code>
|
||||
</label>
|
||||
</div>
|
||||
<div class="row row-file">
|
||||
<label for="wasm-file">Select final.wasm</label>
|
||||
<input id="wasm-file" type="file" />
|
||||
</div>
|
||||
<div id="error" class="row"></div>
|
||||
<div class="row">
|
||||
<button id="button-expression">Run as Roc expression test</button>
|
||||
<button id="button-refcount">Run as reference counting test</button>
|
||||
<button id="button-run">RUN</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
const file_input = document.getElementById("wasm-file");
|
||||
const button_expression = document.getElementById("button-expression");
|
||||
const button_refcount = document.getElementById("button-refcount");
|
||||
const refcount_checkbox = document.getElementById("refcount-test");
|
||||
const button = document.getElementById("button-run");
|
||||
const error_box = document.getElementById("error");
|
||||
|
||||
button_expression.onclick = runExpressionTest;
|
||||
button_refcount.onclick = runRefcountTest;
|
||||
if (localStorage.getItem("refcount-test")) {
|
||||
refcount_checkbox.checked = true;
|
||||
}
|
||||
refcount_checkbox.onchange = function (ev) {
|
||||
if (ev.target.checked) {
|
||||
localStorage.setItem("refcount-test", "true");
|
||||
} else {
|
||||
localStorage.removeItem("refcount-test");
|
||||
}
|
||||
}
|
||||
|
||||
button.onclick = function () {
|
||||
if (refcount_checkbox.checked) {
|
||||
runRefcountTest();
|
||||
} else {
|
||||
runExpressionTest();
|
||||
}
|
||||
}
|
||||
|
||||
file_input.onchange = function () {
|
||||
error_box.innerHTML = "";
|
||||
};
|
||||
|
@ -126,7 +147,7 @@
|
|||
const file = getFile();
|
||||
const instance = await compileFileToInstance(file);
|
||||
|
||||
debugger; // Next call is Wasm! Step into test_wrapper, then $#UserApp_main_1
|
||||
debugger; // Next call is Wasm! Step into test_wrapper, then $main
|
||||
instance.exports.test_wrapper();
|
||||
}
|
||||
|
||||
|
@ -137,7 +158,7 @@
|
|||
const refcount_vector_addr =
|
||||
instance.exports.init_refcount_test(MAX_ALLOCATIONS);
|
||||
|
||||
debugger; // Next call is Wasm! Step into test_wrapper, then $#UserApp_main_1
|
||||
debugger; // Next call is Wasm! Step into test_wrapper, then $main
|
||||
instance.exports.test_wrapper();
|
||||
|
||||
const words = new Uint32Array(instance.exports.memory.buffer);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use libloading::Library;
|
||||
use roc_build::link::{link, LinkType};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_load::Threading;
|
||||
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading};
|
||||
use roc_region::all::LineInfo;
|
||||
use tempfile::tempdir;
|
||||
|
||||
|
@ -50,15 +50,19 @@ pub fn helper(
|
|||
module_src = &temp;
|
||||
}
|
||||
|
||||
let load_config = LoadConfig {
|
||||
target_info: roc_target::TargetInfo::default_x86_64(),
|
||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
threading: Threading::Single,
|
||||
exec_mode: ExecutionMode::Executable,
|
||||
};
|
||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
filename,
|
||||
module_src,
|
||||
src_dir,
|
||||
Default::default(),
|
||||
roc_target::TargetInfo::default_x86_64(),
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::Single,
|
||||
load_config,
|
||||
);
|
||||
|
||||
let mut loaded = loaded.expect("failed to load module");
|
||||
|
@ -96,8 +100,16 @@ pub fn helper(
|
|||
}
|
||||
|
||||
debug_assert_eq!(exposed_to_host.values.len(), 1);
|
||||
let main_fn_symbol = loaded.entry_point.symbol;
|
||||
let main_fn_layout = loaded.entry_point.layout;
|
||||
let entry_point = match loaded.entry_point {
|
||||
EntryPoint::Executable { symbol, layout, .. } => {
|
||||
roc_mono::ir::EntryPoint { symbol, layout }
|
||||
}
|
||||
EntryPoint::Test => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
let main_fn_symbol = entry_point.symbol;
|
||||
let main_fn_layout = entry_point.layout;
|
||||
|
||||
let mut layout_ids = roc_mono::layout::LayoutIds::default();
|
||||
let main_fn_name = layout_ids
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use roc_error_macros::internal_error;
|
||||
use roc_gen_wasm::{round_up_to_alignment, wasm32_sized::Wasm32Sized};
|
||||
use roc_mono::layout::Builtin;
|
||||
use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
|
||||
use std::convert::TryInto;
|
||||
|
||||
|
@ -57,8 +58,9 @@ impl FromWasm32Memory for RocStr {
|
|||
|
||||
let str_words: &[u32; 3] = unsafe { std::mem::transmute(&str_bytes) };
|
||||
|
||||
let big_elem_ptr = str_words[0] as usize;
|
||||
let big_length = str_words[1] as usize;
|
||||
let big_elem_ptr = str_words[Builtin::WRAPPER_PTR as usize] as usize;
|
||||
let big_length = str_words[Builtin::WRAPPER_LEN as usize] as usize;
|
||||
let big_capacity = str_words[Builtin::WRAPPER_CAPACITY as usize] as usize;
|
||||
|
||||
let last_byte = str_bytes[11];
|
||||
let is_small_str = last_byte >= 0x80;
|
||||
|
@ -70,16 +72,20 @@ impl FromWasm32Memory for RocStr {
|
|||
&memory_bytes[big_elem_ptr..][..big_length]
|
||||
};
|
||||
|
||||
unsafe { RocStr::from_slice_unchecked(slice) }
|
||||
let mut roc_str = unsafe { RocStr::from_slice_unchecked(slice) };
|
||||
if !is_small_str {
|
||||
roc_str.reserve(big_capacity - big_length)
|
||||
}
|
||||
roc_str
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromWasm32Memory + Clone> FromWasm32Memory for RocList<T> {
|
||||
fn decode(memory: &[u8], offset: u32) -> Self {
|
||||
let bytes = <u64 as FromWasm32Memory>::decode(memory, offset);
|
||||
|
||||
let length = (bytes >> 32) as u32;
|
||||
let elements = bytes as u32;
|
||||
let elements = <u32 as FromWasm32Memory>::decode(memory, offset + 4 * Builtin::WRAPPER_PTR);
|
||||
let length = <u32 as FromWasm32Memory>::decode(memory, offset + 4 * Builtin::WRAPPER_LEN);
|
||||
let capacity =
|
||||
<u32 as FromWasm32Memory>::decode(memory, offset + 4 * Builtin::WRAPPER_CAPACITY);
|
||||
|
||||
let mut items = Vec::with_capacity(length as usize);
|
||||
|
||||
|
@ -91,7 +97,9 @@ impl<T: FromWasm32Memory + Clone> FromWasm32Memory for RocList<T> {
|
|||
items.push(item);
|
||||
}
|
||||
|
||||
RocList::from_slice(&items)
|
||||
let mut list = RocList::with_capacity(capacity as usize);
|
||||
list.extend_from_slice(&items);
|
||||
list
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ use roc_build::program::FunctionIterator;
|
|||
use roc_collections::all::MutSet;
|
||||
use roc_gen_llvm::llvm::build::LlvmBackendMode;
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_load::Threading;
|
||||
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_reporting::report::RenderTarget;
|
||||
|
@ -66,15 +66,19 @@ fn create_llvm_module<'a>(
|
|||
module_src = &temp;
|
||||
}
|
||||
|
||||
let load_config = LoadConfig {
|
||||
target_info,
|
||||
render: RenderTarget::ColorTerminal,
|
||||
threading: Threading::Single,
|
||||
exec_mode: ExecutionMode::Executable,
|
||||
};
|
||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
filename,
|
||||
module_src,
|
||||
src_dir,
|
||||
Default::default(),
|
||||
target_info,
|
||||
RenderTarget::ColorTerminal,
|
||||
Threading::Single,
|
||||
load_config,
|
||||
);
|
||||
|
||||
let mut loaded = match loaded {
|
||||
|
@ -226,6 +230,14 @@ fn create_llvm_module<'a>(
|
|||
// platform to provide them.
|
||||
add_default_roc_externs(&env);
|
||||
|
||||
let entry_point = match entry_point {
|
||||
EntryPoint::Executable { symbol, layout, .. } => {
|
||||
roc_mono::ir::EntryPoint { symbol, layout }
|
||||
}
|
||||
EntryPoint::Test => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
let (main_fn_name, main_fn) = match config.mode {
|
||||
LlvmBackendMode::Binary => unreachable!(),
|
||||
LlvmBackendMode::CliTest => unreachable!(),
|
||||
|
|
|
@ -4,7 +4,7 @@ use roc_collections::all::MutSet;
|
|||
use roc_gen_wasm::wasm32_result::Wasm32Result;
|
||||
use roc_gen_wasm::wasm_module::{Export, ExportType};
|
||||
use roc_gen_wasm::DEBUG_SETTINGS;
|
||||
use roc_load::Threading;
|
||||
use roc_load::{ExecutionMode, LoadConfig, Threading};
|
||||
use std::marker::PhantomData;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
@ -35,9 +35,9 @@ fn promote_expr_to_module(src: &str) -> String {
|
|||
}
|
||||
|
||||
fn write_final_wasm() -> bool {
|
||||
use roc_debug_flags::{dbg_do, ROC_WRITE_FINAL_WASM};
|
||||
use roc_debug_flags::dbg_do;
|
||||
|
||||
dbg_do!(ROC_WRITE_FINAL_WASM, {
|
||||
dbg_do!(roc_debug_flags::ROC_WRITE_FINAL_WASM, {
|
||||
return true;
|
||||
});
|
||||
|
||||
|
@ -84,15 +84,19 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>(
|
|||
module_src = &temp;
|
||||
}
|
||||
|
||||
let load_config = LoadConfig {
|
||||
target_info: roc_target::TargetInfo::default_wasm32(),
|
||||
render: roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
threading: Threading::Single,
|
||||
exec_mode: ExecutionMode::Executable,
|
||||
};
|
||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
filename,
|
||||
module_src,
|
||||
src_dir,
|
||||
Default::default(),
|
||||
roc_target::TargetInfo::default_wasm32(),
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::Single,
|
||||
load_config,
|
||||
);
|
||||
|
||||
let loaded = loaded.expect("failed to load module");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "test_mono"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
26
crates/compiler/test_mono/generated/issue_3669.txt
Normal file
26
crates/compiler/test_mono/generated/issue_3669.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
procedure Bool.7 (#Attr.2, #Attr.3):
|
||||
let Bool.9 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
|
||||
ret Bool.9;
|
||||
|
||||
procedure Test.2 (Test.19):
|
||||
joinpoint Test.13 Test.7:
|
||||
let Test.16 : U8 = 1i64;
|
||||
let Test.17 : U8 = GetTagId Test.7;
|
||||
let Test.18 : Int1 = lowlevel Eq Test.16 Test.17;
|
||||
if Test.18 then
|
||||
let Test.14 : {} = Struct {};
|
||||
ret Test.14;
|
||||
else
|
||||
let Test.5 : [<rnu><null>, C *self] = UnionAtIndex (Id 0) (Index 0) Test.7;
|
||||
jump Test.13 Test.5;
|
||||
in
|
||||
jump Test.13 Test.19;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.12 : [<rnu><null>, C *self] = TagId(1) ;
|
||||
let Test.10 : {} = CallByName Test.2 Test.12;
|
||||
dec Test.12;
|
||||
let Test.11 : {} = Struct {};
|
||||
let Test.8 : Int1 = CallByName Bool.7 Test.10 Test.11;
|
||||
let Test.9 : Str = "";
|
||||
ret Test.9;
|
|
@ -13,6 +13,8 @@ extern crate indoc;
|
|||
#[allow(dead_code)]
|
||||
const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024;
|
||||
|
||||
use roc_load::ExecutionMode;
|
||||
use roc_load::LoadConfig;
|
||||
use test_mono_macros::*;
|
||||
|
||||
use roc_collections::all::MutMap;
|
||||
|
@ -91,15 +93,19 @@ fn compiles_to_ir(test_name: &str, src: &str) {
|
|||
module_src = &temp;
|
||||
}
|
||||
|
||||
let load_config = LoadConfig {
|
||||
target_info: TARGET_INFO,
|
||||
threading: Threading::Single,
|
||||
render: roc_reporting::report::RenderTarget::Generic,
|
||||
exec_mode: ExecutionMode::Executable,
|
||||
};
|
||||
let loaded = roc_load::load_and_monomorphize_from_str(
|
||||
arena,
|
||||
filename,
|
||||
module_src,
|
||||
src_dir,
|
||||
Default::default(),
|
||||
TARGET_INFO,
|
||||
roc_reporting::report::RenderTarget::Generic,
|
||||
Threading::Single,
|
||||
load_config,
|
||||
);
|
||||
|
||||
let mut loaded = match loaded {
|
||||
|
@ -1895,3 +1901,24 @@ fn issue_3560_nested_tag_constructor_is_newtype() {
|
|||
"#
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn issue_3669() {
|
||||
indoc!(
|
||||
r#"
|
||||
Peano a := [
|
||||
Zero,
|
||||
Successor (Peano a)
|
||||
]
|
||||
|
||||
unwrap : Peano a -> {}
|
||||
unwrap = \@Peano p ->
|
||||
when p is
|
||||
Zero -> {}
|
||||
Successor inner -> unwrap inner
|
||||
|
||||
when unwrap (@Peano Zero) == {} is
|
||||
_ -> ""
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "test_mono_macros"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "roc_types"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
|
|
@ -2366,10 +2366,6 @@ impl AliasVariables {
|
|||
|
||||
let all_variables_len = (subs.variables.len() as u32 - variables_start) as u16;
|
||||
|
||||
if type_variables_len == 3 {
|
||||
panic!();
|
||||
}
|
||||
|
||||
Self {
|
||||
variables_start,
|
||||
type_variables_len,
|
||||
|
|
|
@ -3,7 +3,7 @@ authors = ["The Roc Contributors"]
|
|||
edition = "2021"
|
||||
license = "UPL-1.0"
|
||||
name = "roc_unify"
|
||||
version = "0.1.0"
|
||||
version = "0.0.1"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "1.3.2"
|
||||
|
|
|
@ -680,25 +680,49 @@ fn unify_two_aliases<M: MetaCollector>(
|
|||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
ctx: &Context,
|
||||
// _symbol has an underscore because it's unused in --release builds
|
||||
_symbol: Symbol,
|
||||
kind: AliasKind,
|
||||
symbol: Symbol,
|
||||
args: AliasVariables,
|
||||
real_var: Variable,
|
||||
other_args: AliasVariables,
|
||||
other_real_var: Variable,
|
||||
other_content: &Content,
|
||||
) -> Outcome<M> {
|
||||
if args.len() == other_args.len() {
|
||||
let mut outcome = Outcome::default();
|
||||
let it = args
|
||||
.all_variables()
|
||||
.into_iter()
|
||||
.zip(other_args.all_variables().into_iter());
|
||||
|
||||
for (l, r) in it {
|
||||
let args_it = args
|
||||
.type_variables()
|
||||
.into_iter()
|
||||
.zip(other_args.type_variables().into_iter());
|
||||
|
||||
let lambda_set_it = args
|
||||
.lambda_set_variables()
|
||||
.into_iter()
|
||||
.zip(other_args.lambda_set_variables().into_iter());
|
||||
|
||||
let mut merged_args = Vec::with_capacity(args.type_variables().len());
|
||||
let mut merged_lambda_set_args = Vec::with_capacity(args.lambda_set_variables().len());
|
||||
debug_assert_eq!(
|
||||
merged_args.capacity() + merged_lambda_set_args.capacity(),
|
||||
args.all_variables_len as _
|
||||
);
|
||||
|
||||
for (l, r) in args_it {
|
||||
let l_var = env.subs[l];
|
||||
let r_var = env.subs[r];
|
||||
outcome.union(unify_pool(env, pool, l_var, r_var, ctx.mode));
|
||||
|
||||
let merged_var = choose_merged_var(env.subs, l_var, r_var);
|
||||
merged_args.push(merged_var);
|
||||
}
|
||||
|
||||
for (l, r) in lambda_set_it {
|
||||
let l_var = env.subs[l];
|
||||
let r_var = env.subs[r];
|
||||
outcome.union(unify_pool(env, pool, l_var, r_var, ctx.mode));
|
||||
|
||||
let merged_var = choose_merged_var(env.subs, l_var, r_var);
|
||||
merged_lambda_set_args.push(merged_var);
|
||||
}
|
||||
|
||||
if outcome.mismatches.is_empty() {
|
||||
|
@ -730,12 +754,21 @@ fn unify_two_aliases<M: MetaCollector>(
|
|||
let _ = real_var_outcome.mismatches.drain(..);
|
||||
outcome.union(real_var_outcome);
|
||||
|
||||
outcome.union(merge(env, ctx, *other_content));
|
||||
let merged_real_var = choose_merged_var(env.subs, real_var, other_real_var);
|
||||
|
||||
// POSSIBLE OPT: choose_merged_var chooses the left when the choice is arbitrary. If
|
||||
// the merged vars are all left, avoid re-insertion. Is checking for argument slice
|
||||
// equality faster than re-inserting?
|
||||
let merged_variables =
|
||||
AliasVariables::insert_into_subs(env.subs, merged_args, merged_lambda_set_args);
|
||||
let merged_content = Content::Alias(symbol, merged_variables, merged_real_var, kind);
|
||||
|
||||
outcome.union(merge(env, ctx, merged_content));
|
||||
}
|
||||
|
||||
outcome
|
||||
} else {
|
||||
mismatch!("{:?}", _symbol)
|
||||
mismatch!("{:?}", symbol)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -769,12 +802,12 @@ fn unify_alias<M: MetaCollector>(
|
|||
env,
|
||||
pool,
|
||||
ctx,
|
||||
AliasKind::Structural,
|
||||
symbol,
|
||||
args,
|
||||
real_var,
|
||||
*other_args,
|
||||
*other_real_var,
|
||||
other_content,
|
||||
)
|
||||
} else {
|
||||
unify_pool(env, pool, real_var, *other_real_var, ctx.mode)
|
||||
|
@ -838,12 +871,12 @@ fn unify_opaque<M: MetaCollector>(
|
|||
env,
|
||||
pool,
|
||||
ctx,
|
||||
AliasKind::Opaque,
|
||||
symbol,
|
||||
args,
|
||||
real_var,
|
||||
*other_args,
|
||||
*other_real_var,
|
||||
other_content,
|
||||
)
|
||||
} else {
|
||||
mismatch!("{:?}", symbol)
|
||||
|
@ -2208,6 +2241,46 @@ fn maybe_mark_union_recursive(env: &mut Env, union_var: Variable) {
|
|||
}
|
||||
}
|
||||
|
||||
fn choose_merged_var(subs: &Subs, var1: Variable, var2: Variable) -> Variable {
|
||||
// If one of the variables is a recursion var, keep that one, so that we avoid inlining
|
||||
// a recursive tag union type content where we should have a recursion var instead.
|
||||
//
|
||||
// When might this happen? For example, in the code
|
||||
//
|
||||
// Indirect : [Indirect ConsList]
|
||||
//
|
||||
// ConsList : [Nil, Cons Indirect]
|
||||
//
|
||||
// l : ConsList
|
||||
// l = Cons (Indirect (Cons (Indirect Nil)))
|
||||
// # ^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~~~^ region-a
|
||||
// # ~~~~~~~~~~~~~~~~~~~~~ region-b
|
||||
// l
|
||||
//
|
||||
// Suppose `ConsList` has the expanded type `[Nil, Cons [Indirect <rec>]] as <rec>`.
|
||||
// After unifying the tag application annotated "region-b" with the recursion variable `<rec>`,
|
||||
// we might have that e.g. `actual` is `<rec>` and `expected` is `[Cons (Indirect ...)]`.
|
||||
//
|
||||
// Now, we need to be careful to set the type we choose to represent the merged type
|
||||
// here to be `<rec>`, not the tag union content of `expected`! Otherwise, we will
|
||||
// have lost a recursion variable in the recursive tag union.
|
||||
//
|
||||
// This would not be incorrect from a type perspective, but causes problems later on for e.g.
|
||||
// layout generation, which expects recursion variables to be placed correctly. Attempting to detect
|
||||
// this during layout generation does not work so well because it may be that there *are* recursive
|
||||
// tag unions that should be inlined, and not pass through recursion variables. So instead, resolve
|
||||
// these cases here.
|
||||
//
|
||||
// See tests labeled "issue_2810" for more examples.
|
||||
match (
|
||||
(var1, subs.get_content_unchecked(var1)),
|
||||
(var2, subs.get_content_unchecked(var2)),
|
||||
) {
|
||||
((var, Content::RecursionVar { .. }), _) | (_, (var, Content::RecursionVar { .. })) => var,
|
||||
_ => var1,
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_shared_tags_new<M: MetaCollector>(
|
||||
env: &mut Env,
|
||||
pool: &mut Pool,
|
||||
|
@ -2268,44 +2341,7 @@ fn unify_shared_tags_new<M: MetaCollector>(
|
|||
outcome.union(unify_pool(env, pool, actual, expected, ctx.mode));
|
||||
|
||||
if outcome.mismatches.is_empty() {
|
||||
// If one of the variables is a recursion var, keep that one, so that we avoid inlining
|
||||
// a recursive tag union type content where we should have a recursion var instead.
|
||||
//
|
||||
// When might this happen? For example, in the code
|
||||
//
|
||||
// Indirect : [Indirect ConsList]
|
||||
//
|
||||
// ConsList : [Nil, Cons Indirect]
|
||||
//
|
||||
// l : ConsList
|
||||
// l = Cons (Indirect (Cons (Indirect Nil)))
|
||||
// # ^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~~~^ region-a
|
||||
// # ~~~~~~~~~~~~~~~~~~~~~ region-b
|
||||
// l
|
||||
//
|
||||
// Suppose `ConsList` has the expanded type `[Nil, Cons [Indirect <rec>]] as <rec>`.
|
||||
// After unifying the tag application annotated "region-b" with the recursion variable `<rec>`,
|
||||
// we might have that e.g. `actual` is `<rec>` and `expected` is `[Cons (Indirect ...)]`.
|
||||
//
|
||||
// Now, we need to be careful to set the type we choose to represent the merged type
|
||||
// here to be `<rec>`, not the tag union content of `expected`! Otherwise, we will
|
||||
// have lost a recursion variable in the recursive tag union.
|
||||
//
|
||||
// This would not be incorrect from a type perspective, but causes problems later on for e.g.
|
||||
// layout generation, which expects recursion variables to be placed correctly. Attempting to detect
|
||||
// this during layout generation does not work so well because it may be that there *are* recursive
|
||||
// tag unions that should be inlined, and not pass through recursion variables. So instead, resolve
|
||||
// these cases here.
|
||||
//
|
||||
// See tests labeled "issue_2810" for more examples.
|
||||
let merged_var = match (
|
||||
(actual, env.subs.get_content_unchecked(actual)),
|
||||
(expected, env.subs.get_content_unchecked(expected)),
|
||||
) {
|
||||
((var, Content::RecursionVar { .. }), _)
|
||||
| (_, (var, Content::RecursionVar { .. })) => var,
|
||||
_ => actual,
|
||||
};
|
||||
let merged_var = choose_merged_var(env.subs, actual, expected);
|
||||
|
||||
matching_vars.push(merged_var);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue