mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Merge branch 'trunk' of github.com:rtfeldman/roc into wasm-linking-test-improvements
This commit is contained in:
commit
f1cadbcc05
54 changed files with 3485 additions and 1442 deletions
|
@ -19,6 +19,7 @@ ROC_WORKSPACE_DIR = { value = "", relative = true }
|
|||
# Set = "1" to turn a debug flag on.
|
||||
ROC_PRETTY_PRINT_ALIAS_CONTENTS = "0"
|
||||
ROC_PRINT_UNIFICATIONS = "0"
|
||||
ROC_PRINT_UNIFICATIONS_DERIVED = "0"
|
||||
ROC_PRINT_MISMATCHES = "0"
|
||||
ROC_VERIFY_RIGID_LET_GENERALIZED = "0"
|
||||
ROC_PRINT_IR_AFTER_SPECIALIZATION = "0"
|
||||
|
|
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -3670,6 +3670,18 @@ dependencies = [
|
|||
name = "roc_debug_flags"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "roc_derive_key"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
"roc_types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_docs"
|
||||
version = "0.1.0"
|
||||
|
@ -3951,6 +3963,7 @@ dependencies = [
|
|||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_debug_flags",
|
||||
"roc_derive_key",
|
||||
"roc_error_macros",
|
||||
"roc_exhaustive",
|
||||
"roc_late_solve",
|
||||
|
@ -4797,6 +4810,31 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test_derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
"lazy_static",
|
||||
"pretty_assertions",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_debug_flags",
|
||||
"roc_derive_key",
|
||||
"roc_load_internal",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"ven_pretty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "test_gen"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -15,9 +15,11 @@ members = [
|
|||
"compiler/solve",
|
||||
"compiler/late_solve",
|
||||
"compiler/fmt",
|
||||
"compiler/derive_key",
|
||||
"compiler/mono",
|
||||
"compiler/alias_analysis",
|
||||
"compiler/test_mono",
|
||||
"compiler/test_derive",
|
||||
"compiler/load",
|
||||
"compiler/load_internal",
|
||||
"compiler/gen_llvm",
|
||||
|
|
|
@ -23,7 +23,7 @@ Many programs can however be compiled correctly. Check out [examples](examples)
|
|||
|
||||
Run examples as follows:
|
||||
```
|
||||
cargo run examples/hello-world/helloWorld.roc
|
||||
cargo run examples/hello-world/main.roc
|
||||
```
|
||||
Some examples like `examples/benchmarks/NQueens.roc` require input after running.
|
||||
For NQueens, input 10 in the terminal and press enter.
|
||||
|
|
|
@ -69,6 +69,7 @@ pub fn load_types(
|
|||
unreachable!("Builtin decl in userspace module?")
|
||||
}
|
||||
Declaration::InvalidCycle(..) => Vec::new(),
|
||||
Declaration::Expects(..) => Vec::new(),
|
||||
});
|
||||
|
||||
let vars_iter = defs_iter.filter_map(
|
||||
|
|
|
@ -176,11 +176,15 @@ pub fn build_file<'a>(
|
|||
"Find Specializations",
|
||||
module_timing.find_specializations,
|
||||
);
|
||||
report_timing(
|
||||
buf,
|
||||
"Make Specializations",
|
||||
module_timing.make_specializations,
|
||||
);
|
||||
let multiple_make_specializations_passes = module_timing.make_specializations.len() > 1;
|
||||
for (i, pass_time) in module_timing.make_specializations.iter().enumerate() {
|
||||
let suffix = if multiple_make_specializations_passes {
|
||||
format!(" (Pass {})", i)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
report_timing(buf, &format!("Make Specializations{}", suffix), *pass_time);
|
||||
}
|
||||
report_timing(buf, "Other", module_timing.other());
|
||||
buf.push('\n');
|
||||
report_timing(buf, "Total", module_timing.total());
|
||||
|
@ -460,16 +464,6 @@ pub fn check_file(
|
|||
report_timing(buf, "Canonicalize", module_timing.canonicalize);
|
||||
report_timing(buf, "Constrain", module_timing.constrain);
|
||||
report_timing(buf, "Solve", module_timing.solve);
|
||||
report_timing(
|
||||
buf,
|
||||
"Find Specializations",
|
||||
module_timing.find_specializations,
|
||||
);
|
||||
report_timing(
|
||||
buf,
|
||||
"Make Specializations",
|
||||
module_timing.make_specializations,
|
||||
);
|
||||
report_timing(buf, "Other", module_timing.other());
|
||||
buf.push('\n');
|
||||
report_timing(buf, "Total", module_timing.total());
|
||||
|
|
|
@ -3,7 +3,7 @@ extern crate const_format;
|
|||
|
||||
use build::BuiltFile;
|
||||
use bumpalo::Bump;
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use clap::{Arg, ArgMatches, Command, ValueSource};
|
||||
use roc_build::link::{LinkType, LinkingStrategy};
|
||||
use roc_error_macros::{internal_error, user_error};
|
||||
use roc_load::{LoadingProblem, Threading};
|
||||
|
@ -24,6 +24,8 @@ pub mod build;
|
|||
mod format;
|
||||
pub use format::format;
|
||||
|
||||
const DEFAULT_ROC_FILENAME: &str = "main.roc";
|
||||
|
||||
pub const CMD_BUILD: &str = "build";
|
||||
pub const CMD_RUN: &str = "run";
|
||||
pub const CMD_REPL: &str = "repl";
|
||||
|
@ -57,13 +59,11 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
let flag_optimize = Arg::new(FLAG_OPTIMIZE)
|
||||
.long(FLAG_OPTIMIZE)
|
||||
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
|
||||
.requires(ROC_FILE)
|
||||
.required(false);
|
||||
|
||||
let flag_max_threads = Arg::new(FLAG_MAX_THREADS)
|
||||
.long(FLAG_MAX_THREADS)
|
||||
.help("Limit the number of threads (and hence cores) used during compilation.")
|
||||
.requires(ROC_FILE)
|
||||
.takes_value(true)
|
||||
.validator(|s| s.parse::<usize>())
|
||||
.required(false);
|
||||
|
@ -81,7 +81,6 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
let flag_debug = Arg::new(FLAG_DEBUG)
|
||||
.long(FLAG_DEBUG)
|
||||
.help("Store LLVM debug information in the generated program.")
|
||||
.requires(ROC_FILE)
|
||||
.required(false);
|
||||
|
||||
let flag_valgrind = Arg::new(FLAG_VALGRIND)
|
||||
|
@ -108,13 +107,17 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
|
||||
let roc_file_to_run = Arg::new(ROC_FILE)
|
||||
.help("The .roc file of an app to run")
|
||||
.allow_invalid_utf8(true);
|
||||
.allow_invalid_utf8(true)
|
||||
.required(false)
|
||||
.default_value(DEFAULT_ROC_FILENAME);
|
||||
|
||||
let args_for_app = Arg::new(ARGS_FOR_APP)
|
||||
.help("Arguments to pass into the app being run")
|
||||
.requires(ROC_FILE)
|
||||
.help("Arguments to pass into the app being run, e.g. `roc run -- arg1 arg2`")
|
||||
.allow_invalid_utf8(true)
|
||||
.multiple_values(true);
|
||||
.multiple_values(true)
|
||||
.takes_value(true)
|
||||
.allow_hyphen_values(true)
|
||||
.last(true);
|
||||
|
||||
let app = Command::new("roc")
|
||||
.version(concatcp!(VERSION, "\n"))
|
||||
|
@ -154,7 +157,8 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
Arg::new(ROC_FILE)
|
||||
.help("The .roc file to build")
|
||||
.allow_invalid_utf8(true)
|
||||
.required(true),
|
||||
.required(false)
|
||||
.default_value(DEFAULT_ROC_FILENAME),
|
||||
)
|
||||
)
|
||||
.subcommand(Command::new(CMD_REPL)
|
||||
|
@ -171,7 +175,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.arg(flag_linker.clone())
|
||||
.arg(flag_precompiled.clone())
|
||||
.arg(flag_valgrind.clone())
|
||||
.arg(roc_file_to_run.clone().required(true))
|
||||
.arg(roc_file_to_run.clone())
|
||||
.arg(args_for_app.clone())
|
||||
)
|
||||
.subcommand(Command::new(CMD_FORMAT)
|
||||
|
@ -199,7 +203,8 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
Arg::new(ROC_FILE)
|
||||
.help("The .roc file of an app to check")
|
||||
.allow_invalid_utf8(true)
|
||||
.required(true),
|
||||
.required(false)
|
||||
.default_value(DEFAULT_ROC_FILENAME),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
|
@ -317,9 +322,17 @@ pub fn build(
|
|||
|
||||
match err.kind() {
|
||||
NotFound => {
|
||||
match path.to_str() {
|
||||
Some(path_str) => println!("File not found: {}", path_str),
|
||||
None => println!("Malformed file path : {:?}", path),
|
||||
let path_string = path.to_string_lossy();
|
||||
|
||||
// TODO these should use roc_reporting to display nicer error messages.
|
||||
match matches.value_source(ROC_FILE) {
|
||||
Some(ValueSource::DefaultValue) => {
|
||||
eprintln!(
|
||||
"\nNo `.roc` file was specified, and the current directory does not contain a {} file to use as a default.\n\nYou can run `roc help` for more information on how to provide a .roc file.\n",
|
||||
DEFAULT_ROC_FILENAME
|
||||
)
|
||||
}
|
||||
_ => eprintln!("\nThis file was not found: {}\n\nYou can run `roc help` for more information on how to provide a .roc file.\n", path_string),
|
||||
}
|
||||
|
||||
process::exit(1);
|
||||
|
|
|
@ -94,21 +94,23 @@ mod cli_run {
|
|||
file: &'a Path,
|
||||
args: I,
|
||||
stdin: &[&str],
|
||||
input_file: Option<PathBuf>,
|
||||
opt_input_file: Option<PathBuf>,
|
||||
) -> Out {
|
||||
let compile_out = match input_file {
|
||||
Some(input_file) => run_roc(
|
||||
let compile_out = if let Some(input_file) = opt_input_file {
|
||||
run_roc(
|
||||
// converting these all to String avoids lifetime issues
|
||||
args.into_iter().map(|arg| arg.to_string()).chain([
|
||||
file.to_str().unwrap().to_string(),
|
||||
"--".to_string(),
|
||||
input_file.to_str().unwrap().to_string(),
|
||||
]),
|
||||
stdin,
|
||||
),
|
||||
None => run_roc(
|
||||
)
|
||||
} else {
|
||||
run_roc(
|
||||
args.into_iter().chain(iter::once(file.to_str().unwrap())),
|
||||
stdin,
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
// If there is any stderr, it should be reporting the runtime and that's it!
|
||||
|
@ -131,7 +133,7 @@ mod cli_run {
|
|||
stdin: &[&str],
|
||||
executable_filename: &str,
|
||||
flags: &[&str],
|
||||
input_file: Option<PathBuf>,
|
||||
opt_input_file: Option<PathBuf>,
|
||||
expected_ending: &str,
|
||||
use_valgrind: bool,
|
||||
) {
|
||||
|
@ -151,7 +153,7 @@ mod cli_run {
|
|||
run_roc_on(file, iter::once(CMD_BUILD).chain(flags.clone()), &[], None);
|
||||
|
||||
if use_valgrind && ALLOW_VALGRIND {
|
||||
let (valgrind_out, raw_xml) = if let Some(ref input_file) = input_file {
|
||||
let (valgrind_out, raw_xml) = if let Some(ref input_file) = opt_input_file {
|
||||
run_with_valgrind(
|
||||
stdin.iter().copied(),
|
||||
&[
|
||||
|
@ -201,7 +203,7 @@ mod cli_run {
|
|||
}
|
||||
|
||||
valgrind_out
|
||||
} else if let Some(ref input_file) = input_file {
|
||||
} else if let Some(ref input_file) = opt_input_file {
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
stdin.iter().copied(),
|
||||
|
@ -215,12 +217,12 @@ mod cli_run {
|
|||
)
|
||||
}
|
||||
}
|
||||
CliMode::Roc => run_roc_on(file, flags.clone(), stdin, input_file.clone()),
|
||||
CliMode::Roc => run_roc_on(file, flags.clone(), stdin, opt_input_file.clone()),
|
||||
CliMode::RocRun => run_roc_on(
|
||||
file,
|
||||
iter::once(CMD_RUN).chain(flags.clone()),
|
||||
stdin,
|
||||
input_file.clone(),
|
||||
opt_input_file.clone(),
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -382,8 +384,8 @@ mod cli_run {
|
|||
// ]
|
||||
examples! {
|
||||
helloWorld:"hello-world" => Example {
|
||||
filename: "helloWorld.roc",
|
||||
executable_filename: "helloWorld",
|
||||
filename: "main.roc",
|
||||
executable_filename: "hello",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
expected_ending:"Hello, World!\n",
|
||||
|
|
|
@ -21,6 +21,8 @@ interface Encode
|
|||
bool,
|
||||
string,
|
||||
list,
|
||||
record,
|
||||
tag,
|
||||
custom,
|
||||
appendWith,
|
||||
append,
|
||||
|
@ -51,6 +53,8 @@ EncoderFormatting has
|
|||
bool : Bool -> Encoder fmt | fmt has EncoderFormatting
|
||||
string : Str -> Encoder fmt | fmt has EncoderFormatting
|
||||
list : List elem, (elem -> Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting
|
||||
record : List { key : Str, value : Encoder fmt } -> Encoder fmt | fmt has EncoderFormatting
|
||||
tag : Str, List (Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting
|
||||
|
||||
custom : (List U8, fmt -> List U8) -> Encoder fmt | fmt has EncoderFormatting
|
||||
custom = \encoder -> @Encoder encoder
|
||||
|
|
|
@ -7,6 +7,7 @@ interface Json
|
|||
imports
|
||||
[
|
||||
Encode.{
|
||||
Encoder,
|
||||
custom,
|
||||
appendWith,
|
||||
u8,
|
||||
|
@ -25,6 +26,8 @@ interface Json
|
|||
bool,
|
||||
string,
|
||||
list,
|
||||
record,
|
||||
tag,
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -81,3 +84,52 @@ list = \lst, encodeElem ->
|
|||
withList = List.walk lst head (\bytes1, elem -> appendWith bytes1 (encodeElem elem) (@Json {}))
|
||||
|
||||
List.append withList (Num.toU8 ']')
|
||||
|
||||
record = \fields ->
|
||||
custom \bytes, @Json {} ->
|
||||
writeRecord = \{ buffer, fieldsLeft }, { key, value } ->
|
||||
bufferWithKeyValue =
|
||||
List.append buffer (Num.toU8 '"')
|
||||
|> List.concat (Str.toUtf8 key)
|
||||
|> List.append (Num.toU8 '"')
|
||||
|> List.append (Num.toU8 ':')
|
||||
|> appendWith value (@Json {})
|
||||
|
||||
bufferWithSuffix =
|
||||
if fieldsLeft > 0 then
|
||||
List.append bufferWithKeyValue (Num.toU8 ',')
|
||||
else
|
||||
bufferWithKeyValue
|
||||
|
||||
{ buffer: bufferWithSuffix, fieldsLeft: fieldsLeft - 1 }
|
||||
|
||||
bytesHead = List.append bytes (Num.toU8 '{')
|
||||
{ buffer: bytesWithRecord } = List.walk fields { buffer: bytesHead, fieldsLeft: List.len fields } writeRecord
|
||||
|
||||
List.append bytesWithRecord (Num.toU8 '}')
|
||||
|
||||
tag = \name, payload ->
|
||||
custom \bytes, @Json {} ->
|
||||
# Idea: encode `A v1 v2` as `{"A": [v1, v2]}`
|
||||
writePayload = \{ buffer, itemsLeft }, encoder ->
|
||||
bufferWithValue = appendWith buffer encoder (@Json {})
|
||||
bufferWithSuffix =
|
||||
if itemsLeft > 0 then
|
||||
List.append bufferWithValue (Num.toU8 ',')
|
||||
else
|
||||
bufferWithValue
|
||||
|
||||
{ buffer: bufferWithSuffix, itemsLeft: itemsLeft - 1 }
|
||||
|
||||
bytesHead =
|
||||
List.append bytes (Num.toU8 '{')
|
||||
|> List.append (Num.toU8 '"')
|
||||
|> List.concat (Str.toUtf8 name)
|
||||
|> List.append (Num.toU8 '"')
|
||||
|> List.append (Num.toU8 ':')
|
||||
|> List.append (Num.toU8 '[')
|
||||
|
||||
{ buffer: bytesWithPayload } = List.walk payload { buffer: bytesHead, itemsLeft: List.len payload } writePayload
|
||||
|
||||
List.append bytesWithPayload (Num.toU8 ']')
|
||||
|> List.append (Num.toU8 '}')
|
||||
|
|
|
@ -220,6 +220,73 @@ impl<Phase: ResolvePhase> IAbilitiesStore<Phase> {
|
|||
self.next_specialization_id += 1;
|
||||
id
|
||||
}
|
||||
|
||||
/// Creates a store from [`self`] that closes over the abilities/members given by the
|
||||
/// imported `symbols`, and their specializations (if any).
|
||||
pub fn closure_from_imported(&self, symbols: &VecSet<Symbol>) -> PendingAbilitiesStore {
|
||||
let Self {
|
||||
members_of_ability,
|
||||
ability_members,
|
||||
declared_specializations,
|
||||
|
||||
// Covered by `declared_specializations`
|
||||
specialization_to_root: _,
|
||||
|
||||
// Taking closure for a new module, so specialization IDs can be fresh
|
||||
next_specialization_id: _,
|
||||
resolved_specializations: _,
|
||||
} = self;
|
||||
|
||||
let mut new = PendingAbilitiesStore::default();
|
||||
|
||||
// 1. Figure out the abilities we need to introduce.
|
||||
let mut abilities_to_introduce = VecSet::with_capacity(2);
|
||||
symbols.iter().for_each(|symbol| {
|
||||
if let Some(member_data) = ability_members.get(symbol) {
|
||||
// If the symbol is member of an ability, we need to capture the entire ability.
|
||||
abilities_to_introduce.insert(member_data.parent_ability);
|
||||
}
|
||||
if members_of_ability.contains_key(symbol) {
|
||||
abilities_to_introduce.insert(*symbol);
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Add each ability, and any specializations of its members we know about.
|
||||
for ability in abilities_to_introduce.into_iter() {
|
||||
let members = members_of_ability.get(&ability).unwrap();
|
||||
let mut imported_member_data = Vec::with_capacity(members.len());
|
||||
for member in members {
|
||||
let AbilityMemberData {
|
||||
parent_ability,
|
||||
region,
|
||||
typ: _,
|
||||
} = ability_members.get(member).unwrap().clone();
|
||||
// All external members need to be marked as imported. We'll figure out their real
|
||||
// type variables when it comes time to solve the module we're currently importing
|
||||
// into.
|
||||
let imported_data = AbilityMemberData {
|
||||
parent_ability,
|
||||
region,
|
||||
typ: PendingMemberType::Imported,
|
||||
};
|
||||
|
||||
imported_member_data.push((*member, imported_data));
|
||||
}
|
||||
|
||||
new.register_ability(ability, imported_member_data);
|
||||
|
||||
// Add any specializations of the ability's members we know about.
|
||||
declared_specializations
|
||||
.iter()
|
||||
.filter(|((member, _), _)| members.contains(member))
|
||||
.for_each(|(&(member, typ), specialization)| {
|
||||
new.register_specializing_symbol(specialization.symbol, member);
|
||||
new.import_specialization(member, typ, specialization);
|
||||
});
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
impl IAbilitiesStore<Resolved> {
|
||||
|
@ -326,73 +393,6 @@ impl IAbilitiesStore<Pending> {
|
|||
debug_assert!(old_spec.is_none(), "Replacing existing specialization");
|
||||
}
|
||||
|
||||
/// Creates a store from [`self`] that closes over the abilities/members given by the
|
||||
/// imported `symbols`, and their specializations (if any).
|
||||
pub fn closure_from_imported(&self, symbols: &VecSet<Symbol>) -> Self {
|
||||
let Self {
|
||||
members_of_ability,
|
||||
ability_members,
|
||||
declared_specializations,
|
||||
|
||||
// Covered by `declared_specializations`
|
||||
specialization_to_root: _,
|
||||
|
||||
// Taking closure for a new module, so specialization IDs can be fresh
|
||||
next_specialization_id: _,
|
||||
resolved_specializations: _,
|
||||
} = self;
|
||||
|
||||
let mut new = PendingAbilitiesStore::default();
|
||||
|
||||
// 1. Figure out the abilities we need to introduce.
|
||||
let mut abilities_to_introduce = VecSet::with_capacity(2);
|
||||
symbols.iter().for_each(|symbol| {
|
||||
if let Some(member_data) = ability_members.get(symbol) {
|
||||
// If the symbol is member of an ability, we need to capture the entire ability.
|
||||
abilities_to_introduce.insert(member_data.parent_ability);
|
||||
}
|
||||
if members_of_ability.contains_key(symbol) {
|
||||
abilities_to_introduce.insert(*symbol);
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Add each ability, and any specializations of its members we know about.
|
||||
for ability in abilities_to_introduce.into_iter() {
|
||||
let members = members_of_ability.get(&ability).unwrap();
|
||||
let mut imported_member_data = Vec::with_capacity(members.len());
|
||||
for member in members {
|
||||
let AbilityMemberData {
|
||||
parent_ability,
|
||||
region,
|
||||
typ: _,
|
||||
} = ability_members.get(member).unwrap().clone();
|
||||
// All external members need to be marked as imported. We'll figure out their real
|
||||
// type variables when it comes time to solve the module we're currently importing
|
||||
// into.
|
||||
let imported_data = AbilityMemberData {
|
||||
parent_ability,
|
||||
region,
|
||||
typ: PendingMemberType::Imported,
|
||||
};
|
||||
|
||||
imported_member_data.push((*member, imported_data));
|
||||
}
|
||||
|
||||
new.register_ability(ability, imported_member_data);
|
||||
|
||||
// Add any specializations of the ability's members we know about.
|
||||
declared_specializations
|
||||
.iter()
|
||||
.filter(|((member, _), _)| members.contains(member))
|
||||
.for_each(|(&(member, typ), specialization)| {
|
||||
new.register_specializing_symbol(specialization.symbol, member);
|
||||
new.import_specialization(member, typ, specialization);
|
||||
});
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
pub fn union(&mut self, other: Self) {
|
||||
let Self {
|
||||
members_of_ability: other_members_of_ability,
|
||||
|
|
|
@ -79,10 +79,28 @@ pub struct Annotation {
|
|||
#[derive(Debug)]
|
||||
pub(crate) struct CanDefs {
|
||||
defs: Vec<Option<Def>>,
|
||||
expects: Expects,
|
||||
def_ordering: DefOrdering,
|
||||
aliases: VecMap<Symbol, Alias>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Expects {
|
||||
pub conditions: Vec<Expr>,
|
||||
pub regions: Vec<Region>,
|
||||
pub preceding_comment: Vec<Region>,
|
||||
}
|
||||
|
||||
impl Expects {
|
||||
fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
conditions: Vec::with_capacity(capacity),
|
||||
regions: Vec::with_capacity(capacity),
|
||||
preceding_comment: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Def that has had patterns and type annnotations canonicalized,
|
||||
/// but no Expr canonicalization has happened yet. Also, it has had spaces
|
||||
/// and nesting resolved, and knows whether annotations are standalone or not.
|
||||
|
@ -201,6 +219,7 @@ pub enum Declaration {
|
|||
Declare(Def),
|
||||
DeclareRec(Vec<Def>, IllegalCycleMark),
|
||||
Builtin(Def),
|
||||
Expects(Expects),
|
||||
/// If we know a cycle is illegal during canonicalization.
|
||||
/// Otherwise we will try to detect this during solving; see [`IllegalCycleMark`].
|
||||
InvalidCycle(Vec<CycleEntry>),
|
||||
|
@ -214,6 +233,7 @@ impl Declaration {
|
|||
DeclareRec(defs, _) => defs.len(),
|
||||
InvalidCycle { .. } => 0,
|
||||
Builtin(_) => 0,
|
||||
Expects(_) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,6 +249,10 @@ impl Declaration {
|
|||
&cycles.first().unwrap().expr_region,
|
||||
&cycles.last().unwrap().expr_region,
|
||||
),
|
||||
Declaration::Expects(expects) => Region::span_across(
|
||||
expects.regions.first().unwrap(),
|
||||
expects.regions.last().unwrap(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -541,18 +565,21 @@ fn canonicalize_value_defs<'a>(
|
|||
// the ast::Expr values in pending_exprs for further canonicalization
|
||||
// once we've finished assembling the entire scope.
|
||||
let mut pending_value_defs = Vec::with_capacity(value_defs.len());
|
||||
let mut pending_expects = Vec::with_capacity(value_defs.len());
|
||||
|
||||
for loc_def in value_defs {
|
||||
let mut new_output = Output::default();
|
||||
match to_pending_value_def(
|
||||
let pending = to_pending_value_def(
|
||||
env,
|
||||
var_store,
|
||||
loc_def.value,
|
||||
scope,
|
||||
&mut new_output,
|
||||
pattern_type,
|
||||
) {
|
||||
None => { /* skip */ }
|
||||
Some(pending_def) => {
|
||||
);
|
||||
|
||||
match pending {
|
||||
PendingValue::Def(pending_def) => {
|
||||
// Record the ast::Expr for later. We'll do another pass through these
|
||||
// once we have the entire scope assembled. If we were to canonicalize
|
||||
// the exprs right now, they wouldn't have symbols in scope from defs
|
||||
|
@ -560,6 +587,10 @@ fn canonicalize_value_defs<'a>(
|
|||
pending_value_defs.push(pending_def);
|
||||
output.union(new_output);
|
||||
}
|
||||
PendingValue::SignatureDefMismatch => { /* skip */ }
|
||||
PendingValue::Expect(pending_expect) => {
|
||||
pending_expects.push(pending_expect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -607,8 +638,27 @@ fn canonicalize_value_defs<'a>(
|
|||
def_ordering.insert_symbol_references(def_id as u32, &temp_output.references)
|
||||
}
|
||||
|
||||
let mut expects = Expects::with_capacity(pending_expects.len());
|
||||
|
||||
for pending in pending_expects {
|
||||
let (loc_can_condition, can_output) = canonicalize_expr(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
pending.condition.region,
|
||||
&pending.condition.value,
|
||||
);
|
||||
|
||||
expects.conditions.push(loc_can_condition.value);
|
||||
expects.regions.push(loc_can_condition.region);
|
||||
expects.preceding_comment.push(pending.preceding_comment);
|
||||
|
||||
output.union(can_output);
|
||||
}
|
||||
|
||||
let can_defs = CanDefs {
|
||||
defs,
|
||||
expects,
|
||||
def_ordering,
|
||||
aliases,
|
||||
};
|
||||
|
@ -1009,6 +1059,7 @@ pub(crate) fn sort_can_defs(
|
|||
) -> (Vec<Declaration>, Output) {
|
||||
let CanDefs {
|
||||
mut defs,
|
||||
expects,
|
||||
def_ordering,
|
||||
aliases,
|
||||
} = defs;
|
||||
|
@ -1119,6 +1170,10 @@ pub(crate) fn sort_can_defs(
|
|||
}
|
||||
}
|
||||
|
||||
if !expects.conditions.is_empty() {
|
||||
declarations.push(Declaration::Expects(expects));
|
||||
}
|
||||
|
||||
(declarations, output)
|
||||
}
|
||||
|
||||
|
@ -1574,6 +1629,10 @@ fn decl_to_let(decl: Declaration, loc_ret: Loc<Expr>) -> Loc<Expr> {
|
|||
// Builtins should only be added to top-level decls, not to let-exprs!
|
||||
unreachable!()
|
||||
}
|
||||
Declaration::Expects(expects) => {
|
||||
// Expects should only be added to top-level decls, not to let-exprs!
|
||||
unreachable!("{:?}", &expects)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1739,6 +1798,17 @@ fn to_pending_type_def<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
enum PendingValue<'a> {
|
||||
Def(PendingValueDef<'a>),
|
||||
Expect(PendingExpect<'a>),
|
||||
SignatureDefMismatch,
|
||||
}
|
||||
|
||||
struct PendingExpect<'a> {
|
||||
condition: &'a Loc<ast::Expr<'a>>,
|
||||
preceding_comment: Region,
|
||||
}
|
||||
|
||||
fn to_pending_value_def<'a>(
|
||||
env: &mut Env<'a>,
|
||||
var_store: &mut VarStore,
|
||||
|
@ -1746,7 +1816,7 @@ fn to_pending_value_def<'a>(
|
|||
scope: &mut Scope,
|
||||
output: &mut Output,
|
||||
pattern_type: PatternType,
|
||||
) -> Option<PendingValueDef<'a>> {
|
||||
) -> PendingValue<'a> {
|
||||
use ast::ValueDef::*;
|
||||
|
||||
match def {
|
||||
|
@ -1762,7 +1832,7 @@ fn to_pending_value_def<'a>(
|
|||
loc_pattern.region,
|
||||
);
|
||||
|
||||
Some(PendingValueDef::AnnotationOnly(
|
||||
PendingValue::Def(PendingValueDef::AnnotationOnly(
|
||||
loc_pattern,
|
||||
loc_can_pattern,
|
||||
loc_ann,
|
||||
|
@ -1780,7 +1850,7 @@ fn to_pending_value_def<'a>(
|
|||
loc_pattern.region,
|
||||
);
|
||||
|
||||
Some(PendingValueDef::Body(
|
||||
PendingValue::Def(PendingValueDef::Body(
|
||||
loc_pattern,
|
||||
loc_can_pattern,
|
||||
loc_expr,
|
||||
|
@ -1812,7 +1882,7 @@ fn to_pending_value_def<'a>(
|
|||
body_pattern.region,
|
||||
);
|
||||
|
||||
Some(PendingValueDef::TypedBody(
|
||||
PendingValue::Def(PendingValueDef::TypedBody(
|
||||
body_pattern,
|
||||
loc_can_pattern,
|
||||
ann_type,
|
||||
|
@ -1828,11 +1898,14 @@ fn to_pending_value_def<'a>(
|
|||
// TODO: Should we instead build some PendingValueDef::InvalidAnnotatedBody ? This would
|
||||
// remove the `Option` on this function (and be probably more reliable for further
|
||||
// problem/error reporting)
|
||||
None
|
||||
PendingValue::SignatureDefMismatch
|
||||
}
|
||||
}
|
||||
|
||||
Expect(_condition) => todo!(),
|
||||
Expect(condition) => PendingValue::Expect(PendingExpect {
|
||||
condition,
|
||||
preceding_comment: Region::zero(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -493,6 +493,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
.iter()
|
||||
.all(|(symbol, _)| !exposed_but_not_defined.contains(symbol)));
|
||||
}
|
||||
Expects(_) => { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -574,7 +575,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
DeclareRec(defs, _) => {
|
||||
fix_values_captured_in_closure_defs(defs, &mut VecSet::default())
|
||||
}
|
||||
InvalidCycle(_) | Builtin(_) => {}
|
||||
InvalidCycle(_) | Builtin(_) | Expects(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,12 @@ pub fn walk_decl<V: Visitor>(visitor: &mut V, decl: &Declaration) {
|
|||
Declaration::DeclareRec(defs, _cycle_mark) => {
|
||||
visit_list!(visitor, visit_def, defs)
|
||||
}
|
||||
Declaration::Expects(expects) => {
|
||||
let it = expects.regions.iter().zip(expects.conditions.iter());
|
||||
for (region, condition) in it {
|
||||
visitor.visit_expr(condition, *region, Variable::BOOL);
|
||||
}
|
||||
}
|
||||
Declaration::Builtin(def) => visitor.visit_def(def),
|
||||
Declaration::InvalidCycle(_cycles) => {
|
||||
// ignore
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::builtins::{
|
|||
use crate::pattern::{constrain_pattern, PatternState};
|
||||
use roc_can::annotation::IntroducedVariables;
|
||||
use roc_can::constraint::{Constraint, Constraints, OpportunisticResolve};
|
||||
use roc_can::def::{Declaration, Def};
|
||||
use roc_can::def::{Declaration, Def, Expects};
|
||||
use roc_can::exhaustive::{sketch_pattern_to_rows, sketch_when_branches, ExhaustiveContext};
|
||||
use roc_can::expected::Expected::{self, *};
|
||||
use roc_can::expected::PExpected;
|
||||
|
@ -801,12 +801,7 @@ pub fn constrain_expr(
|
|||
let branch_constraints = constraints.and_constraint(total_cons);
|
||||
|
||||
constraints.exists(
|
||||
[
|
||||
exhaustive.variable_for_introduction(),
|
||||
branches_cond_var,
|
||||
real_cond_var,
|
||||
*expr_var,
|
||||
],
|
||||
[branches_cond_var, real_cond_var, *expr_var],
|
||||
branch_constraints,
|
||||
)
|
||||
}
|
||||
|
@ -1309,6 +1304,9 @@ pub fn constrain_decls(
|
|||
constraint =
|
||||
constrain_recursive_defs(constraints, &mut env, defs, constraint, *cycle_mark);
|
||||
}
|
||||
Declaration::Expects(expects) => {
|
||||
constraint = constrain_expects(constraints, &mut env, expects, constraint);
|
||||
}
|
||||
Declaration::InvalidCycle(_) => {
|
||||
// invalid cycles give a canonicalization error. we skip them here.
|
||||
continue;
|
||||
|
@ -1705,6 +1703,35 @@ fn attach_resolution_constraints(
|
|||
constraints.and_constraint([constraint, resolution_constrs])
|
||||
}
|
||||
|
||||
fn constrain_expects(
|
||||
constraints: &mut Constraints,
|
||||
env: &mut Env,
|
||||
expects: &Expects,
|
||||
body_con: Constraint,
|
||||
) -> Constraint {
|
||||
let expect_bool = |region| {
|
||||
let bool_type = Type::Variable(Variable::BOOL);
|
||||
Expected::ForReason(Reason::ExpectCondition, bool_type, region)
|
||||
};
|
||||
|
||||
let mut expect_constraints = Vec::with_capacity(expects.conditions.len());
|
||||
|
||||
let it = expects.regions.iter().zip(expects.conditions.iter());
|
||||
for (region, condition) in it {
|
||||
let expected = expect_bool(*region);
|
||||
expect_constraints.push(constrain_expr(
|
||||
constraints,
|
||||
env,
|
||||
*region,
|
||||
condition,
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
let defs_constraint = constraints.and_constraint(expect_constraints);
|
||||
constraints.let_constraint([], [], [], defs_constraint, body_con)
|
||||
}
|
||||
|
||||
fn constrain_def(
|
||||
constraints: &mut Constraints,
|
||||
env: &mut Env,
|
||||
|
|
|
@ -62,8 +62,13 @@ flags! {
|
|||
// ===Solve===
|
||||
|
||||
/// Prints type unifications, before and after they happen.
|
||||
/// Only use this in single-threaded mode!
|
||||
ROC_PRINT_UNIFICATIONS
|
||||
|
||||
/// Like ROC_PRINT_UNIFICATIONS, in the context of typechecking derived implementations.
|
||||
/// Only use this in single-threaded mode!
|
||||
ROC_PRINT_UNIFICATIONS_DERIVED
|
||||
|
||||
/// Prints all type mismatches hit during type unification.
|
||||
ROC_PRINT_MISMATCHES
|
||||
|
||||
|
|
14
compiler/derive_key/Cargo.toml
Normal file
14
compiler/derive_key/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "roc_derive_key"
|
||||
version = "0.1.0"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_can = { path = "../can" }
|
120
compiler/derive_key/src/encoding.rs
Normal file
120
compiler/derive_key/src/encoding.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
use roc_error_macros::internal_error;
|
||||
use roc_module::{
|
||||
ident::{Lowercase, TagName},
|
||||
symbol::Symbol,
|
||||
};
|
||||
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, SubsFmtContent, Variable};
|
||||
|
||||
#[derive(Hash)]
|
||||
pub enum FlatEncodable<'a> {
|
||||
Immediate(Symbol),
|
||||
Key(FlatEncodableKey<'a>),
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug)]
|
||||
pub enum FlatEncodableKey<'a> {
|
||||
List(/* takes one variable */),
|
||||
Set(/* takes one variable */),
|
||||
Dict(/* takes two variables */),
|
||||
// Unfortunate that we must allocate here, c'est la vie
|
||||
Record(Vec<&'a Lowercase>),
|
||||
TagUnion(Vec<(&'a TagName, u16)>),
|
||||
}
|
||||
|
||||
macro_rules! unexpected {
|
||||
($subs:expr, $var:expr) => {
|
||||
internal_error!(
|
||||
"Invalid content for toEncoder: {:?}",
|
||||
SubsFmtContent($subs.get_content_without_compacting($var), $subs)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
impl FlatEncodable<'_> {
|
||||
pub(crate) fn from_var(subs: &Subs, var: Variable) -> FlatEncodable {
|
||||
use FlatEncodable::*;
|
||||
match *subs.get_content_without_compacting(var) {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
FlatType::Apply(sym, _) => match sym {
|
||||
Symbol::LIST_LIST => Key(FlatEncodableKey::List()),
|
||||
Symbol::SET_SET => Key(FlatEncodableKey::Set()),
|
||||
Symbol::DICT_DICT => Key(FlatEncodableKey::Dict()),
|
||||
Symbol::STR_STR => Immediate(Symbol::ENCODE_STRING),
|
||||
_ => unexpected!(subs, var),
|
||||
},
|
||||
FlatType::Record(fields, ext) => {
|
||||
debug_assert!(matches!(
|
||||
subs.get_content_without_compacting(ext),
|
||||
Content::Structure(FlatType::EmptyRecord)
|
||||
));
|
||||
|
||||
let mut field_names: Vec<_> =
|
||||
subs.get_subs_slice(fields.field_names()).iter().collect();
|
||||
field_names.sort();
|
||||
Key(FlatEncodableKey::Record(field_names))
|
||||
}
|
||||
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
|
||||
// The recursion var doesn't matter, because the derived implementation will only
|
||||
// look on the surface of the tag union type, and more over the payloads of the
|
||||
// arguments will be left generic for the monomorphizer to fill in with the
|
||||
// appropriate type. That is,
|
||||
// [ A t1, B t1 t2 ]
|
||||
// and
|
||||
// [ A t1, B t1 t2 ] as R
|
||||
// look the same on the surface, because `R` is only somewhere inside of the
|
||||
// `t`-prefixed payload types.
|
||||
debug_assert!(matches!(
|
||||
subs.get_content_without_compacting(ext),
|
||||
Content::Structure(FlatType::EmptyTagUnion)
|
||||
));
|
||||
let mut tag_names_and_payload_sizes: Vec<_> = tags
|
||||
.iter_all()
|
||||
.map(|(name_index, payload_slice_index)| {
|
||||
let payload_slice = subs[payload_slice_index];
|
||||
let payload_size = payload_slice.length;
|
||||
let name = &subs[name_index];
|
||||
(name, payload_size)
|
||||
})
|
||||
.collect();
|
||||
tag_names_and_payload_sizes.sort_by_key(|t| t.0);
|
||||
Key(FlatEncodableKey::TagUnion(tag_names_and_payload_sizes))
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(name_index, _, _) => {
|
||||
Key(FlatEncodableKey::TagUnion(vec![(&subs[name_index], 0)]))
|
||||
}
|
||||
FlatType::EmptyRecord => Key(FlatEncodableKey::Record(vec![])),
|
||||
FlatType::EmptyTagUnion => Key(FlatEncodableKey::TagUnion(vec![])),
|
||||
//
|
||||
FlatType::Erroneous(_) => unexpected!(subs, var),
|
||||
FlatType::Func(..) => unexpected!(subs, var),
|
||||
},
|
||||
Content::Alias(sym, _, real_var, _) => match sym {
|
||||
Symbol::NUM_U8 => Immediate(Symbol::ENCODE_U8),
|
||||
Symbol::NUM_U16 => Immediate(Symbol::ENCODE_U16),
|
||||
Symbol::NUM_U32 => Immediate(Symbol::ENCODE_U32),
|
||||
Symbol::NUM_U64 => Immediate(Symbol::ENCODE_U64),
|
||||
Symbol::NUM_U128 => Immediate(Symbol::ENCODE_U128),
|
||||
Symbol::NUM_I8 => Immediate(Symbol::ENCODE_I8),
|
||||
Symbol::NUM_I16 => Immediate(Symbol::ENCODE_I16),
|
||||
Symbol::NUM_I32 => Immediate(Symbol::ENCODE_I32),
|
||||
Symbol::NUM_I64 => Immediate(Symbol::ENCODE_I64),
|
||||
Symbol::NUM_I128 => Immediate(Symbol::ENCODE_I128),
|
||||
Symbol::NUM_DEC => Immediate(Symbol::ENCODE_DEC),
|
||||
Symbol::NUM_F32 => Immediate(Symbol::ENCODE_F32),
|
||||
Symbol::NUM_F64 => Immediate(Symbol::ENCODE_F64),
|
||||
// TODO: 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(real_var, _) => Self::from_var(subs, real_var),
|
||||
//
|
||||
Content::RecursionVar { .. } => unexpected!(subs, var),
|
||||
Content::Error => unexpected!(subs, var),
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => unexpected!(subs, var),
|
||||
Content::LambdaSet(_) => unexpected!(subs, var),
|
||||
}
|
||||
}
|
||||
}
|
63
compiler/derive_key/src/lib.rs
Normal file
63
compiler/derive_key/src/lib.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
//! To avoid duplicating derived implementations for the same type, derived implementations are
|
||||
//! addressed by a key of their type content. However, different derived implementations can be
|
||||
//! reused based on different properties of the type. For example:
|
||||
//!
|
||||
//! - `Eq` does not care about surface type representations; its derived implementations can be
|
||||
//! uniquely addressed by the [`Layout`][crate::layout::Layout] of a type.
|
||||
//! - `Encoding` must care about surface type representations; for example, `{ a: "" }` and
|
||||
//! `{ b: "" }` have different derived implementations. However, it does not need to distinguish
|
||||
//! between e.g. required and optional record fields.
|
||||
//! - `Decoding` is like encoding, but has some differences. For one, it *does* need to distinguish
|
||||
//! between required and optional record fields.
|
||||
//!
|
||||
//! For these reasons the content keying is based on a [`Strategy`] as well.
|
||||
|
||||
pub mod encoding;
|
||||
|
||||
use encoding::{FlatEncodable, FlatEncodableKey};
|
||||
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::{Subs, Variable};
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug)]
|
||||
#[repr(u8)]
|
||||
enum Strategy {
|
||||
Encoding,
|
||||
#[allow(unused)]
|
||||
Decoding,
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug)]
|
||||
pub enum Derived<R>
|
||||
where
|
||||
R: std::hash::Hash + PartialEq + Eq + std::fmt::Debug,
|
||||
{
|
||||
/// If a derived implementation name is well-known ahead-of-time, we can inline the symbol
|
||||
/// directly rather than associating a key for an implementation to be made later on.
|
||||
Immediate(Symbol),
|
||||
/// Key of the derived implementation to use. This allows association of derived implementation
|
||||
/// names to a key, when the key is known ahead-of-time but the implementation (and it's name)
|
||||
/// is yet-to-be-made.
|
||||
Key(DeriveKey<R>),
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Debug)]
|
||||
pub struct DeriveKey<R>
|
||||
where
|
||||
R: std::hash::Hash + PartialEq + Eq + std::fmt::Debug,
|
||||
{
|
||||
strategy: Strategy,
|
||||
pub repr: R,
|
||||
}
|
||||
|
||||
impl<'a> Derived<FlatEncodableKey<'a>> {
|
||||
pub fn encoding(subs: &'a Subs, var: Variable) -> Self {
|
||||
match encoding::FlatEncodable::from_var(subs, var) {
|
||||
FlatEncodable::Immediate(imm) => Derived::Immediate(imm),
|
||||
FlatEncodable::Key(repr) => Derived::Key(DeriveKey {
|
||||
strategy: Strategy::Encoding,
|
||||
repr,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -330,7 +330,9 @@ fn build_proc_symbol<'a, B: Backend<'a>>(
|
|||
let base_name = backend.symbol_to_string(sym, layout_id);
|
||||
|
||||
let fn_name = if backend.env().exposed_to_host.contains(&sym) {
|
||||
format!("roc_{}_exposed", base_name)
|
||||
layout_ids
|
||||
.get_toplevel(sym, &layout)
|
||||
.to_exposed_symbol_string(sym, backend.interns())
|
||||
} else {
|
||||
base_name
|
||||
};
|
||||
|
|
|
@ -3296,12 +3296,20 @@ fn expose_function_to_host<'a, 'ctx, 'env>(
|
|||
env: &Env<'a, 'ctx, 'env>,
|
||||
symbol: Symbol,
|
||||
roc_function: FunctionValue<'ctx>,
|
||||
arguments: &[Layout<'a>],
|
||||
arguments: &'a [Layout<'a>],
|
||||
return_layout: Layout<'a>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
) {
|
||||
// Assumption: there is only one specialization of a host-exposed function
|
||||
let ident_string = symbol.as_str(&env.interns);
|
||||
let c_function_name: String = format!("roc__{}_1_exposed", ident_string);
|
||||
|
||||
let proc_layout = ProcLayout {
|
||||
arguments,
|
||||
result: return_layout,
|
||||
};
|
||||
|
||||
let c_function_name: String = layout_ids
|
||||
.get_toplevel(symbol, &proc_layout)
|
||||
.to_exposed_symbol_string(symbol, &env.interns);
|
||||
|
||||
expose_function_to_host_help_c_abi(
|
||||
env,
|
||||
|
@ -4077,6 +4085,7 @@ pub fn build_proc_headers<'a, 'ctx, 'env>(
|
|||
mod_solutions: &'a ModSolutions,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
|
||||
scope: &mut Scope<'a, 'ctx>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
// alias_analysis_solutions: AliasAnalysisSolutions,
|
||||
) -> Vec<
|
||||
'a,
|
||||
|
@ -4096,7 +4105,7 @@ pub fn build_proc_headers<'a, 'ctx, 'env>(
|
|||
let it = func_solutions.specs();
|
||||
let mut function_values = Vec::with_capacity_in(it.size_hint().0, env.arena);
|
||||
for specialization in it {
|
||||
let fn_val = build_proc_header(env, *specialization, symbol, &proc);
|
||||
let fn_val = build_proc_header(env, *specialization, symbol, &proc, layout_ids);
|
||||
|
||||
if proc.args.is_empty() {
|
||||
// this is a 0-argument thunk, i.e. a top-level constant definition
|
||||
|
@ -4167,7 +4176,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
|
|||
// Add all the Proc headers to the module.
|
||||
// We have to do this in a separate pass first,
|
||||
// because their bodies may reference each other.
|
||||
let headers = build_proc_headers(env, mod_solutions, procedures, &mut scope);
|
||||
let headers = build_proc_headers(env, mod_solutions, procedures, &mut scope, &mut layout_ids);
|
||||
|
||||
let (_, function_pass) = construct_optimization_passes(env.module, opt_level);
|
||||
|
||||
|
@ -4256,6 +4265,7 @@ fn build_proc_header<'a, 'ctx, 'env>(
|
|||
func_spec: FuncSpec,
|
||||
symbol: Symbol,
|
||||
proc: &roc_mono::ir::Proc<'a>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
) -> FunctionValue<'ctx> {
|
||||
let args = proc.args;
|
||||
let arena = env.arena;
|
||||
|
@ -4293,6 +4303,7 @@ fn build_proc_header<'a, 'ctx, 'env>(
|
|||
fn_val,
|
||||
arguments.into_bump_slice(),
|
||||
proc.ret_layout,
|
||||
layout_ids,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -252,6 +252,8 @@ impl<'a> WasmBackend<'a> {
|
|||
.to_symbol_string(symbol, self.interns);
|
||||
let name = String::from_str_in(&name, self.env.arena).into_bump_str();
|
||||
|
||||
// dbg!(name);
|
||||
|
||||
self.proc_lookup.push(ProcLookupData {
|
||||
name: symbol,
|
||||
layout,
|
||||
|
|
|
@ -85,7 +85,7 @@ pub fn build_app_module<'a>(
|
|||
host_module: WasmModule<'a>,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> (WasmModule<'a>, BitVec<usize>, u32) {
|
||||
let layout_ids = LayoutIds::default();
|
||||
let mut layout_ids = LayoutIds::default();
|
||||
let mut procs = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut proc_lookup = Vec::with_capacity_in(procedures.len() * 2, env.arena);
|
||||
let mut host_to_app_map = Vec::with_capacity_in(env.exposed_to_host.len(), env.arena);
|
||||
|
@ -110,10 +110,13 @@ pub fn build_app_module<'a>(
|
|||
if env.exposed_to_host.contains(&sym) {
|
||||
maybe_main_fn_index = Some(fn_index);
|
||||
|
||||
// Assumption: there is only one specialization of a host-exposed function
|
||||
let ident_string = sym.as_str(interns);
|
||||
let c_function_name = bumpalo::format!(in env.arena, "roc__{}_1_exposed", ident_string);
|
||||
host_to_app_map.push((c_function_name.into_bump_str(), fn_index));
|
||||
let exposed_name = layout_ids
|
||||
.get_toplevel(sym, &proc_layout)
|
||||
.to_exposed_symbol_string(sym, interns);
|
||||
|
||||
let exposed_name_bump: &'a str = env.arena.alloc_str(&exposed_name);
|
||||
|
||||
host_to_app_map.push((exposed_name_bump, fn_index));
|
||||
}
|
||||
|
||||
proc_lookup.push(ProcLookupData {
|
||||
|
|
|
@ -46,7 +46,7 @@ use roc_solve::module::SolvedModule;
|
|||
use roc_solve::solve;
|
||||
use roc_target::TargetInfo;
|
||||
use roc_types::solved_types::Solved;
|
||||
use roc_types::subs::{Subs, VarStore, Variable};
|
||||
use roc_types::subs::{ExposedTypesStorageSubs, Subs, VarStore, Variable};
|
||||
use roc_types::types::{Alias, AliasKind};
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
use std::collections::HashMap;
|
||||
|
@ -510,6 +510,7 @@ pub struct LoadedModule {
|
|||
pub dep_idents: IdentIdsByModule,
|
||||
pub exposed_aliases: MutMap<Symbol, Alias>,
|
||||
pub exposed_values: Vec<Symbol>,
|
||||
pub exposed_types_storage: ExposedTypesStorageSubs,
|
||||
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||
pub timings: MutMap<ModuleId, ModuleTiming>,
|
||||
pub documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
|
@ -687,6 +688,7 @@ enum Msg<'a> {
|
|||
solved_subs: Solved<Subs>,
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
exposed_aliases_by_symbol: MutMap<Symbol, (bool, Alias)>,
|
||||
exposed_types_storage: ExposedTypesStorageSubs,
|
||||
dep_idents: IdentIdsByModule,
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
abilities_store: AbilitiesStore,
|
||||
|
@ -877,7 +879,8 @@ pub struct ModuleTiming {
|
|||
pub constrain: Duration,
|
||||
pub solve: Duration,
|
||||
pub find_specializations: Duration,
|
||||
pub make_specializations: Duration,
|
||||
// indexed by make specializations pass
|
||||
pub make_specializations: Vec<Duration>,
|
||||
// TODO pub monomorphize: Duration,
|
||||
/// Total duration will always be more than the sum of the other fields, due
|
||||
/// to things like state lookups in between phases, waiting on other threads, etc.
|
||||
|
@ -895,7 +898,7 @@ impl ModuleTiming {
|
|||
constrain: Duration::default(),
|
||||
solve: Duration::default(),
|
||||
find_specializations: Duration::default(),
|
||||
make_specializations: Duration::default(),
|
||||
make_specializations: Vec::with_capacity(2),
|
||||
start_time,
|
||||
end_time: start_time, // just for now; we'll overwrite this at the end
|
||||
}
|
||||
|
@ -921,8 +924,9 @@ impl ModuleTiming {
|
|||
} = self;
|
||||
|
||||
let calculate = |t: Result<Duration, _>| -> Option<Duration> {
|
||||
t.ok()?
|
||||
.checked_sub(*make_specializations)?
|
||||
make_specializations
|
||||
.iter()
|
||||
.fold(t.ok(), |t, pass_time| t?.checked_sub(*pass_time))?
|
||||
.checked_sub(*find_specializations)?
|
||||
.checked_sub(*solve)?
|
||||
.checked_sub(*constrain)?
|
||||
|
@ -1404,6 +1408,7 @@ fn state_thread_step<'a>(
|
|||
solved_subs,
|
||||
exposed_vars_by_symbol,
|
||||
exposed_aliases_by_symbol,
|
||||
exposed_types_storage,
|
||||
dep_idents,
|
||||
documentation,
|
||||
abilities_store,
|
||||
|
@ -1421,6 +1426,7 @@ fn state_thread_step<'a>(
|
|||
solved_subs,
|
||||
exposed_aliases_by_symbol,
|
||||
exposed_vars_by_symbol,
|
||||
exposed_types_storage,
|
||||
dep_idents,
|
||||
documentation,
|
||||
abilities_store,
|
||||
|
@ -2209,6 +2215,7 @@ fn update<'a>(
|
|||
solved_subs,
|
||||
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
|
||||
exposed_aliases_by_symbol: solved_module.aliases,
|
||||
exposed_types_storage: solved_module.exposed_types,
|
||||
dep_idents,
|
||||
documentation,
|
||||
abilities_store,
|
||||
|
@ -2658,11 +2665,13 @@ fn finish_specialization(
|
|||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn finish(
|
||||
state: State,
|
||||
solved: Solved<Subs>,
|
||||
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
exposed_types_storage: ExposedTypesStorageSubs,
|
||||
dep_idents: IdentIdsByModule,
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
abilities_store: AbilitiesStore,
|
||||
|
@ -2697,6 +2706,7 @@ fn finish(
|
|||
exposed_aliases: exposed_aliases_by_symbol,
|
||||
exposed_values,
|
||||
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
|
||||
exposed_types_storage,
|
||||
sources,
|
||||
timings: state.timings,
|
||||
documentation,
|
||||
|
@ -3738,7 +3748,7 @@ impl<'a> BuildTask<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_imports(
|
||||
pub fn add_imports(
|
||||
my_module: ModuleId,
|
||||
subs: &mut Subs,
|
||||
mut pending_abilities: PendingAbilitiesStore,
|
||||
|
@ -4407,9 +4417,11 @@ fn make_specializations<'a>(
|
|||
mono_env.home.register_debug_idents(mono_env.ident_ids);
|
||||
|
||||
let make_specializations_end = SystemTime::now();
|
||||
module_timing.make_specializations = make_specializations_end
|
||||
module_timing.make_specializations.push(
|
||||
make_specializations_end
|
||||
.duration_since(make_specializations_start)
|
||||
.unwrap();
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
Msg::MadeSpecializations {
|
||||
module_id: home,
|
||||
|
@ -4495,6 +4507,7 @@ fn build_pending_specializations<'a>(
|
|||
)
|
||||
}
|
||||
}
|
||||
Expects(_) => todo!("toplevel expect codgen"),
|
||||
InvalidCycle(_) | DeclareRec(..) => {
|
||||
// do nothing?
|
||||
// this may mean the loc_symbols are not defined during codegen; is that a problem?
|
||||
|
@ -5013,7 +5026,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
|
|||
/// Generic number types (Num, Int, Float, etc.) are treated as `DelayedAlias`es resolved during
|
||||
/// type solving.
|
||||
/// All that remains are Signed8, Signed16, etc.
|
||||
fn default_aliases() -> roc_solve::solve::Aliases {
|
||||
pub fn default_aliases() -> roc_solve::solve::Aliases {
|
||||
use roc_types::types::Type;
|
||||
|
||||
let mut solve_aliases = roc_solve::solve::Aliases::default();
|
||||
|
|
|
@ -290,6 +290,10 @@ mod test_load {
|
|||
cycle @ InvalidCycle(_) => {
|
||||
panic!("Unexpected cyclic def in module declarations: {:?}", cycle);
|
||||
}
|
||||
expects @ Expects(_) => {
|
||||
// at least at the moment this does not happen
|
||||
panic!("Unexpected expects in module declarations: {:?}", expects);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1321,10 +1321,12 @@ define_builtins! {
|
|||
17 ENCODE_BOOL: "bool"
|
||||
18 ENCODE_STRING: "string"
|
||||
19 ENCODE_LIST: "list"
|
||||
20 ENCODE_CUSTOM: "custom"
|
||||
21 ENCODE_APPEND_WITH: "appendWith"
|
||||
22 ENCODE_APPEND: "append"
|
||||
23 ENCODE_TO_BYTES: "toBytes"
|
||||
20 ENCODE_RECORD: "record"
|
||||
21 ENCODE_TAG: "tag"
|
||||
22 ENCODE_CUSTOM: "custom"
|
||||
23 ENCODE_APPEND_WITH: "appendWith"
|
||||
24 ENCODE_APPEND: "append"
|
||||
25 ENCODE_TO_BYTES: "toBytes"
|
||||
}
|
||||
10 JSON: "Json" => {
|
||||
0 JSON_JSON: "Json"
|
||||
|
|
|
@ -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_late_solve = { path = "../late_solve" }
|
||||
roc_std = { path = "../../roc_std", default-features = false }
|
||||
roc_problem = { path = "../problem" }
|
||||
|
|
599
compiler/mono/src/derive/encoding.rs
Normal file
599
compiler/mono/src/derive/encoding.rs
Normal file
|
@ -0,0 +1,599 @@
|
|||
//! Derivers for the `Encoding` ability.
|
||||
|
||||
use std::iter::once;
|
||||
|
||||
use bumpalo::Bump;
|
||||
|
||||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Field, Recursive, WhenBranch};
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_collections::SendMap;
|
||||
use roc_derive_key::encoding::FlatEncodableKey;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_late_solve::{instantiate_rigids, AbilitiesView};
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{
|
||||
Content, ExhaustiveMark, ExposedTypesStorageSubs, FlatType, GetSubsSlice, LambdaSet,
|
||||
OptVariable, RecordFields, RedundantMark, Subs, SubsFmtContent, SubsSlice, UnionLambdas,
|
||||
UnionTags, Variable, VariableSubsSlice,
|
||||
};
|
||||
use roc_types::types::{AliasKind, RecordField};
|
||||
|
||||
use crate::derive::synth_var;
|
||||
|
||||
macro_rules! bad_input {
|
||||
($subs:expr, $var:expr) => {
|
||||
bad_input!($subs, $var, "Invalid content")
|
||||
};
|
||||
($subs:expr, $var:expr, $msg:expr) => {
|
||||
internal_error!(
|
||||
"{:?} for toEncoder deriver: {:?}",
|
||||
$msg,
|
||||
SubsFmtContent($subs.get_content_without_compacting($var), $subs)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Env<'a> {
|
||||
pub home: ModuleId,
|
||||
pub arena: &'a Bump,
|
||||
pub subs: &'a mut Subs,
|
||||
pub ident_ids: &'a mut IdentIds,
|
||||
pub exposed_encode_types: &'a mut ExposedTypesStorageSubs,
|
||||
}
|
||||
|
||||
impl Env<'_> {
|
||||
fn unique_symbol(&mut self) -> Symbol {
|
||||
let ident_id = self.ident_ids.gen_unique();
|
||||
|
||||
Symbol::new(self.home, ident_id)
|
||||
}
|
||||
|
||||
fn import_encode_symbol(&mut self, symbol: Symbol) -> Variable {
|
||||
debug_assert_eq!(symbol.module_id(), ModuleId::ENCODE);
|
||||
|
||||
let storage_var = self
|
||||
.exposed_encode_types
|
||||
.stored_vars_by_symbol
|
||||
.get(&symbol)
|
||||
.unwrap();
|
||||
|
||||
let imported = self
|
||||
.exposed_encode_types
|
||||
.storage_subs
|
||||
.export_variable_to(self.subs, *storage_var);
|
||||
|
||||
instantiate_rigids(self.subs, imported.variable);
|
||||
|
||||
imported.variable
|
||||
}
|
||||
|
||||
fn unify(&mut self, left: Variable, right: Variable) {
|
||||
// NOTE: I don't believe the abilities store is necessary for unification at this point!
|
||||
roc_late_solve::unify(
|
||||
self.home,
|
||||
self.arena,
|
||||
self.subs,
|
||||
&AbilitiesView::Module(&AbilitiesStore::default()),
|
||||
left,
|
||||
right,
|
||||
)
|
||||
.expect("unification failed!")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: decide whether it will be better to pass the whole signature, or just the argument type.
|
||||
// For now we are only using the argument type for convinience of testing.
|
||||
#[allow(dead_code)]
|
||||
fn verify_signature(env: &mut Env<'_>, signature: Variable) {
|
||||
// Verify the signature is what we expect: input -> Encoder fmt | fmt has EncoderFormatting
|
||||
// and get the input type
|
||||
match env.subs.get_content_without_compacting(signature) {
|
||||
Content::Structure(FlatType::Func(input, _, output)) => {
|
||||
// Check the output is Encoder fmt | fmt has EncoderFormatting
|
||||
match env.subs.get_content_without_compacting(*output) {
|
||||
Content::Alias(Symbol::ENCODE_ENCODER, args, _, AliasKind::Opaque) => {
|
||||
match env.subs.get_subs_slice(args.all_variables()) {
|
||||
[one] => match env.subs.get_content_without_compacting(*one) {
|
||||
Content::FlexAbleVar(_, Symbol::ENCODE_ENCODERFORMATTING) => {}
|
||||
_ => bad_input!(env.subs, signature),
|
||||
},
|
||||
_ => bad_input!(env.subs, signature),
|
||||
}
|
||||
}
|
||||
_ => bad_input!(env.subs, signature),
|
||||
}
|
||||
|
||||
// Get the only parameter into toEncoder
|
||||
match env.subs.get_subs_slice(*input) {
|
||||
[one] => *one,
|
||||
_ => bad_input!(env.subs, signature),
|
||||
}
|
||||
}
|
||||
_ => bad_input!(env.subs, signature),
|
||||
};
|
||||
}
|
||||
|
||||
enum OwnedFlatEncodable {
|
||||
List,
|
||||
Set,
|
||||
Dict,
|
||||
Record(Vec<Lowercase>),
|
||||
TagUnion(Vec<(TagName, u16)>),
|
||||
}
|
||||
|
||||
pub struct OwnedFlatEncodableKey(OwnedFlatEncodable);
|
||||
|
||||
impl From<FlatEncodableKey<'_>> for OwnedFlatEncodableKey {
|
||||
fn from(key: FlatEncodableKey) -> Self {
|
||||
use OwnedFlatEncodable::*;
|
||||
let key = match key {
|
||||
FlatEncodableKey::List() => List,
|
||||
FlatEncodableKey::Set() => Set,
|
||||
FlatEncodableKey::Dict() => Dict,
|
||||
FlatEncodableKey::Record(fields) => Record(fields.into_iter().cloned().collect()),
|
||||
FlatEncodableKey::TagUnion(tags) => TagUnion(
|
||||
tags.into_iter()
|
||||
.map(|(tag, arity)| (tag.clone(), arity))
|
||||
.collect(),
|
||||
),
|
||||
};
|
||||
Self(key)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn derive_to_encoder(env: &mut Env<'_>, key: OwnedFlatEncodableKey) -> Expr {
|
||||
match key.0 {
|
||||
OwnedFlatEncodable::List => todo!(),
|
||||
OwnedFlatEncodable::Set => todo!(),
|
||||
OwnedFlatEncodable::Dict => todo!(),
|
||||
OwnedFlatEncodable::Record(fields) => {
|
||||
// Generalized record var so we can reuse this impl between many records:
|
||||
// if fields = { a, b }, this is { a: t1, b: t2 } for fresh t1, t2.
|
||||
let flex_fields = fields
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
(
|
||||
name,
|
||||
RecordField::Required(env.subs.fresh_unnamed_flex_var()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<(Lowercase, _)>>();
|
||||
let fields = RecordFields::insert_into_subs(env.subs, flex_fields);
|
||||
let record_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)),
|
||||
);
|
||||
|
||||
to_encoder_record(env, record_var, fields)
|
||||
}
|
||||
OwnedFlatEncodable::TagUnion(tags) => {
|
||||
// Generalized tag union var so we can reuse this impl between many unions:
|
||||
// if tags = [ A arity=2, B arity=1 ], this is [ A t1 t2, B t3 ] for fresh t1, t2, t3
|
||||
let flex_tag_labels = tags
|
||||
.into_iter()
|
||||
.map(|(label, arity)| {
|
||||
let variables_slice =
|
||||
VariableSubsSlice::reserve_into_subs(env.subs, arity.into());
|
||||
for var_index in variables_slice {
|
||||
env.subs[var_index] = env.subs.fresh_unnamed_flex_var();
|
||||
}
|
||||
(label, variables_slice)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let union_tags = UnionTags::insert_slices_into_subs(env.subs, flex_tag_labels);
|
||||
let tag_union_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::TagUnion(union_tags, Variable::EMPTY_TAG_UNION)),
|
||||
);
|
||||
|
||||
to_encoder_tag_union(env, tag_union_var, union_tags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_encoder_record(env: &mut Env<'_>, record_var: Variable, fields: RecordFields) -> Expr {
|
||||
// Suppose rcd = { a: t1, b: t2 }. Build
|
||||
//
|
||||
// \rcd -> Encode.record [
|
||||
// { key: "a", value: Encode.toEncoder rcd.a },
|
||||
// { key: "b", value: Encode.toEncoder rcd.b },
|
||||
// ]
|
||||
|
||||
let rcd_sym = env.unique_symbol();
|
||||
let whole_rcd_var = env.subs.fresh_unnamed_flex_var(); // type of the { key, value } records in the list
|
||||
|
||||
use Expr::*;
|
||||
|
||||
let fields_list = fields
|
||||
.iter_all()
|
||||
.map(|(field_name_index, field_var_index, _)| {
|
||||
let field_name = env.subs[field_name_index].clone();
|
||||
let field_var = env.subs[field_var_index];
|
||||
let field_var_slice = VariableSubsSlice::new(field_var_index.index, 1);
|
||||
|
||||
// key: "a"
|
||||
let key_field = Field {
|
||||
var: Variable::STR,
|
||||
region: Region::zero(),
|
||||
loc_expr: Box::new(Loc::at_zero(Str(field_name.as_str().into()))),
|
||||
};
|
||||
|
||||
// rcd.a
|
||||
let field_access = Access {
|
||||
record_var,
|
||||
ext_var: env.subs.fresh_unnamed_flex_var(),
|
||||
field_var,
|
||||
loc_expr: Box::new(Loc::at_zero(Var(rcd_sym))),
|
||||
field: field_name,
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
// (typeof rcd.a) -[clos]-> t1
|
||||
let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
|
||||
let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1
|
||||
let this_to_encoder_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
field_var_slice,
|
||||
to_encoder_clos_var,
|
||||
encoder_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// val -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
// ~ (typeof rcd.a) -[clos]-> t1
|
||||
env.unify(to_encoder_fn_var, this_to_encoder_fn_var);
|
||||
|
||||
// toEncoder : (typeof rcd.a) -[clos]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let to_encoder_fn = Box::new((
|
||||
to_encoder_fn_var,
|
||||
Loc::at_zero(Var(Symbol::ENCODE_TO_ENCODER)),
|
||||
to_encoder_clos_var,
|
||||
encoder_var,
|
||||
));
|
||||
|
||||
// toEncoder rcd.a
|
||||
let to_encoder_call = Call(
|
||||
to_encoder_fn,
|
||||
vec![(field_var, Loc::at_zero(field_access))],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
// value: toEncoder rcd.a
|
||||
let value_field = Field {
|
||||
var: encoder_var,
|
||||
region: Region::zero(),
|
||||
loc_expr: Box::new(Loc::at_zero(to_encoder_call)),
|
||||
};
|
||||
|
||||
// { key: "a", value: toEncoder rcd.a }
|
||||
let mut kv = SendMap::default();
|
||||
kv.insert("key".into(), key_field);
|
||||
kv.insert("value".into(), value_field);
|
||||
|
||||
let this_record_fields = RecordFields::insert_into_subs(
|
||||
env.subs,
|
||||
(once(("key".into(), RecordField::Required(Variable::STR))))
|
||||
.chain(once(("value".into(), RecordField::Required(encoder_var)))),
|
||||
);
|
||||
let this_record_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Record(this_record_fields, Variable::EMPTY_RECORD)),
|
||||
);
|
||||
// NOTE: must be done to unify the lambda sets under `encoder_var`
|
||||
env.unify(this_record_var, whole_rcd_var);
|
||||
|
||||
Loc::at_zero(Record {
|
||||
record_var: whole_rcd_var,
|
||||
fields: kv,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// typeof [ { key: .., value: .. }, { key: .., value: .. } ]
|
||||
let fields_rcd_var_slice = VariableSubsSlice::insert_into_subs(env.subs, once(whole_rcd_var));
|
||||
let fields_list_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Apply(Symbol::LIST_LIST, fields_rcd_var_slice)),
|
||||
);
|
||||
|
||||
// [ { key: .., value: ..}, .. ]
|
||||
let fields_list = List {
|
||||
elem_var: whole_rcd_var,
|
||||
loc_elems: fields_list,
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
// fields_list_var -[clos]-> t1
|
||||
let fields_list_var_slice =
|
||||
VariableSubsSlice::insert_into_subs(env.subs, once(fields_list_var));
|
||||
let encode_record_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
|
||||
let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1
|
||||
let this_encode_record_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
fields_list_var_slice,
|
||||
encode_record_clos_var,
|
||||
encoder_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// List { key : Str, value : Encoder fmt } -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
// ~ fields_list_var -[clos]-> t1
|
||||
env.unify(encode_record_fn_var, this_encode_record_fn_var);
|
||||
|
||||
// Encode.record : fields_list_var -[clos]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let encode_record_fn = Box::new((
|
||||
encode_record_fn_var,
|
||||
Loc::at_zero(Var(Symbol::ENCODE_RECORD)),
|
||||
encode_record_clos_var,
|
||||
encoder_var,
|
||||
));
|
||||
|
||||
// Encode.record [ { key: .., value: .. }, .. ]
|
||||
let encode_record_call = Call(
|
||||
encode_record_fn,
|
||||
vec![(fields_list_var, Loc::at_zero(fields_list))],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
let fn_name = env.unique_symbol();
|
||||
let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, vec![])));
|
||||
// -[fn_name]->
|
||||
let fn_clos_var = synth_var(
|
||||
env.subs,
|
||||
Content::LambdaSet(LambdaSet {
|
||||
solved: fn_name_labels,
|
||||
recursion_var: OptVariable::NONE,
|
||||
unspecialized: SubsSlice::default(),
|
||||
}),
|
||||
);
|
||||
// typeof rcd -[fn_name]-> (typeof Encode.record [ .. ] = Encoder fmt)
|
||||
let record_var_slice = SubsSlice::insert_into_subs(env.subs, once(record_var));
|
||||
let fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(record_var_slice, fn_clos_var, encoder_var)),
|
||||
);
|
||||
|
||||
// \rcd -[fn_name]-> Encode.record [ { key: .., value: .. }, .. ]
|
||||
Closure(ClosureData {
|
||||
function_type: fn_var,
|
||||
closure_type: fn_clos_var,
|
||||
return_type: encoder_var,
|
||||
name: fn_name,
|
||||
captured_symbols: vec![],
|
||||
recursive: Recursive::NotRecursive,
|
||||
arguments: vec![(
|
||||
record_var,
|
||||
AnnotatedMark::known_exhaustive(),
|
||||
Loc::at_zero(Pattern::Identifier(rcd_sym)),
|
||||
)],
|
||||
loc_body: Box::new(Loc::at_zero(encode_record_call)),
|
||||
})
|
||||
}
|
||||
|
||||
fn to_encoder_tag_union(env: &mut Env<'_>, tag_union_var: Variable, tags: UnionTags) -> Expr {
|
||||
// Suppose tag = [ A t1 t2, B t3 ]. Build
|
||||
//
|
||||
// \tag -> when tag is
|
||||
// A v1 v2 -> Encode.tag "A" [ Encode.toEncoder v1, Encode.toEncoder v2 ]
|
||||
// B v3 -> Encode.tag "B" [ Encode.toEncoder v3 ]
|
||||
|
||||
let tag_sym = env.unique_symbol();
|
||||
let whole_tag_encoders_var = env.subs.fresh_unnamed_flex_var(); // type of the Encode.tag ... calls in the branch bodies
|
||||
|
||||
use Expr::*;
|
||||
|
||||
let branches = tags
|
||||
.iter_all()
|
||||
.map(|(tag_name_index, tag_vars_slice_index)| {
|
||||
// A
|
||||
let tag_name = &env.subs[tag_name_index].clone();
|
||||
let vars_slice = env.subs[tag_vars_slice_index];
|
||||
// t1 t2
|
||||
let payload_vars = env.subs.get_subs_slice(vars_slice).to_vec();
|
||||
// v1 v2
|
||||
let payload_syms: Vec<_> = std::iter::repeat_with(|| env.unique_symbol())
|
||||
.take(payload_vars.len())
|
||||
.collect();
|
||||
|
||||
// `A v1 v2` pattern
|
||||
let pattern = Pattern::AppliedTag {
|
||||
whole_var: tag_union_var,
|
||||
tag_name: tag_name.clone(),
|
||||
ext_var: Variable::EMPTY_TAG_UNION,
|
||||
// (t1, v1) (t2, v2)
|
||||
arguments: (payload_vars.iter())
|
||||
.zip(payload_syms.iter())
|
||||
.map(|(var, sym)| (*var, Loc::at_zero(Pattern::Identifier(*sym))))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
// whole type of the elements in [ Encode.toEncoder v1, Encode.toEncoder v2 ]
|
||||
let whole_payload_encoders_var = env.subs.fresh_unnamed_flex_var();
|
||||
// [ Encode.toEncoder v1, Encode.toEncoder v2 ]
|
||||
let payload_to_encoders = (payload_syms.iter())
|
||||
.zip(payload_vars.iter())
|
||||
.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);
|
||||
|
||||
// wanted: t1 -[clos]-> t'
|
||||
let var_slice_of_sym_var =
|
||||
VariableSubsSlice::insert_into_subs(env.subs, [sym_var]); // [ t1 ]
|
||||
let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
|
||||
let encoder_var = env.subs.fresh_unnamed_flex_var(); // t'
|
||||
let this_to_encoder_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
var_slice_of_sym_var,
|
||||
to_encoder_clos_var,
|
||||
encoder_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// val -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
// ~ t1 -[clos]-> t'
|
||||
env.unify(to_encoder_fn_var, this_to_encoder_fn_var);
|
||||
|
||||
// toEncoder : t1 -[clos]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let to_encoder_fn = Box::new((
|
||||
this_to_encoder_fn_var,
|
||||
Loc::at_zero(Var(Symbol::ENCODE_TO_ENCODER)),
|
||||
to_encoder_clos_var,
|
||||
encoder_var,
|
||||
));
|
||||
|
||||
// toEncoder rcd.a
|
||||
let to_encoder_call = Call(
|
||||
to_encoder_fn,
|
||||
vec![(sym_var, Loc::at_zero(Var(sym)))],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
// NOTE: must be done to unify the lambda sets under `encoder_var`
|
||||
env.unify(encoder_var, whole_payload_encoders_var);
|
||||
|
||||
Loc::at_zero(to_encoder_call)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// typeof [ Encode.toEncoder v1, Encode.toEncoder v2 ]
|
||||
let whole_encoders_var_slice =
|
||||
VariableSubsSlice::insert_into_subs(env.subs, [whole_payload_encoders_var]);
|
||||
let payload_encoders_list_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Apply(Symbol::LIST_LIST, whole_encoders_var_slice)),
|
||||
);
|
||||
|
||||
// [ Encode.toEncoder v1, Encode.toEncoder v2 ]
|
||||
let payload_encoders_list = List {
|
||||
elem_var: whole_payload_encoders_var,
|
||||
loc_elems: payload_to_encoders,
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
// wanted: Str, List whole_encoders_var -[clos]-> t'
|
||||
// wanted: Str, List whole_encoders_var
|
||||
let this_encode_tag_args_var_slice = VariableSubsSlice::insert_into_subs(
|
||||
env.subs,
|
||||
[Variable::STR, payload_encoders_list_var],
|
||||
);
|
||||
let this_encode_tag_clos_var = env.subs.fresh_unnamed_flex_var(); // -[clos]->
|
||||
let this_encoder_var = env.subs.fresh_unnamed_flex_var(); // t'
|
||||
let this_encode_tag_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
this_encode_tag_args_var_slice,
|
||||
this_encode_tag_clos_var,
|
||||
this_encoder_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// Str, List (Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting
|
||||
// ~ Str, List whole_encoders_var -[clos]-> t'
|
||||
env.unify(encode_tag_fn_var, this_encode_tag_fn_var);
|
||||
|
||||
// Encode.tag : Str, List whole_encoders_var -[clos]-> Encoder fmt | fmt has EncoderFormatting
|
||||
let encode_tag_fn = Box::new((
|
||||
this_encode_tag_fn_var,
|
||||
Loc::at_zero(Var(Symbol::ENCODE_TAG)),
|
||||
this_encode_tag_clos_var,
|
||||
this_encoder_var,
|
||||
));
|
||||
|
||||
// Encode.tag "A" [ Encode.toEncoder v1, Encode.toEncoder v2 ]
|
||||
let encode_tag_call = Call(
|
||||
encode_tag_fn,
|
||||
vec![
|
||||
// (Str, "A")
|
||||
(Variable::STR, Loc::at_zero(Str(tag_name.0.as_str().into()))),
|
||||
// (List (Encoder fmt), [ Encode.toEncoder v1, Encode.toEncoder v2 ])
|
||||
(
|
||||
payload_encoders_list_var,
|
||||
Loc::at_zero(payload_encoders_list),
|
||||
),
|
||||
],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
// NOTE: must be done to unify the lambda sets under `encoder_var`
|
||||
// Encode.tag "A" [ Encode.toEncoder v1, Encode.toEncoder v2 ] ~ whole_encoders
|
||||
env.unify(this_encoder_var, whole_tag_encoders_var);
|
||||
|
||||
WhenBranch {
|
||||
patterns: vec![Loc::at_zero(pattern)],
|
||||
value: Loc::at_zero(encode_tag_call),
|
||||
guard: None,
|
||||
redundant: RedundantMark::known_non_redundant(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// when tag is
|
||||
// A v1 v2 -> Encode.tag "A" [ Encode.toEncoder v1, Encode.toEncoder v2 ]
|
||||
// B v3 -> Encode.tag "B" [ Encode.toEncoder v3 ]
|
||||
let when_branches = When {
|
||||
loc_cond: Box::new(Loc::at_zero(Var(tag_sym))),
|
||||
cond_var: tag_union_var,
|
||||
expr_var: whole_tag_encoders_var,
|
||||
region: Region::zero(),
|
||||
branches,
|
||||
branches_cond_var: tag_union_var,
|
||||
exhaustive: ExhaustiveMark::known_exhaustive(),
|
||||
};
|
||||
|
||||
let fn_name = env.unique_symbol();
|
||||
let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, vec![])));
|
||||
// -[fn_name]->
|
||||
let fn_clos_var = synth_var(
|
||||
env.subs,
|
||||
Content::LambdaSet(LambdaSet {
|
||||
solved: fn_name_labels,
|
||||
recursion_var: OptVariable::NONE,
|
||||
unspecialized: SubsSlice::default(),
|
||||
}),
|
||||
);
|
||||
// tag_union_var -[fn_name]-> whole_tag_encoders_var
|
||||
let tag_union_var_slice = SubsSlice::insert_into_subs(env.subs, once(tag_union_var));
|
||||
let fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
tag_union_var_slice,
|
||||
fn_clos_var,
|
||||
whole_tag_encoders_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// \tag -> when tag is
|
||||
// A v1 v2 -> Encode.tag "A" [ Encode.toEncoder v1, Encode.toEncoder v2 ]
|
||||
// B v3 -> Encode.tag "B" [ Encode.toEncoder v3 ]
|
||||
Closure(ClosureData {
|
||||
function_type: fn_var,
|
||||
closure_type: fn_clos_var,
|
||||
return_type: whole_tag_encoders_var,
|
||||
name: fn_name,
|
||||
captured_symbols: vec![],
|
||||
recursive: Recursive::NotRecursive,
|
||||
arguments: vec![(
|
||||
tag_union_var,
|
||||
AnnotatedMark::known_exhaustive(),
|
||||
Loc::at_zero(Pattern::Identifier(tag_sym)),
|
||||
)],
|
||||
loc_body: Box::new(Loc::at_zero(when_branches)),
|
||||
})
|
||||
}
|
18
compiler/mono/src/derive/mod.rs
Normal file
18
compiler/mono/src/derive/mod.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
//! Auto-derivers of builtin ability methods.
|
||||
|
||||
use roc_types::subs::{Content, Descriptor, Mark, OptVariable, Rank, Subs, Variable};
|
||||
|
||||
pub fn synth_var(subs: &mut Subs, content: Content) -> Variable {
|
||||
let descriptor = Descriptor {
|
||||
content,
|
||||
// NOTE: this is incorrect, but that is irrelevant - derivers may only be called during
|
||||
// monomorphization (or later), at which point we do not care about variable
|
||||
// generalization. Hence ranks should not matter.
|
||||
rank: Rank::toplevel(),
|
||||
mark: Mark::NONE,
|
||||
copy: OptVariable::NONE,
|
||||
};
|
||||
subs.fresh(descriptor)
|
||||
}
|
||||
|
||||
pub mod encoding;
|
|
@ -2913,13 +2913,20 @@ pub fn list_layout_from_elem<'a>(
|
|||
pub struct LayoutId(u32);
|
||||
|
||||
impl LayoutId {
|
||||
// Returns something like "foo#1" when given a symbol that interns to "foo"
|
||||
// Returns something like "#UserApp_foo_1" when given a symbol that interns to "foo"
|
||||
// and a LayoutId of 1.
|
||||
pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
|
||||
let ident_string = symbol.as_str(interns);
|
||||
let module_string = interns.module_ids.get_name(symbol.module_id()).unwrap();
|
||||
format!("{}_{}_{}", module_string, ident_string, self.0)
|
||||
}
|
||||
|
||||
// Returns something like "roc__foo_1_exposed" when given a symbol that interns to "foo"
|
||||
// and a LayoutId of 1.
|
||||
pub fn to_exposed_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
|
||||
let ident_string = symbol.as_str(interns);
|
||||
format!("roc__{}_{}_exposed", ident_string, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
struct IdsByLayout<'a> {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
pub mod borrow;
|
||||
pub mod code_gen_help;
|
||||
mod copy;
|
||||
pub mod derive;
|
||||
pub mod inc_dec;
|
||||
pub mod ir;
|
||||
pub mod layout;
|
||||
|
|
|
@ -562,6 +562,27 @@ impl ObligationCache<'_> {
|
|||
return Err(var);
|
||||
}
|
||||
}
|
||||
Alias(
|
||||
Symbol::NUM_U8
|
||||
| Symbol::NUM_U16
|
||||
| Symbol::NUM_U32
|
||||
| Symbol::NUM_U64
|
||||
| Symbol::NUM_U128
|
||||
| Symbol::NUM_I8
|
||||
| Symbol::NUM_I16
|
||||
| Symbol::NUM_I32
|
||||
| Symbol::NUM_I64
|
||||
| Symbol::NUM_I128
|
||||
| Symbol::NUM_NAT
|
||||
| Symbol::NUM_F32
|
||||
| Symbol::NUM_F64
|
||||
| Symbol::NUM_DEC,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
) => {
|
||||
// yes
|
||||
}
|
||||
Alias(_, arguments, real_type_var, _) => {
|
||||
push_var_slice!(arguments.all_variables());
|
||||
stack.push(*real_type_var);
|
||||
|
|
|
@ -792,7 +792,9 @@ fn solve(
|
|||
let result = offenders.len();
|
||||
|
||||
if result > 0 {
|
||||
dbg!(&subs, &offenders, &let_con.def_types);
|
||||
eprintln!("subs = {:?}", &subs);
|
||||
eprintln!("offenders = {:?}", &offenders);
|
||||
eprintln!("let_con.def_types = {:?}", &let_con.def_types);
|
||||
}
|
||||
|
||||
result
|
||||
|
@ -1873,7 +1875,7 @@ fn compact_lambda_set<P: Phase>(
|
|||
// Default has default : {} -> a | a has Default
|
||||
//
|
||||
// {a, b} = default {}
|
||||
// # ^^^^^^^ {} -[{a: t1, b: t2}:default:1]
|
||||
// # ^^^^^^^ {} -[{a: t1, b: t2}:default:1]-> {a: t1, b: t2}
|
||||
new_unspecialized.push(uls);
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -6817,4 +6817,17 @@ mod solve_expr {
|
|||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_of_lambdas() {
|
||||
infer_queries!(
|
||||
indoc!(
|
||||
r#"
|
||||
[\{} -> {}, \{} -> {}]
|
||||
#^^^^^^^^^^^^^^^^^^^^^^{-1}
|
||||
"#
|
||||
),
|
||||
&[r#"[\{} -> {}, \{} -> {}] : List ({}* -[[1(1), 2(2)]]-> {})"#],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
31
compiler/test_derive/Cargo.toml
Normal file
31
compiler/test_derive/Cargo.toml
Normal file
|
@ -0,0 +1,31 @@
|
|||
[package]
|
||||
name = "test_derive"
|
||||
version = "0.1.0"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[test]]
|
||||
name = "test_derive"
|
||||
path = "src/tests.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_load_internal = { path = "../load_internal" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_derive_key = { path = "../derive_key" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_constrain = { path = "../constrain" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
lazy_static = "1.4.0"
|
||||
indoc = "1.0.3"
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
pretty_assertions = "1.0.0"
|
597
compiler/test_derive/src/encoding.rs
Normal file
597
compiler/test_derive/src/encoding.rs
Normal file
|
@ -0,0 +1,597 @@
|
|||
#![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 std::path::PathBuf;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use indoc::indoc;
|
||||
use pretty_assertions::assert_eq;
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
use crate::pretty_print::{pretty_print, Ctx};
|
||||
use roc_can::{
|
||||
abilities::{AbilitiesStore, ResolvedSpecializations},
|
||||
constraint::Constraints,
|
||||
expected::Expected,
|
||||
expr::Expr,
|
||||
module::RigidVariables,
|
||||
};
|
||||
use roc_collections::VecSet;
|
||||
use roc_constrain::{
|
||||
expr::constrain_expr,
|
||||
module::{ExposedByModule, ExposedForModule, ExposedModuleTypes},
|
||||
};
|
||||
use roc_debug_flags::dbg_do;
|
||||
use roc_derive_key::{encoding::FlatEncodableKey, Derived};
|
||||
use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading};
|
||||
use roc_module::{
|
||||
ident::{ModuleName, TagName},
|
||||
symbol::{IdentIds, Interns, ModuleId, Symbol},
|
||||
};
|
||||
use roc_mono::derive::{
|
||||
encoding::{self, Env},
|
||||
synth_var,
|
||||
};
|
||||
use roc_region::all::{LineInfo, Region};
|
||||
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, Type},
|
||||
};
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
fn check_derived_typechecks(
|
||||
derived: Expr,
|
||||
test_module: ModuleId,
|
||||
mut test_subs: Subs,
|
||||
interns: &Interns,
|
||||
exposed_encode_types: ExposedTypesStorageSubs,
|
||||
encode_abilities_store: AbilitiesStore,
|
||||
expected_type: &str,
|
||||
) {
|
||||
// constrain the derived
|
||||
let mut constraints = Constraints::new();
|
||||
let mut env = roc_constrain::expr::Env {
|
||||
rigids: Default::default(),
|
||||
resolutions_to_make: Default::default(),
|
||||
home: test_module,
|
||||
};
|
||||
let real_type = test_subs.fresh_unnamed_flex_var();
|
||||
let constr = constrain_expr(
|
||||
&mut constraints,
|
||||
&mut env,
|
||||
Region::zero(),
|
||||
&derived,
|
||||
Expected::NoExpectation(Type::Variable(real_type)),
|
||||
);
|
||||
|
||||
// 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_specializations: ResolvedSpecializations::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(
|
||||
&constraints,
|
||||
constr,
|
||||
RigidVariables::default(),
|
||||
test_subs,
|
||||
default_aliases(),
|
||||
abilities_store,
|
||||
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{}", buf);
|
||||
}
|
||||
|
||||
let pretty_type =
|
||||
name_and_print_var(real_type, subs, test_module, interns, DebugPrint::NOTHING);
|
||||
|
||||
assert_eq!(expected_type, pretty_type);
|
||||
}
|
||||
|
||||
fn derive_test<S>(synth_input: S, expected_type: &str, expected_source: &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: mut exposed_encode_types,
|
||||
abilities_store,
|
||||
..
|
||||
} = roc_load_internal::file::load_and_typecheck_str(
|
||||
&arena,
|
||||
encode_path().file_name().unwrap().into(),
|
||||
source,
|
||||
encode_path().parent().unwrap(),
|
||||
Default::default(),
|
||||
target_info,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::AllAvailable,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let test_module = interns.module_id(&ModuleName::from("Test"));
|
||||
let mut test_subs = Subs::new();
|
||||
interns
|
||||
.all_ident_ids
|
||||
.insert(test_module, IdentIds::default());
|
||||
|
||||
let signature_var = synth_input(&mut test_subs);
|
||||
let key = get_key(&test_subs, signature_var).into();
|
||||
|
||||
let mut env = Env {
|
||||
home: test_module,
|
||||
arena: &arena,
|
||||
subs: &mut test_subs,
|
||||
ident_ids: interns.all_ident_ids.get_mut(&test_module).unwrap(),
|
||||
exposed_encode_types: &mut exposed_encode_types,
|
||||
};
|
||||
|
||||
let derived = encoding::derive_to_encoder(&mut env, key);
|
||||
test_module.register_debug_idents(interns.all_ident_ids.get(&test_module).unwrap());
|
||||
|
||||
let ctx = Ctx { interns: &interns };
|
||||
let derived_program = pretty_print(&ctx, &derived);
|
||||
assert_eq!(expected_source, derived_program);
|
||||
|
||||
check_derived_typechecks(
|
||||
derived,
|
||||
test_module,
|
||||
test_subs,
|
||||
&interns,
|
||||
exposed_encode_types,
|
||||
abilities_store,
|
||||
expected_type,
|
||||
);
|
||||
}
|
||||
|
||||
fn get_key(subs: &Subs, var: Variable) -> FlatEncodableKey {
|
||||
match Derived::encoding(subs, var) {
|
||||
Derived::Immediate(_) => unreachable!(),
|
||||
Derived::Key(key) => key.repr,
|
||||
}
|
||||
}
|
||||
|
||||
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, 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)
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
// {{{ hash tests
|
||||
|
||||
test_hash_eq! {
|
||||
same_record:
|
||||
v!({ a: v!(U8), }), v!({ a: v!(U8), })
|
||||
same_record_fields_diff_types:
|
||||
v!({ a: v!(U8), }), v!({ a: v!(STR), })
|
||||
same_record_fields_any_order:
|
||||
v!({ a: v!(U8), b: v!(U8), c: v!(U8), }),
|
||||
v!({ c: v!(U8), a: v!(U8), b: v!(U8), })
|
||||
explicit_empty_record_and_implicit_empty_record:
|
||||
v!(EMPTY_RECORD), v!({})
|
||||
same_record_fields_required_vs_optional:
|
||||
v!({ a: v!(U8), b: v!(U8), }),
|
||||
v!({ ?a: v!(U8), ?b: v!(U8), })
|
||||
|
||||
same_tag_union:
|
||||
v!([ A v!(U8) v!(STR), B v!(STR) ]), v!([ A v!(U8) v!(STR), B v!(STR) ])
|
||||
same_tag_union_tags_diff_types:
|
||||
v!([ A v!(U8) v!(U8), B v!(U8) ]), v!([ A v!(STR) v!(STR), B v!(STR) ])
|
||||
same_tag_union_tags_any_order:
|
||||
v!([ A v!(U8) v!(U8), B v!(U8), C ]), v!([ C, B v!(STR), A v!(STR) v!(STR) ])
|
||||
explicit_empty_tag_union_and_implicit_empty_tag_union:
|
||||
v!(EMPTY_TAG_UNION), v!([])
|
||||
|
||||
same_recursive_tag_union:
|
||||
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)
|
||||
|
||||
list_list_diff_types:
|
||||
v!(Symbol::LIST_LIST v!(STR)), v!(Symbol::LIST_LIST v!(U8))
|
||||
set_set_diff_types:
|
||||
v!(Symbol::SET_SET v!(STR)), v!(Symbol::SET_SET v!(U8))
|
||||
dict_dict_diff_types:
|
||||
v!(Symbol::DICT_DICT v!(STR) v!(STR)), v!(Symbol::DICT_DICT v!(U8) v!(U8))
|
||||
str_str:
|
||||
v!(Symbol::STR_STR), v!(Symbol::STR_STR)
|
||||
|
||||
alias_eq_real_type:
|
||||
v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!([False, True])
|
||||
diff_alias_same_real_type:
|
||||
v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([False, True]))
|
||||
|
||||
opaque_eq_real_type:
|
||||
v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!([False, True])
|
||||
diff_opaque_same_real_type:
|
||||
v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(@Symbol::UNDERSCORE => v!([False, True]))
|
||||
|
||||
opaque_real_type_eq_alias_real_type:
|
||||
v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([False, True]))
|
||||
}
|
||||
|
||||
test_hash_neq! {
|
||||
different_record_fields:
|
||||
v!({ a: v!(U8), }), v!({ b: v!(U8), })
|
||||
record_empty_vs_nonempty:
|
||||
v!(EMPTY_RECORD), v!({ a: v!(U8), })
|
||||
|
||||
different_tag_union_tags:
|
||||
v!([ A v!(U8) ]), v!([ B v!(U8) ])
|
||||
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)
|
||||
|
||||
same_alias_diff_real_type:
|
||||
v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::BOOL_BOOL => v!([ False, True, Maybe ]))
|
||||
diff_alias_diff_real_type:
|
||||
v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([ False, True, Maybe ]))
|
||||
|
||||
same_opaque_diff_real_type:
|
||||
v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(@Symbol::BOOL_BOOL => v!([ False, True, Maybe ]))
|
||||
diff_opaque_diff_real_type:
|
||||
v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(@Symbol::UNDERSCORE => v!([ False, True, Maybe ]))
|
||||
}
|
||||
|
||||
// }}} hash tests
|
||||
|
||||
// {{{ deriver tests
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
derive_test(
|
||||
v!(EMPTY_RECORD),
|
||||
"{} -> Encoder fmt | fmt has EncoderFormatting",
|
||||
indoc!(
|
||||
r#"
|
||||
\Test.0 -> Encode.record []
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_field_record() {
|
||||
derive_test(
|
||||
v!({}),
|
||||
"{} -> Encoder fmt | fmt has EncoderFormatting",
|
||||
indoc!(
|
||||
r#"
|
||||
\Test.0 -> Encode.record []
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_field_record() {
|
||||
derive_test(
|
||||
v!({ a: v!(U8), }),
|
||||
"{ a : val } -> Encoder fmt | fmt has EncoderFormatting, val has Encoding",
|
||||
indoc!(
|
||||
r#"
|
||||
\Test.0 -> Encode.record [{ value: Encode.toEncoder Test.0.a, key: "a", }]
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_field_record() {
|
||||
derive_test(
|
||||
v!({ a: v!(U8), b: v!(STR), }),
|
||||
"{ a : val, b : a } -> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding",
|
||||
indoc!(
|
||||
r#"
|
||||
\Test.0 ->
|
||||
Encode.record [
|
||||
{ value: Encode.toEncoder Test.0.a, key: "a", },
|
||||
{ value: Encode.toEncoder Test.0.b, key: "b", },
|
||||
]
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[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),
|
||||
"[] -> Encoder fmt | fmt has EncoderFormatting",
|
||||
indoc!(
|
||||
r#"
|
||||
\Test.0 -> when Test.0 is
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_one_label_zero_args() {
|
||||
derive_test(
|
||||
v!([A]),
|
||||
"[A] -> Encoder fmt | fmt has EncoderFormatting",
|
||||
indoc!(
|
||||
r#"
|
||||
\Test.0 -> when Test.0 is A -> Encode.tag "A" []
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_one_label_two_args() {
|
||||
derive_test(
|
||||
v!([A v!(U8) v!(STR)]),
|
||||
"[A val a] -> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding",
|
||||
indoc!(
|
||||
r#"
|
||||
\Test.0 ->
|
||||
when Test.0 is
|
||||
A Test.1 Test.2 ->
|
||||
Encode.tag "A" [Encode.toEncoder Test.1, Encode.toEncoder Test.2]
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_two_labels() {
|
||||
derive_test(
|
||||
v!([A v!(U8) v!(STR) v!(U16), B v!(STR)]),
|
||||
"[A val a b, B c] -> Encoder fmt | a has Encoding, b has Encoding, c has Encoding, fmt has EncoderFormatting, val has Encoding",
|
||||
indoc!(
|
||||
r#"
|
||||
\Test.0 ->
|
||||
when Test.0 is
|
||||
A Test.1 Test.2 Test.3 ->
|
||||
Encode.tag "A" [
|
||||
Encode.toEncoder Test.1,
|
||||
Encode.toEncoder Test.2,
|
||||
Encode.toEncoder Test.3,
|
||||
]
|
||||
B Test.4 -> Encode.tag "B" [Encode.toEncoder Test.4]
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_tag_union() {
|
||||
derive_test(
|
||||
v!([Nil, Cons v!(U8) v!(*lst) ] as lst),
|
||||
"[Cons val a, Nil] -> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding",
|
||||
indoc!(
|
||||
r#"
|
||||
\Test.0 ->
|
||||
when Test.0 is
|
||||
Cons Test.1 Test.2 ->
|
||||
Encode.tag "Cons" [Encode.toEncoder Test.1, Encode.toEncoder Test.2]
|
||||
Nil -> Encode.tag "Nil" []
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// }}} deriver tests
|
299
compiler/test_derive/src/pretty_print.rs
Normal file
299
compiler/test_derive/src/pretty_print.rs
Normal file
|
@ -0,0 +1,299 @@
|
|||
//! Pretty-prints the canonical AST back to check our work - do things look reasonable?
|
||||
|
||||
use roc_can::expr::Expr::{self, *};
|
||||
use roc_can::expr::{ClosureData, WhenBranch};
|
||||
use roc_can::pattern::{Pattern, RecordDestruct};
|
||||
|
||||
use roc_module::symbol::Interns;
|
||||
use ven_pretty::{Arena, DocAllocator, DocBuilder};
|
||||
|
||||
pub struct Ctx<'a> {
|
||||
pub interns: &'a Interns,
|
||||
}
|
||||
|
||||
pub fn pretty_print(c: &Ctx, e: &Expr) -> String {
|
||||
let f = Arena::new();
|
||||
expr(c, EPrec::Free, &f, e)
|
||||
.append(f.hardline())
|
||||
.1
|
||||
.pretty(80)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
macro_rules! maybe_paren {
|
||||
($paren_if_above:expr, $my_prec:expr, $doc:expr) => {
|
||||
if $my_prec > $paren_if_above {
|
||||
$doc.parens().group()
|
||||
} else {
|
||||
$doc
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(PartialEq, PartialOrd)]
|
||||
enum EPrec {
|
||||
Free,
|
||||
CallArg,
|
||||
}
|
||||
|
||||
fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> {
|
||||
use EPrec::*;
|
||||
match e {
|
||||
Num(_, n, _, _) | Int(_, _, n, _, _) | Float(_, _, n, _, _) => f.text(&**n),
|
||||
Str(s) => f.text(format!(r#""{}""#, s)),
|
||||
SingleQuote(c) => f.text(format!("'{}'", c)),
|
||||
List {
|
||||
elem_var: _,
|
||||
loc_elems,
|
||||
} => f
|
||||
.reflow("[")
|
||||
.append(
|
||||
f.concat(loc_elems.iter().map(|le| {
|
||||
f.hardline()
|
||||
.append(expr(c, Free, f, &le.value))
|
||||
.append(f.text(","))
|
||||
}))
|
||||
.group()
|
||||
.nest(2),
|
||||
)
|
||||
.append(f.line_())
|
||||
.append("]")
|
||||
.group()
|
||||
.flat_alt(
|
||||
f.reflow("[")
|
||||
.append(f.intersperse(
|
||||
loc_elems.iter().map(|le| expr(c, Free, f, &le.value)),
|
||||
f.reflow(", "),
|
||||
))
|
||||
.append(f.line_())
|
||||
.append("]")
|
||||
.group(),
|
||||
),
|
||||
Var(sym) | AbilityMember(sym, _, _) => f.text(format!(
|
||||
"{}.{}",
|
||||
sym.module_string(c.interns),
|
||||
sym.as_str(c.interns),
|
||||
)),
|
||||
When {
|
||||
loc_cond, branches, ..
|
||||
} => f
|
||||
.reflow("when ")
|
||||
.append(expr(c, Free, f, &loc_cond.value))
|
||||
.append(f.text(" is"))
|
||||
.append(
|
||||
f.concat(branches.iter().map(|b| f.line().append(branch(c, f, b))))
|
||||
.group(),
|
||||
)
|
||||
.nest(2)
|
||||
.group(),
|
||||
If {
|
||||
branches,
|
||||
final_else,
|
||||
..
|
||||
} => f
|
||||
.concat(branches.iter().enumerate().map(|(i, (cond, body))| {
|
||||
let head = if i == 0 { "if " } else { "else if " };
|
||||
(f.reflow(head)
|
||||
.append(expr(c, Free, f, &cond.value))
|
||||
.group()
|
||||
.nest(2))
|
||||
.append(f.line())
|
||||
.append(
|
||||
f.reflow("then")
|
||||
.append(f.softline().append(expr(c, Free, f, &body.value)))
|
||||
.group()
|
||||
.nest(2),
|
||||
)
|
||||
.append(f.line())
|
||||
}))
|
||||
.append(
|
||||
f.reflow("else ")
|
||||
.append(expr(c, Free, f, &final_else.value))
|
||||
.group()
|
||||
.nest(2),
|
||||
)
|
||||
.group(),
|
||||
LetRec(_, _, _) => todo!(),
|
||||
LetNonRec(_, _) => todo!(),
|
||||
Call(fun, args, _) => {
|
||||
let (_, fun, _, _) = &**fun;
|
||||
maybe_paren!(
|
||||
Free,
|
||||
p,
|
||||
expr(c, CallArg, f, &fun.value)
|
||||
.append(f.softline())
|
||||
.append(f.intersperse(
|
||||
args.iter().map(|le| expr(c, CallArg, f, &le.1.value)),
|
||||
f.softline()
|
||||
))
|
||||
.group()
|
||||
)
|
||||
}
|
||||
RunLowLevel { .. } => todo!(),
|
||||
ForeignCall { .. } => todo!(),
|
||||
Closure(ClosureData {
|
||||
arguments,
|
||||
loc_body,
|
||||
..
|
||||
}) => f
|
||||
.text("\\")
|
||||
.append(
|
||||
f.intersperse(
|
||||
arguments
|
||||
.iter()
|
||||
.map(|(_, _, arg)| pattern(c, PPrec::Free, f, &arg.value)),
|
||||
f.text(", "),
|
||||
),
|
||||
)
|
||||
.append(f.text(" ->"))
|
||||
.append(f.line())
|
||||
.append(expr(c, Free, f, &loc_body.value))
|
||||
.nest(2)
|
||||
.group(),
|
||||
Record { fields, .. } => f
|
||||
.reflow("{")
|
||||
.append(
|
||||
f.concat(fields.iter().map(|(name, field)| {
|
||||
let field = f
|
||||
.text(name.as_str())
|
||||
.append(f.reflow(": "))
|
||||
.append(expr(c, Free, f, &field.loc_expr.value))
|
||||
.nest(2)
|
||||
.group();
|
||||
f.line().append(field).append(",")
|
||||
}))
|
||||
.nest(2)
|
||||
.group(),
|
||||
)
|
||||
.append(f.line())
|
||||
.append(f.text("}"))
|
||||
.group(),
|
||||
EmptyRecord => f.text("{}"),
|
||||
Access {
|
||||
loc_expr, field, ..
|
||||
} => expr(c, CallArg, f, &loc_expr.value)
|
||||
.append(f.text(format!(".{}", field.as_str())))
|
||||
.group(),
|
||||
Accessor(_) => todo!(),
|
||||
Update { .. } => todo!(),
|
||||
Tag { .. } => todo!(),
|
||||
ZeroArgumentTag { .. } => todo!(),
|
||||
OpaqueRef { .. } => todo!(),
|
||||
Expect { .. } => todo!(),
|
||||
TypedHole(_) => todo!(),
|
||||
RuntimeError(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn branch<'a>(c: &Ctx, f: &'a Arena<'a>, b: &'a WhenBranch) -> DocBuilder<'a, Arena<'a>> {
|
||||
let WhenBranch {
|
||||
patterns,
|
||||
value,
|
||||
guard,
|
||||
redundant: _,
|
||||
} = b;
|
||||
|
||||
f.intersperse(
|
||||
patterns
|
||||
.iter()
|
||||
.map(|lp| pattern(c, PPrec::Free, f, &lp.value)),
|
||||
f.text(" | "),
|
||||
)
|
||||
.append(match guard {
|
||||
Some(e) => f.text("if ").append(expr(c, EPrec::Free, f, &e.value)),
|
||||
None => f.nil(),
|
||||
})
|
||||
.append(f.text(" ->"))
|
||||
.append(f.line())
|
||||
.append(expr(c, EPrec::Free, f, &value.value))
|
||||
.nest(2)
|
||||
.group()
|
||||
}
|
||||
|
||||
#[derive(PartialEq, PartialOrd)]
|
||||
enum PPrec {
|
||||
Free,
|
||||
AppArg,
|
||||
}
|
||||
|
||||
fn pattern<'a>(
|
||||
c: &Ctx,
|
||||
prec: PPrec,
|
||||
f: &'a Arena<'a>,
|
||||
p: &'a Pattern,
|
||||
) -> DocBuilder<'a, Arena<'a>> {
|
||||
use PPrec::*;
|
||||
use Pattern::*;
|
||||
match p {
|
||||
Identifier(sym)
|
||||
| AbilityMemberSpecialization {
|
||||
specializes: sym, ..
|
||||
} => f.text(format!(
|
||||
"{}.{}",
|
||||
sym.module_string(c.interns),
|
||||
sym.as_str(c.interns),
|
||||
)),
|
||||
AppliedTag {
|
||||
tag_name,
|
||||
arguments,
|
||||
..
|
||||
} => maybe_paren!(
|
||||
Free,
|
||||
prec,
|
||||
f.text(tag_name.0.as_str())
|
||||
.append(if arguments.is_empty() {
|
||||
f.nil()
|
||||
} else {
|
||||
f.space()
|
||||
})
|
||||
.append(
|
||||
f.intersperse(
|
||||
arguments
|
||||
.iter()
|
||||
.map(|(_, lp)| pattern(c, AppArg, f, &lp.value)),
|
||||
f.space(),
|
||||
)
|
||||
)
|
||||
.group()
|
||||
),
|
||||
UnwrappedOpaque {
|
||||
opaque, argument, ..
|
||||
} => f
|
||||
.text(format!("@{} ", opaque.module_string(c.interns)))
|
||||
.append(pattern(c, Free, f, &argument.1.value))
|
||||
.group(),
|
||||
RecordDestructure { destructs, .. } => f
|
||||
.text("{")
|
||||
.append(
|
||||
f.intersperse(
|
||||
destructs.iter().map(|l| &l.value).map(
|
||||
|RecordDestruct { label, typ, .. }| match typ {
|
||||
roc_can::pattern::DestructType::Required => f.text(label.as_str()),
|
||||
roc_can::pattern::DestructType::Optional(_, e) => f
|
||||
.text(label.as_str())
|
||||
.append(f.text(" ? "))
|
||||
.append(expr(c, EPrec::Free, f, &e.value)),
|
||||
roc_can::pattern::DestructType::Guard(_, p) => f
|
||||
.text(label.as_str())
|
||||
.append(f.text(": "))
|
||||
.append(pattern(c, Free, f, &p.value)),
|
||||
},
|
||||
),
|
||||
f.text(", "),
|
||||
),
|
||||
)
|
||||
.append(f.text("}"))
|
||||
.group(),
|
||||
NumLiteral(_, n, _, _) | IntLiteral(_, _, n, _, _) | FloatLiteral(_, _, n, _, _) => {
|
||||
f.text(&**n)
|
||||
}
|
||||
StrLiteral(s) => f.text(format!(r#""{}""#, s)),
|
||||
SingleQuote(c) => f.text(format!("'{}'", c)),
|
||||
Underscore => f.text("_"),
|
||||
|
||||
Shadowed(_, _, _) => todo!(),
|
||||
OpaqueNotInScope(_) => todo!(),
|
||||
UnsupportedPattern(_) => todo!(),
|
||||
MalformedPattern(_, _) => todo!(),
|
||||
}
|
||||
}
|
5
compiler/test_derive/src/tests.rs
Normal file
5
compiler/test_derive/src/tests.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
#![cfg(test)]
|
||||
|
||||
mod encoding;
|
||||
|
||||
mod pretty_print;
|
|
@ -98,11 +98,9 @@ pub fn helper(
|
|||
let main_fn_layout = loaded.entry_point.layout;
|
||||
|
||||
let mut layout_ids = roc_mono::layout::LayoutIds::default();
|
||||
let main_fn_name_base = layout_ids
|
||||
let main_fn_name = layout_ids
|
||||
.get_toplevel(main_fn_symbol, &main_fn_layout)
|
||||
.to_symbol_string(main_fn_symbol, &interns);
|
||||
|
||||
let main_fn_name = format!("roc_{}_exposed", main_fn_name_base);
|
||||
.to_exposed_symbol_string(main_fn_symbol, &interns);
|
||||
|
||||
let mut lines = Vec::new();
|
||||
// errors whose reporting we delay (so we can see that code gen generates runtime errors)
|
||||
|
|
|
@ -1078,14 +1078,6 @@ impl ExhaustiveMark {
|
|||
Self(Variable::EMPTY_TAG_UNION)
|
||||
}
|
||||
|
||||
pub fn variable_for_introduction(&self) -> Variable {
|
||||
debug_assert!(
|
||||
self.0 != Variable::EMPTY_TAG_UNION,
|
||||
"Attempting to introduce known mark"
|
||||
);
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn set_non_exhaustive(&self, subs: &mut Subs) {
|
||||
subs.set_content(self.0, Content::Error);
|
||||
}
|
||||
|
@ -1110,14 +1102,6 @@ impl RedundantMark {
|
|||
Self(Variable::EMPTY_TAG_UNION)
|
||||
}
|
||||
|
||||
pub fn variable_for_introduction(&self) -> Variable {
|
||||
debug_assert!(
|
||||
self.0 != Variable::EMPTY_TAG_UNION,
|
||||
"Attempting to introduce known mark"
|
||||
);
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn set_redundant(&self, subs: &mut Subs) {
|
||||
subs.set_content(self.0, Content::Error);
|
||||
}
|
||||
|
@ -1282,6 +1266,10 @@ define_const_var! {
|
|||
:pub F64,
|
||||
|
||||
:pub DEC,
|
||||
|
||||
// The following are abound in derived abilities, so we cache them.
|
||||
:pub STR,
|
||||
:pub LIST_U8,
|
||||
}
|
||||
|
||||
impl Variable {
|
||||
|
@ -1323,6 +1311,8 @@ impl Variable {
|
|||
|
||||
Symbol::NUM_DEC => Some(Variable::DEC),
|
||||
|
||||
Symbol::STR_STR => Some(Variable::STR),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1699,6 +1689,20 @@ impl Subs {
|
|||
)
|
||||
});
|
||||
|
||||
subs.set_content(Variable::STR, {
|
||||
Content::Structure(FlatType::Apply(
|
||||
Symbol::STR_STR,
|
||||
VariableSubsSlice::default(),
|
||||
))
|
||||
});
|
||||
|
||||
let u8_slice =
|
||||
VariableSubsSlice::insert_into_subs(&mut subs, std::iter::once(Variable::U8));
|
||||
subs.set_content(
|
||||
Variable::LIST_U8,
|
||||
Content::Structure(FlatType::Apply(Symbol::LIST_LIST, u8_slice)),
|
||||
);
|
||||
|
||||
subs
|
||||
}
|
||||
|
||||
|
@ -1743,8 +1747,8 @@ impl Subs {
|
|||
|
||||
/// Unions two keys without the possibility of failure.
|
||||
pub fn union(&mut self, left: Variable, right: Variable, desc: Descriptor) {
|
||||
let l_root = self.utable.inlined_get_root_key(left);
|
||||
let r_root = self.utable.inlined_get_root_key(right);
|
||||
let l_root = self.utable.root_key(left);
|
||||
let r_root = self.utable.root_key(right);
|
||||
|
||||
// NOTE this swapping is intentional! most of our unifying commands are based on the elm
|
||||
// source, but unify_roots is from `ena`, not the elm source. Turns out that they have
|
||||
|
@ -1799,7 +1803,7 @@ impl Subs {
|
|||
|
||||
#[inline(always)]
|
||||
pub fn get_root_key(&mut self, key: Variable) -> Variable {
|
||||
self.utable.inlined_get_root_key(key)
|
||||
self.utable.root_key(key)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -1809,7 +1813,7 @@ impl Subs {
|
|||
|
||||
#[inline(always)]
|
||||
pub fn set(&mut self, key: Variable, r_value: Descriptor) {
|
||||
let l_key = self.utable.inlined_get_root_key(key);
|
||||
let l_key = self.utable.root_key(key);
|
||||
|
||||
// self.utable.update_value(l_key, |node| node.value = r_value);
|
||||
self.utable.set_descriptor(l_key, r_value)
|
||||
|
@ -4607,7 +4611,7 @@ struct CopyImportEnv<'a> {
|
|||
}
|
||||
|
||||
pub fn copy_import_to(
|
||||
source: &mut Subs, // mut to set the copy
|
||||
source: &mut Subs, // mut to set the copy. TODO: use a separate copy table to avoid mut
|
||||
target: &mut Subs,
|
||||
var: Variable,
|
||||
rank: Rank,
|
||||
|
|
|
@ -1,25 +1,46 @@
|
|||
use std::hint::unreachable_unchecked;
|
||||
|
||||
use crate::subs::{Content, Descriptor, Mark, OptVariable, Rank, Variable, VariableSubsSlice};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct UnificationTable {
|
||||
contents: Vec<Content>,
|
||||
ranks: Vec<Rank>,
|
||||
marks: Vec<Mark>,
|
||||
copies: Vec<OptVariable>,
|
||||
redirects: Vec<OptVariable>,
|
||||
metadata: Vec<Combine>,
|
||||
}
|
||||
|
||||
pub(crate) struct Snapshot(UnificationTable);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Combine {
|
||||
Redirect(Variable),
|
||||
Root(Root),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Root {
|
||||
rank: Rank,
|
||||
mark: Mark,
|
||||
copy: OptVariable,
|
||||
}
|
||||
|
||||
impl Root {
|
||||
const NONE: Self = Self {
|
||||
rank: Rank::NONE,
|
||||
mark: Mark::NONE,
|
||||
copy: OptVariable::NONE,
|
||||
};
|
||||
}
|
||||
|
||||
impl Combine {
|
||||
const NONE: Self = Self::Root(Root::NONE);
|
||||
}
|
||||
|
||||
impl UnificationTable {
|
||||
#[allow(unused)]
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
Self {
|
||||
contents: Vec::with_capacity(cap), // vec![Content::Error; cap],
|
||||
ranks: Vec::with_capacity(cap), // vec![Rank::NONE; cap],
|
||||
marks: Vec::with_capacity(cap), // vec![Mark::NONE; cap],
|
||||
copies: Vec::with_capacity(cap), // vec![OptVariable::NONE; cap],
|
||||
redirects: Vec::with_capacity(cap), // vec![OptVariable::NONE; cap],
|
||||
contents: Vec::with_capacity(cap),
|
||||
metadata: Vec::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,12 +59,8 @@ impl UnificationTable {
|
|||
|
||||
self.contents
|
||||
.extend(repeat(Content::FlexVar(None)).take(extra_length));
|
||||
self.ranks.extend(repeat(Rank::NONE).take(extra_length));
|
||||
self.marks.extend(repeat(Mark::NONE).take(extra_length));
|
||||
self.copies
|
||||
.extend(repeat(OptVariable::NONE).take(extra_length));
|
||||
self.redirects
|
||||
.extend(repeat(OptVariable::NONE).take(extra_length));
|
||||
self.metadata
|
||||
.extend(repeat(Combine::NONE).take(extra_length));
|
||||
|
||||
VariableSubsSlice::new(start as _, extra_length as _)
|
||||
}
|
||||
|
@ -58,10 +75,10 @@ impl UnificationTable {
|
|||
let variable = unsafe { Variable::from_index(self.len() as _) };
|
||||
|
||||
self.contents.push(content);
|
||||
self.ranks.push(rank);
|
||||
self.marks.push(mark);
|
||||
self.copies.push(copy);
|
||||
self.redirects.push(OptVariable::NONE);
|
||||
|
||||
let combine = Combine::Root(Root { rank, mark, copy });
|
||||
|
||||
self.metadata.push(combine);
|
||||
|
||||
variable
|
||||
}
|
||||
|
@ -75,53 +92,78 @@ impl UnificationTable {
|
|||
mark: Mark,
|
||||
copy: OptVariable,
|
||||
) {
|
||||
let index = self.root_key(key).index() as usize;
|
||||
let root = self.root_key(key);
|
||||
self.set_unchecked(root, content, rank, mark, copy)
|
||||
}
|
||||
|
||||
pub fn set_unchecked(
|
||||
&mut self,
|
||||
key: Variable,
|
||||
content: Content,
|
||||
rank: Rank,
|
||||
mark: Mark,
|
||||
copy: OptVariable,
|
||||
) {
|
||||
let index = key.index() as usize;
|
||||
|
||||
self.contents[index] = content;
|
||||
self.ranks[index] = rank;
|
||||
self.marks[index] = mark;
|
||||
self.copies[index] = copy;
|
||||
|
||||
self.metadata[index] = Combine::Root(Root { rank, mark, copy });
|
||||
}
|
||||
|
||||
pub fn modify<F, T>(&mut self, key: Variable, mapper: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut Descriptor) -> T,
|
||||
{
|
||||
let index = self.root_key(key).index() as usize;
|
||||
let root_key = self.root_key(key);
|
||||
let index = root_key.index() as usize;
|
||||
|
||||
let root = self.get_root_unchecked(root_key);
|
||||
|
||||
let mut desc = Descriptor {
|
||||
content: self.contents[index],
|
||||
rank: self.ranks[index],
|
||||
mark: self.marks[index],
|
||||
copy: self.copies[index],
|
||||
rank: root.rank,
|
||||
mark: root.mark,
|
||||
copy: root.copy,
|
||||
};
|
||||
|
||||
let result = mapper(&mut desc);
|
||||
|
||||
self.contents[index] = desc.content;
|
||||
self.ranks[index] = desc.rank;
|
||||
self.marks[index] = desc.mark;
|
||||
self.copies[index] = desc.copy;
|
||||
self.set_unchecked(root_key, desc.content, desc.rank, desc.mark, desc.copy);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// GET UNCHECKED
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_root_unchecked(&self, key: Variable) -> Root {
|
||||
match self.metadata[key.index() as usize] {
|
||||
Combine::Root(root) => root,
|
||||
Combine::Redirect(_) => {
|
||||
if cfg!(debug_assertions) {
|
||||
unreachable!("unchecked access on non-root variable {:?}", key);
|
||||
} else {
|
||||
unsafe { unreachable_unchecked() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_rank_unchecked(&self, key: Variable) -> Rank {
|
||||
self.ranks[key.index() as usize]
|
||||
self.get_root_unchecked(key).rank
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_mark_unchecked(&self, key: Variable) -> Mark {
|
||||
self.marks[key.index() as usize]
|
||||
self.get_root_unchecked(key).mark
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[inline(always)]
|
||||
pub fn get_copy_unchecked(&self, key: Variable) -> OptVariable {
|
||||
self.copies[key.index() as usize]
|
||||
self.get_root_unchecked(key).copy
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -131,20 +173,34 @@ impl UnificationTable {
|
|||
|
||||
// GET CHECKED
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_root(&self, key: Variable) -> Root {
|
||||
let root_key = self.root_key_without_compacting(key);
|
||||
match self.metadata[root_key.index() as usize] {
|
||||
Combine::Root(root) => root,
|
||||
Combine::Redirect(_) => {
|
||||
if cfg!(debug_assertions) {
|
||||
unreachable!("the root key {:?} is not actually a root key", root_key);
|
||||
} else {
|
||||
unsafe { unreachable_unchecked() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_rank(&self, key: Variable) -> Rank {
|
||||
self.ranks[self.root_key_without_compacting(key).index() as usize]
|
||||
self.get_root(key).rank
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_mark(&self, key: Variable) -> Mark {
|
||||
self.marks[self.root_key_without_compacting(key).index() as usize]
|
||||
self.get_root(key).mark
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_copy(&self, key: Variable) -> OptVariable {
|
||||
let index = self.root_key_without_compacting(key).index() as usize;
|
||||
self.copies[index]
|
||||
self.get_root(key).copy
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -154,20 +210,37 @@ impl UnificationTable {
|
|||
|
||||
// SET UNCHECKED
|
||||
|
||||
#[inline(always)]
|
||||
pub fn modify_root_unchecked<F, T>(&mut self, key: Variable, f: F) -> T
|
||||
where
|
||||
F: Fn(&mut Root) -> T,
|
||||
{
|
||||
match &mut self.metadata[key.index() as usize] {
|
||||
Combine::Root(root) => f(root),
|
||||
Combine::Redirect(_) => {
|
||||
if cfg!(debug_assertions) {
|
||||
unreachable!("unchecked access on non-root variable {:?}", key);
|
||||
} else {
|
||||
unsafe { unreachable_unchecked() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_rank_unchecked(&mut self, key: Variable, value: Rank) {
|
||||
self.ranks[key.index() as usize] = value;
|
||||
self.modify_root_unchecked(key, |root| root.rank = value)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_mark_unchecked(&mut self, key: Variable, value: Mark) {
|
||||
self.marks[key.index() as usize] = value;
|
||||
self.modify_root_unchecked(key, |root| root.mark = value)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[inline(always)]
|
||||
pub fn set_copy_unchecked(&mut self, key: Variable, value: OptVariable) {
|
||||
self.copies[key.index() as usize] = value;
|
||||
self.modify_root_unchecked(key, |root| root.copy = value)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -180,20 +253,20 @@ impl UnificationTable {
|
|||
|
||||
#[inline(always)]
|
||||
pub fn set_rank(&mut self, key: Variable, value: Rank) {
|
||||
let index = self.root_key(key).index() as usize;
|
||||
self.ranks[index] = value;
|
||||
let root_key = self.root_key(key);
|
||||
self.modify_root_unchecked(root_key, |root| root.rank = value)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_mark(&mut self, key: Variable, value: Mark) {
|
||||
let index = self.root_key(key).index() as usize;
|
||||
self.marks[index] = value;
|
||||
let root_key = self.root_key(key);
|
||||
self.modify_root_unchecked(root_key, |root| root.mark = value)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_copy(&mut self, key: Variable, value: OptVariable) {
|
||||
let index = self.root_key(key).index() as usize;
|
||||
self.copies[index] = value;
|
||||
let root_key = self.root_key(key);
|
||||
self.modify_root_unchecked(root_key, |root| root.copy = value)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -206,22 +279,19 @@ impl UnificationTable {
|
|||
|
||||
#[inline(always)]
|
||||
pub fn root_key(&mut self, mut key: Variable) -> Variable {
|
||||
let index = key.index() as usize;
|
||||
let root = self.root_key_without_compacting(key);
|
||||
|
||||
while let Some(redirect) = self.redirects[key.index() as usize].into_variable() {
|
||||
key = redirect;
|
||||
while let Combine::Redirect(redirect) = &mut self.metadata[key.index() as usize] {
|
||||
key = *redirect;
|
||||
*redirect = root;
|
||||
}
|
||||
|
||||
if index != key.index() as usize {
|
||||
self.redirects[index] = OptVariable::from(key);
|
||||
}
|
||||
|
||||
key
|
||||
root
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn root_key_without_compacting(&self, mut key: Variable) -> Variable {
|
||||
while let Some(redirect) = self.redirects[key.index() as usize].into_variable() {
|
||||
while let Combine::Redirect(redirect) = self.metadata[key.index() as usize] {
|
||||
key = redirect;
|
||||
}
|
||||
|
||||
|
@ -246,7 +316,7 @@ impl UnificationTable {
|
|||
}
|
||||
|
||||
pub fn is_redirect(&self, key: Variable) -> bool {
|
||||
self.redirects[key.index() as usize].is_some()
|
||||
matches!(self.metadata[key.index() as usize], Combine::Redirect(_))
|
||||
}
|
||||
|
||||
pub fn unioned(&mut self, a: Variable, b: Variable) -> bool {
|
||||
|
@ -256,17 +326,13 @@ impl UnificationTable {
|
|||
// custom very specific helpers
|
||||
#[inline(always)]
|
||||
pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank {
|
||||
let index = self.root_key(key).index() as usize;
|
||||
let root_key = self.root_key(key);
|
||||
|
||||
self.marks[index] = mark;
|
||||
self.modify_root_unchecked(root_key, |root| {
|
||||
root.mark = mark;
|
||||
|
||||
self.ranks[index]
|
||||
}
|
||||
|
||||
// TODO remove
|
||||
#[inline(always)]
|
||||
pub fn inlined_get_root_key(&mut self, key: Variable) -> Variable {
|
||||
self.root_key(key)
|
||||
root.rank
|
||||
})
|
||||
}
|
||||
|
||||
/// NOTE: assumes variables are root
|
||||
|
@ -276,34 +342,29 @@ impl UnificationTable {
|
|||
|
||||
// redirect from -> to
|
||||
if from_index != to_index {
|
||||
self.redirects[from_index] = OptVariable::from(to);
|
||||
self.metadata[from_index] = Combine::Redirect(to)
|
||||
}
|
||||
|
||||
// update to's Descriptor
|
||||
self.contents[to_index] = desc.content;
|
||||
self.ranks[to_index] = desc.rank;
|
||||
self.marks[to_index] = desc.mark;
|
||||
self.copies[to_index] = desc.copy;
|
||||
self.set_unchecked(to, desc.content, desc.rank, desc.mark, desc.copy);
|
||||
}
|
||||
|
||||
pub fn get_descriptor(&self, key: Variable) -> Descriptor {
|
||||
let index = self.root_key_without_compacting(key).index() as usize;
|
||||
let root_key = self.root_key_without_compacting(key);
|
||||
let index = root_key.index() as usize;
|
||||
|
||||
let root = self.get_root_unchecked(root_key);
|
||||
|
||||
Descriptor {
|
||||
content: self.contents[index],
|
||||
rank: self.ranks[index],
|
||||
mark: self.marks[index],
|
||||
copy: self.copies[index],
|
||||
rank: root.rank,
|
||||
mark: root.mark,
|
||||
copy: root.copy,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_descriptor(&mut self, key: Variable, desc: Descriptor) {
|
||||
let index = self.root_key(key).index() as usize;
|
||||
|
||||
self.contents[index] = desc.content;
|
||||
self.ranks[index] = desc.rank;
|
||||
self.marks[index] = desc.mark;
|
||||
self.copies[index] = desc.copy;
|
||||
self.set(key, desc.content, desc.rank, desc.mark, desc.copy);
|
||||
}
|
||||
|
||||
pub(crate) fn serialize(
|
||||
|
@ -314,10 +375,37 @@ impl UnificationTable {
|
|||
use crate::subs::Subs;
|
||||
|
||||
written = Subs::serialize_slice(&self.contents, writer, written)?;
|
||||
written = Subs::serialize_slice(&self.ranks, writer, written)?;
|
||||
written = Subs::serialize_slice(&self.marks, writer, written)?;
|
||||
written = Subs::serialize_slice(&self.copies, writer, written)?;
|
||||
written = Subs::serialize_slice(&self.redirects, writer, written)?;
|
||||
|
||||
let mut ranks = Vec::new();
|
||||
let mut marks = Vec::new();
|
||||
let mut copies = Vec::new();
|
||||
let mut redirects = Vec::new();
|
||||
|
||||
for c in self.metadata.iter() {
|
||||
match c {
|
||||
Combine::Redirect(redirect) => {
|
||||
let root = Root::NONE;
|
||||
|
||||
ranks.push(root.rank);
|
||||
marks.push(root.mark);
|
||||
copies.push(root.copy);
|
||||
|
||||
redirects.push(OptVariable::from(*redirect));
|
||||
}
|
||||
Combine::Root(root) => {
|
||||
ranks.push(root.rank);
|
||||
marks.push(root.mark);
|
||||
copies.push(root.copy);
|
||||
|
||||
redirects.push(OptVariable::NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
written = Subs::serialize_slice(&ranks, writer, written)?;
|
||||
written = Subs::serialize_slice(&marks, writer, written)?;
|
||||
written = Subs::serialize_slice(&copies, writer, written)?;
|
||||
written = Subs::serialize_slice(&redirects, writer, written)?;
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
@ -331,12 +419,29 @@ impl UnificationTable {
|
|||
let (copies, offset) = Subs::deserialize_slice::<OptVariable>(bytes, length, offset);
|
||||
let (redirects, offset) = Subs::deserialize_slice::<OptVariable>(bytes, length, offset);
|
||||
|
||||
let mut metadata = Vec::with_capacity(ranks.len());
|
||||
|
||||
let it = ranks
|
||||
.iter()
|
||||
.zip(marks.iter())
|
||||
.zip(copies.iter())
|
||||
.zip(redirects.iter());
|
||||
for (((rank, mark), copy), redirect) in it {
|
||||
match redirect.into_variable() {
|
||||
Some(redirect) => metadata.push(Combine::Redirect(redirect)),
|
||||
None => {
|
||||
metadata.push(Combine::Root(Root {
|
||||
rank: *rank,
|
||||
mark: *mark,
|
||||
copy: *copy,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let this = Self {
|
||||
contents: contents.to_vec(),
|
||||
ranks: ranks.to_vec(),
|
||||
marks: marks.to_vec(),
|
||||
copies: copies.to_vec(),
|
||||
redirects: redirects.to_vec(),
|
||||
metadata,
|
||||
};
|
||||
|
||||
(this, offset)
|
||||
|
|
|
@ -528,7 +528,6 @@ fn unify_two_aliases(
|
|||
|
||||
outcome
|
||||
} else {
|
||||
dbg!(args.len(), other_args.len());
|
||||
mismatch!("{:?}", _symbol)
|
||||
}
|
||||
}
|
||||
|
@ -1130,6 +1129,8 @@ fn unify_shared_fields(
|
|||
let mut matching_fields = Vec::with_capacity(shared_fields.len());
|
||||
let num_shared_fields = shared_fields.len();
|
||||
|
||||
let mut whole_outcome = Outcome::default();
|
||||
|
||||
for (name, (actual, expected)) in shared_fields {
|
||||
let local_outcome = unify_pool(
|
||||
subs,
|
||||
|
@ -1163,6 +1164,7 @@ fn unify_shared_fields(
|
|||
};
|
||||
|
||||
matching_fields.push((name, actual));
|
||||
whole_outcome.union(local_outcome);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1211,7 +1213,9 @@ fn unify_shared_fields(
|
|||
|
||||
let flat_type = FlatType::Record(fields, new_ext_var);
|
||||
|
||||
merge(subs, ctx, Structure(flat_type))
|
||||
let merge_outcome = merge(subs, ctx, Structure(flat_type));
|
||||
whole_outcome.union(merge_outcome);
|
||||
whole_outcome
|
||||
} else {
|
||||
mismatch!("in unify_shared_fields")
|
||||
}
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
# Examples
|
||||
|
||||
Took a look around in this folder; `examples/benchmarks/` contains some larger examples.
|
||||
|
||||
Run examples as follows:
|
||||
|
||||
1. Navigate to the examples directory
|
||||
1. Navigate to this `examples` folder
|
||||
|
||||
```bash
|
||||
cd examples
|
||||
```
|
||||
|
||||
2. Run "Hello, World!" example
|
||||
2. Run a particular example, such as Hello World:
|
||||
|
||||
```bash
|
||||
cargo run hello-world/helloWorld.roc
|
||||
roc run hello-world/main.roc
|
||||
```
|
||||
|
||||
`examples/benchmarks/` contains some larger examples.
|
||||
|
||||
Some examples like `examples/benchmarks/NQueens.roc` require input after running.
|
||||
For NQueens, input 10 in the terminal and press enter.
|
||||
|
|
1
examples/breakout/platform/Cargo.lock
generated
1
examples/breakout/platform/Cargo.lock
generated
|
@ -2091,6 +2091,7 @@ checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
|
|||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"static_assertions 0.1.1",
|
||||
]
|
||||
|
||||
|
|
7
examples/false-interpreter/platform/Cargo.lock
generated
7
examples/false-interpreter/platform/Cargo.lock
generated
|
@ -2,6 +2,12 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||
|
||||
[[package]]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
|
@ -20,6 +26,7 @@ checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
|
|||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
|
|
1
examples/gui/platform/Cargo.lock
generated
1
examples/gui/platform/Cargo.lock
generated
|
@ -2091,6 +2091,7 @@ checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
|
|||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"static_assertions 0.1.1",
|
||||
]
|
||||
|
||||
|
|
1
examples/hello-world/.gitignore
vendored
1
examples/hello-world/.gitignore
vendored
|
@ -3,4 +3,5 @@ helloRust
|
|||
helloSwift
|
||||
helloWorld
|
||||
helloZig
|
||||
hello
|
||||
*.wasm
|
||||
|
|
|
@ -1,44 +1,18 @@
|
|||
# Hello, World!
|
||||
|
||||
To run, `cd` into this directory and run:
|
||||
To run, `cd` into this directory and run this in your terminal:
|
||||
|
||||
```bash
|
||||
cargo run helloWorld.roc
|
||||
roc run
|
||||
```
|
||||
|
||||
To run in release mode instead, do:
|
||||
This will run `main.roc` because, unless you explicitly give it a filename, `roc run`
|
||||
defaults to running a file named `main.roc`. Other `roc` commands (like `roc build`, `roc test`, and so on) also default to `main.roc` unless you explicitly give them a filename.
|
||||
|
||||
```bash
|
||||
cargo run --release helloWorld.roc
|
||||
```
|
||||
# About this example
|
||||
|
||||
## Design Notes
|
||||
This uses a very simple platform which does nothing more than printing the string you give it.
|
||||
|
||||
This demonstrates the basic design of hosts: Roc code gets compiled into a pure
|
||||
function (in this case, a thunk that always returns `"Hello, World!\n"`) and
|
||||
then the host calls that function. Fundamentally, that's the whole idea! The host
|
||||
might not even have a `main` - it could be a library, a plugin, anything.
|
||||
Everything else is built on this basic "hosts calling linked pure functions" design.
|
||||
The line `main = "Hello, World!\n"` sets this string to be `"Hello, World!"` with a newline at the end, and the lines `packages { pf: "c-platform" }` and `provides [main] to pf` specify that the `c-platform/` directory contains this app's platform.
|
||||
|
||||
For example, things get more interesting when the compiled Roc function returns
|
||||
a `Task` - that is, a tagged union data structure containing function pointers
|
||||
to callback closures. This lets the Roc pure function describe arbitrary
|
||||
chainable effects, which the host can interpret to perform I/O as requested by
|
||||
the Roc program. (The tagged union `Task` would have a variant for each supported
|
||||
I/O operation.)
|
||||
|
||||
In this trivial example, it's very easy to line up the API between the host and
|
||||
the Roc program. In a more involved host, this would be much trickier - especially
|
||||
if the API were changing frequently during development.
|
||||
|
||||
The idea there is to have a first-class concept of "glue code" which host authors
|
||||
can write (it would be plain Roc code, but with some extra keywords that aren't
|
||||
available in normal modules - kinda like `port module` in Elm), and which
|
||||
describe both the Roc-host/C boundary as well as the Roc-host/Roc-app boundary.
|
||||
Roc application authors only care about the Roc-host/Roc-app portion, and the
|
||||
host author only cares about the Roc-host/C boundary when implementing the host.
|
||||
|
||||
Using this glue code, the Roc compiler can generate C header files describing the
|
||||
boundary. This not only gets us host compatibility with C compilers, but also
|
||||
Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen)
|
||||
generates correct Rust FFI bindings from C headers.
|
||||
This platform is called `c-platform` because its low-level code is written in C. There's also a `rust-platform`, `zig-platform`, and so on; if you like, you can try switching `pf: "c-platform"` to `pf: "zig-platform"` or `pf: "rust-platform"` to try one of those platforms instead. They all do the same thing, so the application won't look any different, but if you want to start building your own platforms, this Hello World example gives you some very simple platforms to use as starting points too.
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
app "helloWorld"
|
||||
packages { pf: "c-platform" }
|
||||
# To switch platforms, comment-out the line above and un-comment one below.
|
||||
# packages { pf: "rust-platform" }
|
||||
# packages { pf: "swift-platform" }
|
||||
# packages { pf: "web-platform" } # See ./web-platform/README.md
|
||||
# packages { pf: "zig-platform" }
|
||||
imports []
|
||||
provides [main] to pf
|
||||
|
||||
main = "Hello, World!\n"
|
6
examples/hello-world/main.roc
Normal file
6
examples/hello-world/main.roc
Normal file
|
@ -0,0 +1,6 @@
|
|||
app "hello"
|
||||
packages { pf: "c-platform" }
|
||||
imports []
|
||||
provides [main] to pf
|
||||
|
||||
main = "Hello, World!\n"
|
7
examples/hello-world/rust-platform/Cargo.lock
generated
7
examples/hello-world/rust-platform/Cargo.lock
generated
|
@ -2,6 +2,12 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||
|
||||
[[package]]
|
||||
name = "host"
|
||||
version = "0.1.0"
|
||||
|
@ -20,6 +26,7 @@ checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
|
|||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ To run this website, first compile either of these identical apps:
|
|||
cargo run -- build --target=wasm32 examples/hello-world/web-platform/helloWeb.roc
|
||||
|
||||
# Option B: Compile helloWorld.roc with `pf: "web-platform"` and move the result
|
||||
cargo run -- build --target=wasm32 examples/hello-world/helloWorld.roc
|
||||
cargo run -- build --target=wasm32 examples/hello-world/main.roc
|
||||
(cd examples/hello-world && mv helloWorld.wasm web-platform/helloWeb.wasm)
|
||||
```
|
||||
|
||||
|
|
|
@ -732,7 +732,7 @@ test1 =
|
|||
|
||||
#[test]
|
||||
fn test_hello() {
|
||||
let tokens = tokenize(&example_path("hello-world/helloWorld.roc"));
|
||||
let tokens = tokenize(&example_path("hello-world/main.roc"));
|
||||
|
||||
assert_eq!(tokenparser::module(&tokens), Ok(()));
|
||||
}
|
||||
|
|
|
@ -399,7 +399,8 @@ mod test_reporting {
|
|||
assert_eq!(readable, expected_rendering);
|
||||
}
|
||||
|
||||
fn new_report_problem_as(subdir: &str, src: &str, expected_rendering: &str) {
|
||||
/// Do not call this directly! Use the test_report macro below!
|
||||
fn __new_report_problem_as(subdir: &str, src: &str, expected_rendering: &str) {
|
||||
let arena = Bump::new();
|
||||
|
||||
let finalize_render = |doc: RocDocBuilder<'_>, buf: &mut String| {
|
||||
|
@ -420,6 +421,15 @@ mod test_reporting {
|
|||
assert_multiline_str_eq!(expected_rendering, buf.as_str());
|
||||
}
|
||||
|
||||
macro_rules! test_report {
|
||||
($test_name:ident, $program:expr, $output:expr) => {
|
||||
#[test]
|
||||
fn $test_name() {
|
||||
__new_report_problem_as(std::stringify!($test_name), $program, $output)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn human_readable(str: &str) -> String {
|
||||
str.replace(ANSI_STYLE_CODES.red, "<red>")
|
||||
.replace(ANSI_STYLE_CODES.white, "<white>")
|
||||
|
@ -8821,10 +8831,8 @@ All branches in an `if` must have the same type!
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_demands_not_indented_with_first() {
|
||||
new_report_problem_as(
|
||||
"ability_demands_not_indented_with_first",
|
||||
test_report!(
|
||||
ability_demands_not_indented_with_first,
|
||||
indoc!(
|
||||
r#"
|
||||
Eq has
|
||||
|
@ -8846,14 +8854,11 @@ All branches in an `if` must have the same type!
|
|||
^
|
||||
|
||||
I suspect this line is indented too much (by 4 spaces)"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_demand_value_has_args() {
|
||||
new_report_problem_as(
|
||||
"ability_demand_value_has_args",
|
||||
test_report!(
|
||||
ability_demand_value_has_args,
|
||||
indoc!(
|
||||
r#"
|
||||
Eq has
|
||||
|
@ -8874,9 +8879,8 @@ All branches in an `if` must have the same type!
|
|||
|
||||
I was expecting to see a : annotating the signature of this value
|
||||
next."#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_non_signature_expression() {
|
||||
|
@ -9038,10 +9042,8 @@ All branches in an `if` must have the same type!
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_bad_type_parameter() {
|
||||
new_report_problem_as(
|
||||
"ability_bad_type_parameter",
|
||||
test_report!(
|
||||
ability_bad_type_parameter,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [] to "./platform"
|
||||
|
@ -9072,14 +9074,11 @@ All branches in an `if` must have the same type!
|
|||
If you didn't intend on using `Hash` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn alias_in_has_clause() {
|
||||
new_report_problem_as(
|
||||
"alias_in_has_clause",
|
||||
test_report!(
|
||||
alias_in_has_clause,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
@ -9096,14 +9095,11 @@ All branches in an `if` must have the same type!
|
|||
3│ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool
|
||||
^^^^^^^^^
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn shadowed_type_variable_in_has_clause() {
|
||||
new_report_problem_as(
|
||||
"shadowed_type_variable_in_has_clause",
|
||||
test_report!(
|
||||
shadowed_type_variable_in_has_clause,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ab1] to "./platform"
|
||||
|
@ -9128,14 +9124,11 @@ All branches in an `if` must have the same type!
|
|||
Since these variables have the same name, it's easy to use the wrong
|
||||
one on accident. Give one of them a new name.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_shadows_ability() {
|
||||
new_report_problem_as(
|
||||
"ability_shadows_ability",
|
||||
test_report!(
|
||||
ability_shadows_ability,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ab] to "./platform"
|
||||
|
@ -9162,14 +9155,11 @@ All branches in an `if` must have the same type!
|
|||
Since these abilities have the same name, it's easy to use the wrong
|
||||
one on accident. Give one of them a new name.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_member_does_not_bind_ability() {
|
||||
new_report_problem_as(
|
||||
"ability_member_does_not_bind_ability",
|
||||
test_report!(
|
||||
ability_member_does_not_bind_ability,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [] to "./platform"
|
||||
|
@ -9204,14 +9194,11 @@ All branches in an `if` must have the same type!
|
|||
If you didn't intend on using `Ability` then remove it so future readers
|
||||
of your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_member_binds_parent_twice() {
|
||||
new_report_problem_as(
|
||||
"ability_member_binds_parent_twice",
|
||||
test_report!(
|
||||
ability_member_binds_parent_twice,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [] to "./platform"
|
||||
|
@ -9235,14 +9222,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Hint: Did you mean to only bind `a` to `Eq`?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn has_clause_not_on_toplevel() {
|
||||
new_report_problem_as(
|
||||
"has_clause_outside_of_ability",
|
||||
test_report!(
|
||||
has_clause_not_on_toplevel,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [f] to "./platform"
|
||||
|
@ -9278,14 +9262,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Otherwise, the function does not need to be part of the ability!
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_with_non_implementing_type() {
|
||||
new_report_problem_as(
|
||||
"ability_specialization_with_non_implementing_type",
|
||||
test_report!(
|
||||
ability_specialization_with_non_implementing_type,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
@ -9312,14 +9293,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Note: `hash` is a member of `#UserApp.Hash`
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_does_not_match_type() {
|
||||
new_report_problem_as(
|
||||
"ability_specialization_does_not_match_type",
|
||||
test_report!(
|
||||
ability_specialization_does_not_match_type,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
@ -9348,14 +9326,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Id -> U64
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_is_incomplete() {
|
||||
new_report_problem_as(
|
||||
"ability_specialization_is_incomplete",
|
||||
test_report!(
|
||||
ability_specialization_is_incomplete,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [eq, le] to "./platform"
|
||||
|
@ -9381,14 +9356,11 @@ All branches in an `if` must have the same type!
|
|||
5│ le : a, a -> Bool | a has Eq
|
||||
^^
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_overly_generalized() {
|
||||
new_report_problem_as(
|
||||
"ability_specialization_overly_generalized",
|
||||
test_report!(
|
||||
ability_specialization_overly_generalized,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
@ -9423,14 +9395,11 @@ All branches in an `if` must have the same type!
|
|||
generic implementation for this value, perhaps you don't need an
|
||||
ability?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_conflicting_specialization_types() {
|
||||
new_report_problem_as(
|
||||
"ability_specialization_conflicting_specialization_types",
|
||||
test_report!(
|
||||
ability_specialization_conflicting_specialization_types,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [eq] to "./platform"
|
||||
|
@ -9466,14 +9435,11 @@ All branches in an `if` must have the same type!
|
|||
type by wrapping it? If I have an opaque type Age := U32 I can create
|
||||
an instance of this opaque type by doing @Age 23.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_checked_against_annotation() {
|
||||
new_report_problem_as(
|
||||
"ability_specialization_checked_against_annotation",
|
||||
test_report!(
|
||||
ability_specialization_checked_against_annotation,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [hash] to "./platform"
|
||||
|
@ -9520,14 +9486,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Id -> U64
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_called_with_non_specializing() {
|
||||
new_report_problem_as(
|
||||
"ability_specialization_called_with_non_specializing",
|
||||
test_report!(
|
||||
ability_specialization_called_with_non_specializing,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [noGoodVeryBadTerrible] to "./platform"
|
||||
|
@ -9578,14 +9541,11 @@ All branches in an `if` must have the same type!
|
|||
4│ hash : a -> U64 | a has Hash
|
||||
^^^^
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_not_on_toplevel() {
|
||||
new_report_problem_as(
|
||||
"ability_not_on_toplevel",
|
||||
test_report!(
|
||||
ability_not_on_toplevel,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
@ -9608,14 +9568,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Abilities can only be defined on the top-level of a Roc module.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn expression_generalization_to_ability_is_an_error() {
|
||||
new_report_problem_as(
|
||||
"expression_generalization_to_ability_is_an_error",
|
||||
test_report!(
|
||||
expression_generalization_to_ability_is_an_error,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [hash, hashable] to "./platform"
|
||||
|
@ -9654,14 +9611,11 @@ All branches in an `if` must have the same type!
|
|||
specific type. Maybe change the type annotation to be more specific?
|
||||
Maybe change the code to be more general?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn ability_value_annotations_are_an_error() {
|
||||
new_report_problem_as(
|
||||
"ability_value_annotations_are_an_error",
|
||||
test_report!(
|
||||
ability_value_annotations_are_an_error,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [result] to "./platform"
|
||||
|
@ -9711,14 +9665,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
b has Hash
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn branches_have_more_cases_than_condition() {
|
||||
new_report_problem_as(
|
||||
"branches_have_more_cases_than_condition",
|
||||
test_report!(
|
||||
branches_have_more_cases_than_condition,
|
||||
indoc!(
|
||||
r#"
|
||||
foo : Bool -> Str
|
||||
|
@ -9751,9 +9702,8 @@ All branches in an `if` must have the same type!
|
|||
|
||||
The branches must be cases of the `when` condition's type!
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn always_function() {
|
||||
|
@ -9774,10 +9724,8 @@ All branches in an `if` must have the same type!
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn imports_missing_comma() {
|
||||
new_report_problem_as(
|
||||
"imports_missing_comma",
|
||||
test_report!(
|
||||
imports_missing_comma,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test-missing-comma"
|
||||
|
@ -9799,14 +9747,11 @@ All branches in an `if` must have the same type!
|
|||
I am expecting a comma or end of list, like
|
||||
|
||||
imports [Shape, Vector]"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn not_enough_cases_for_open_union() {
|
||||
new_report_problem_as(
|
||||
"branches_have_more_cases_than_condition",
|
||||
test_report!(
|
||||
not_enough_cases_for_open_union,
|
||||
indoc!(
|
||||
r#"
|
||||
foo : [A, B]a -> Str
|
||||
|
@ -9832,14 +9777,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
I would have to crash if I saw one of those! Add branches for them!
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn issue_2778_specialization_is_not_a_redundant_pattern() {
|
||||
new_report_problem_as(
|
||||
"issue_2778_specialization_is_not_a_redundant_pattern",
|
||||
test_report!(
|
||||
issue_2778_specialization_is_not_a_redundant_pattern,
|
||||
indoc!(
|
||||
r#"
|
||||
formatColor = \color ->
|
||||
|
@ -9851,14 +9793,11 @@ All branches in an `if` must have the same type!
|
|||
Red |> formatColor |> Str.concat (formatColor Orange)
|
||||
"#
|
||||
),
|
||||
"", // no problem
|
||||
)
|
||||
}
|
||||
"" // no problem
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn nested_specialization() {
|
||||
new_report_problem_as(
|
||||
"nested_specialization",
|
||||
test_report!(
|
||||
nested_specialization,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
@ -9883,14 +9822,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Specializations can only be defined on the top-level of a module.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn recursion_var_specialization_error() {
|
||||
new_report_problem_as(
|
||||
"recursion_var_specialization_error",
|
||||
test_report!(
|
||||
recursion_var_specialization_error,
|
||||
indoc!(
|
||||
r#"
|
||||
Job a : [Job (List (Job a))]
|
||||
|
@ -9918,14 +9854,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
List [Job ∞] as ∞
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn type_error_in_apply_is_circular() {
|
||||
new_report_problem_as(
|
||||
"type_error_in_apply_is_circular",
|
||||
test_report!(
|
||||
type_error_in_apply_is_circular,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [go] to "./platform"
|
||||
|
@ -9990,14 +9923,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Set ∞
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn cycle_through_non_function() {
|
||||
new_report_problem_as(
|
||||
"cycle_through_non_function",
|
||||
test_report!(
|
||||
cycle_through_non_function,
|
||||
indoc!(
|
||||
r#"
|
||||
force : ({} -> I64) -> I64
|
||||
|
@ -10028,14 +9958,11 @@ All branches in an `if` must have the same type!
|
|||
│ t2
|
||||
└─────┘
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn function_does_not_implement_encoding() {
|
||||
new_report_problem_as(
|
||||
"function_does_not_implement_encoding",
|
||||
test_report!(
|
||||
function_does_not_implement_encoding,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode] provides [main] to "./platform"
|
||||
|
@ -10059,14 +9986,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Note: `Encoding` cannot be generated for functions.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn unbound_type_in_record_does_not_implement_encoding() {
|
||||
new_report_problem_as(
|
||||
"unbound_type_in_record_does_not_implement_encoding",
|
||||
test_report!(
|
||||
unbound_type_in_record_does_not_implement_encoding,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode] provides [main] to "./platform"
|
||||
|
@ -10097,14 +10021,11 @@ All branches in an `if` must have the same type!
|
|||
Tip: This type variable is not bound to `Encoding`. Consider adding a
|
||||
`has` clause to bind the type variable, like `| a has Encode.Encoding`
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn nested_opaque_does_not_implement_encoding() {
|
||||
new_report_problem_as(
|
||||
"nested_opaque_does_not_implement_encoding",
|
||||
test_report!(
|
||||
nested_opaque_does_not_implement_encoding,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode] provides [main] to "./platform"
|
||||
|
@ -10136,14 +10057,11 @@ All branches in an `if` must have the same type!
|
|||
Tip: `A` does not implement `Encoding`. Consider adding a custom
|
||||
implementation or `has Encode.Encoding` to the definition of `A`.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn derive_non_builtin_ability() {
|
||||
new_report_problem_as(
|
||||
"derive_non_builtin_ability",
|
||||
test_report!(
|
||||
derive_non_builtin_ability,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [A] to "./platform"
|
||||
|
@ -10166,14 +10084,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Note: The builtin abilities are `Encode.Encoding`
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn has_encoding_for_function() {
|
||||
new_report_problem_as(
|
||||
"has_encoding_for_function",
|
||||
test_report!(
|
||||
has_encoding_for_function,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode] provides [A] to "./platform"
|
||||
|
@ -10194,14 +10109,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Tip: You can define a custom implementation of `Encode.Encoding` for `A`.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn has_encoding_for_non_encoding_alias() {
|
||||
new_report_problem_as(
|
||||
"has_encoding_for_non_encoding_alias",
|
||||
test_report!(
|
||||
has_encoding_for_non_encoding_alias,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode] provides [A] to "./platform"
|
||||
|
@ -10225,14 +10137,11 @@ All branches in an `if` must have the same type!
|
|||
|
||||
Tip: You can define a custom implementation of `Encode.Encoding` for `A`.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn has_encoding_for_other_has_encoding() {
|
||||
new_report_problem_as(
|
||||
"has_encoding_for_other_has_encoding",
|
||||
test_report!(
|
||||
has_encoding_for_other_has_encoding,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode] provides [A] to "./platform"
|
||||
|
@ -10242,14 +10151,11 @@ All branches in an `if` must have the same type!
|
|||
B := {} has [Encode.Encoding]
|
||||
"#
|
||||
),
|
||||
indoc!(""), // no error
|
||||
)
|
||||
}
|
||||
indoc!("") // no error
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn has_encoding_for_recursive_deriving() {
|
||||
new_report_problem_as(
|
||||
"has_encoding_for_recursive_deriving",
|
||||
test_report!(
|
||||
has_encoding_for_recursive_deriving,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode] provides [MyNat] to "./platform"
|
||||
|
@ -10257,14 +10163,11 @@ All branches in an `if` must have the same type!
|
|||
MyNat := [S MyNat, Z] has [Encode.Encoding]
|
||||
"#
|
||||
),
|
||||
indoc!(""), // no error
|
||||
)
|
||||
}
|
||||
indoc!("") // no error
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn has_encoding_dominated_by_custom() {
|
||||
new_report_problem_as(
|
||||
"has_encoding_dominated_by_custom",
|
||||
test_report!(
|
||||
has_encoding_dominated_by_custom,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Encode.{ Encoding, toEncoder, custom }] provides [A] to "./platform"
|
||||
|
@ -10296,14 +10199,11 @@ All branches in an `if` must have the same type!
|
|||
implementation first, and fall-back on the derived implementation if
|
||||
needed. Make sure to disambiguate which one you want!
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn issue_1755() {
|
||||
new_report_problem_as(
|
||||
"issue_1755",
|
||||
test_report!(
|
||||
issue_1755,
|
||||
indoc!(
|
||||
r#"
|
||||
Handle := {}
|
||||
|
@ -10345,7 +10245,6 @@ All branches in an `if` must have the same type!
|
|||
a `*`! Maybe the annotation on `withOpen` should have a named type
|
||||
variable in place of the `*`?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue