Merge remote-tracking branch 'origin/trunk' into complete-num-add

This commit is contained in:
Christoph Rüßler 2022-08-07 19:52:06 +02:00
commit 003408e3ef
No known key found for this signature in database
GPG key ID: 81E62DC325A143CB
147 changed files with 4249 additions and 1589 deletions

View file

@ -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"}

View file

@ -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()?
};

View file

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

View file

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

View file

@ -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(

View file

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

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

View file

@ -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);
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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());

View file

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

View file

@ -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(),
};

View file

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

View file

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

View file

@ -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]

View file

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

View 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)
}

View file

@ -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]);

View file

@ -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
}

View 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!")
}
}
}
}

View file

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

View 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),
}
}
}

View file

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

View file

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

View file

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

View file

@ -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);
}

View file

@ -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>(

View file

@ -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() {

View file

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

View file

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

View file

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

View file

@ -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());
}
}

View file

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

View file

@ -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();

View file

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

View file

@ -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);
}

View file

@ -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
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 }

View file

@ -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(
//

View file

@ -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
}
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)
}
};

View file

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

View file

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

View file

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

View file

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

View 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
"###
)
})
}

View file

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

View file

@ -1,5 +1,7 @@
#![cfg(test)]
mod decoding;
mod encoding;
mod pretty_print;
mod util;

View 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,
);
}

View file

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

View file

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

View file

@ -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
);
}

View file

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

View file

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

View file

@ -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() {

View file

@ -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!(

View file

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

View file

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

View file

@ -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
}
}

View file

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

View file

@ -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");

View file

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

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

View file

@ -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
_ -> ""
"#
)
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
}