mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-21 15:59:20 +03:00
Merge remote-tracking branch 'origin/trunk' into builtins-in-roc
This commit is contained in:
commit
e112f6ad2c
2
AUTHORS
2
AUTHORS
@ -74,3 +74,5 @@ Ananda Umamil <zweimach@zweimach.org>
|
||||
SylvanSign <jake.d.bray@gmail.com>
|
||||
Nikita Mounier <36044205+nikitamounier@users.noreply.github.com>
|
||||
Cai Bingjun <62678643+C-BJ@users.noreply.github.com>
|
||||
Jared Cone <jared.cone@gmail.com>
|
||||
Sean Hagstrom <sean@seanhagstrom.com>
|
||||
|
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -3351,6 +3351,7 @@ dependencies = [
|
||||
"roc_parse",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
@ -3519,6 +3520,7 @@ dependencies = [
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"snafu",
|
||||
@ -3703,6 +3705,7 @@ dependencies = [
|
||||
"roc_constrain",
|
||||
"roc_load_internal",
|
||||
"roc_module",
|
||||
"roc_reporting",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
]
|
||||
@ -3734,7 +3737,6 @@ dependencies = [
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"strip-ansi-escapes",
|
||||
"tempfile",
|
||||
"ven_pretty",
|
||||
]
|
||||
@ -3893,6 +3895,7 @@ dependencies = [
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_exhaustive",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
@ -3902,6 +3905,7 @@ dependencies = [
|
||||
"roc_target",
|
||||
"roc_test_utils",
|
||||
"roc_types",
|
||||
"tempfile",
|
||||
"ven_pretty",
|
||||
]
|
||||
|
||||
@ -3916,11 +3920,13 @@ dependencies = [
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
@ -3968,6 +3974,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_types",
|
||||
]
|
||||
@ -4518,6 +4525,7 @@ dependencies = [
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_reporting",
|
||||
"roc_target",
|
||||
"test_mono_macros",
|
||||
]
|
||||
|
@ -19,6 +19,7 @@ roc_unify = { path = "../compiler/unify"}
|
||||
roc_load = { path = "../compiler/load" }
|
||||
roc_target = { path = "../compiler/roc_target" }
|
||||
roc_error_macros = { path = "../error_macros" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
arrayvec = "0.7.2"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
page_size = "0.4.2"
|
||||
|
@ -19,6 +19,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule {
|
||||
}),
|
||||
subs_by_module,
|
||||
TargetInfo::default_x86_64(),
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
);
|
||||
|
||||
match loaded {
|
||||
|
@ -227,12 +227,16 @@ fn solve<'a>(
|
||||
);
|
||||
|
||||
match unify(subs, actual, expected, Mode::EQ) {
|
||||
Success(vars) => {
|
||||
Success {
|
||||
vars,
|
||||
must_implement_ability: _,
|
||||
} => {
|
||||
// TODO(abilities) record deferred ability checks
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
state
|
||||
}
|
||||
Failure(vars, actual_type, expected_type) => {
|
||||
Failure(vars, actual_type, expected_type, _bad_impl) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
let problem = TypeError::BadExpr(
|
||||
@ -267,7 +271,7 @@ fn solve<'a>(
|
||||
//
|
||||
// state
|
||||
// }
|
||||
// Failure(vars, _actual_type, _expected_type) => {
|
||||
// Failure(vars, _actual_type, _expected_type, _bad_impl) => {
|
||||
// introduce(subs, rank, pools, &vars);
|
||||
//
|
||||
// // ERROR NOT REPORTED
|
||||
@ -320,13 +324,17 @@ fn solve<'a>(
|
||||
);
|
||||
|
||||
match unify(subs, actual, expected, Mode::EQ) {
|
||||
Success(vars) => {
|
||||
Success {
|
||||
vars,
|
||||
must_implement_ability: _,
|
||||
} => {
|
||||
// TODO(abilities) record deferred ability checks
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
Failure(vars, actual_type, expected_type) => {
|
||||
Failure(vars, actual_type, expected_type, _bad_impl) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
let problem = TypeError::BadExpr(
|
||||
@ -391,12 +399,16 @@ fn solve<'a>(
|
||||
|
||||
// TODO(ayazhafiz): presence constraints for Expr2/Type2
|
||||
match unify(subs, actual, expected, Mode::EQ) {
|
||||
Success(vars) => {
|
||||
Success {
|
||||
vars,
|
||||
must_implement_ability: _,
|
||||
} => {
|
||||
// TODO(abilities) record deferred ability checks
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
state
|
||||
}
|
||||
Failure(vars, actual_type, expected_type) => {
|
||||
Failure(vars, actual_type, expected_type, _bad_impl) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
let problem = TypeError::BadPattern(
|
||||
@ -699,12 +711,16 @@ fn solve<'a>(
|
||||
let includes = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, &tag_ty);
|
||||
|
||||
match unify(subs, actual, includes, Mode::PRESENT) {
|
||||
Success(vars) => {
|
||||
Success {
|
||||
vars,
|
||||
must_implement_ability: _,
|
||||
} => {
|
||||
// TODO(abilities) record deferred ability checks
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
state
|
||||
}
|
||||
Failure(vars, actual_type, expected_type) => {
|
||||
Failure(vars, actual_type, expected_type, _bad_impl) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
// TODO: do we need a better error type here?
|
||||
@ -1281,7 +1297,7 @@ fn adjust_rank_content(
|
||||
use roc_types::subs::FlatType::*;
|
||||
|
||||
match content {
|
||||
FlexVar(_) | RigidVar(_) | Error => group_rank,
|
||||
FlexVar(_) | RigidVar(_) | FlexAbleVar(..) | RigidAbleVar(..) | Error => group_rank,
|
||||
|
||||
RecursionVar { .. } => group_rank,
|
||||
|
||||
@ -1536,7 +1552,7 @@ fn instantiate_rigids_help(
|
||||
};
|
||||
}
|
||||
|
||||
FlexVar(_) | Error => {}
|
||||
FlexVar(_) | FlexAbleVar(_, _) | Error => {}
|
||||
|
||||
RecursionVar { structure, .. } => {
|
||||
instantiate_rigids_help(subs, max_rank, pools, structure);
|
||||
@ -1547,6 +1563,11 @@ fn instantiate_rigids_help(
|
||||
subs.set(copy, make_descriptor(FlexVar(Some(name))));
|
||||
}
|
||||
|
||||
RigidAbleVar(name, ability) => {
|
||||
// what it's all about: convert the rigid var into a flex var
|
||||
subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability)));
|
||||
}
|
||||
|
||||
Alias(_, args, real_type_var, _) => {
|
||||
for var_index in args.all_variables() {
|
||||
let var = subs[var_index];
|
||||
@ -1772,7 +1793,7 @@ fn deep_copy_var_help(
|
||||
copy
|
||||
}
|
||||
|
||||
FlexVar(_) | Error => copy,
|
||||
FlexVar(_) | FlexAbleVar(_, _) | Error => copy,
|
||||
|
||||
RecursionVar {
|
||||
opt_name,
|
||||
@ -1797,6 +1818,12 @@ fn deep_copy_var_help(
|
||||
copy
|
||||
}
|
||||
|
||||
RigidAbleVar(name, ability) => {
|
||||
subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability)));
|
||||
|
||||
copy
|
||||
}
|
||||
|
||||
Alias(symbol, mut args, real_type_var, kind) => {
|
||||
let mut new_args = Vec::with_capacity(args.all_variables().len());
|
||||
|
||||
|
@ -6,6 +6,7 @@ use roc_build::{
|
||||
use roc_builtins::bitcode;
|
||||
use roc_load::LoadingProblem;
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_reporting::report::RenderTarget;
|
||||
use roc_target::TargetInfo;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{Duration, SystemTime};
|
||||
@ -68,6 +69,8 @@ pub fn build_file<'a>(
|
||||
src_dir.as_path(),
|
||||
subs_by_module,
|
||||
target_info,
|
||||
// TODO: expose this from CLI?
|
||||
RenderTarget::ColorTerminal,
|
||||
)?;
|
||||
|
||||
use target_lexicon::Architecture;
|
||||
@ -242,8 +245,11 @@ pub fn build_file<'a>(
|
||||
let link_start = SystemTime::now();
|
||||
let outcome = if surgically_link {
|
||||
roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path)
|
||||
.map_err(|_| {
|
||||
todo!("gracefully handle failing to surgically link");
|
||||
.map_err(|err| {
|
||||
todo!(
|
||||
"gracefully handle failing to surgically link with error: {:?}",
|
||||
err
|
||||
);
|
||||
})?;
|
||||
BuildOutcome::NoProblems
|
||||
} else if matches!(link_type, LinkType::None) {
|
||||
@ -374,6 +380,8 @@ pub fn check_file(
|
||||
src_dir.as_path(),
|
||||
subs_by_module,
|
||||
target_info,
|
||||
// TODO: expose this from CLI?
|
||||
RenderTarget::ColorTerminal,
|
||||
)?;
|
||||
|
||||
let buf = &mut String::with_capacity(1024);
|
||||
|
141
cli/src/lib.rs
141
cli/src/lib.rs
@ -15,7 +15,9 @@ use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::process::Command;
|
||||
use target_lexicon::BinaryFormat;
|
||||
use target_lexicon::{Architecture, OperatingSystem, Triple, X86_32Architecture};
|
||||
use target_lexicon::{
|
||||
Architecture, Environment, OperatingSystem, Triple, Vendor, X86_32Architecture,
|
||||
};
|
||||
|
||||
pub mod build;
|
||||
mod format;
|
||||
@ -38,6 +40,7 @@ pub const FLAG_NO_LINK: &str = "no-link";
|
||||
pub const FLAG_TARGET: &str = "target";
|
||||
pub const FLAG_TIME: &str = "time";
|
||||
pub const FLAG_LINK: &str = "roc-linker";
|
||||
pub const FLAG_LINKER: &str = "linker";
|
||||
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
|
||||
pub const FLAG_VALGRIND: &str = "valgrind";
|
||||
pub const FLAG_CHECK: &str = "check";
|
||||
@ -79,7 +82,7 @@ pub fn build_app<'a>() -> App<'a> {
|
||||
Arg::new(FLAG_TARGET)
|
||||
.long(FLAG_TARGET)
|
||||
.about("Choose a different target")
|
||||
.default_value(Target::default().as_str())
|
||||
.default_value(Target::default().as_str())
|
||||
.possible_values(Target::OPTIONS)
|
||||
.required(false),
|
||||
)
|
||||
@ -110,13 +113,21 @@ pub fn build_app<'a>() -> App<'a> {
|
||||
.arg(
|
||||
Arg::new(FLAG_LINK)
|
||||
.long(FLAG_LINK)
|
||||
.about("Uses the roc linker instead of the system linker.")
|
||||
.about("Deprecated in favor of --linker")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(FLAG_LINKER)
|
||||
.long(FLAG_LINKER)
|
||||
.about("Sets which linker to use. The surgical linker is enabeld by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.")
|
||||
.possible_values(["surgical", "legacy"])
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(FLAG_PRECOMPILED)
|
||||
.long(FLAG_PRECOMPILED)
|
||||
.about("Assumes the host has been precompiled and skips recompiling the host.")
|
||||
.about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)")
|
||||
.possible_values(["true", "false"])
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
@ -207,13 +218,21 @@ pub fn build_app<'a>() -> App<'a> {
|
||||
.arg(
|
||||
Arg::new(FLAG_LINK)
|
||||
.long(FLAG_LINK)
|
||||
.about("Uses the roc linker instead of the system linker.")
|
||||
.about("Deprecated in favor of --linker")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(FLAG_LINKER)
|
||||
.long(FLAG_LINKER)
|
||||
.about("Sets which linker to use. The surgical linker is enabeld by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.")
|
||||
.possible_values(["surgical", "legacy"])
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(FLAG_PRECOMPILED)
|
||||
.long(FLAG_PRECOMPILED)
|
||||
.about("Assumes the host has been precompiled and skips recompiling the host.")
|
||||
.about("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using a --target other than `--target host`)")
|
||||
.possible_values(["true", "false"])
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
@ -307,16 +326,27 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
|
||||
(false, true) => LinkType::None,
|
||||
(false, false) => LinkType::Executable,
|
||||
};
|
||||
let surgically_link = matches.is_present(FLAG_LINK);
|
||||
let precompiled = matches.is_present(FLAG_PRECOMPILED);
|
||||
|
||||
if surgically_link && !roc_linker::supported(&link_type, &triple) {
|
||||
panic!(
|
||||
"Link type, {:?}, with target, {}, not supported by roc linker",
|
||||
link_type, triple
|
||||
);
|
||||
// TODO remove FLAG_LINK from the code base anytime after the end of May 2022
|
||||
if matches.is_present(FLAG_LINK) {
|
||||
eprintln!("ERROR: The --roc-linker flag has been deprecated because the roc linker is now used automatically where it's supported. (Currently that's only x64 Linux.) No need to use --roc-linker anymore, but you can use the --linker flag to switch linkers.");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
// Use surgical linking when supported, or when explicitly requested with --linker surgical
|
||||
let surgically_link = if matches.is_present(FLAG_LINKER) {
|
||||
matches.value_of(FLAG_LINKER) == Some("surgical")
|
||||
} else {
|
||||
roc_linker::supported(&link_type, &triple)
|
||||
};
|
||||
|
||||
let precompiled = if matches.is_present(FLAG_PRECOMPILED) {
|
||||
matches.value_of(FLAG_PRECOMPILED) == Some("true")
|
||||
} else {
|
||||
// When compiling for a different target, default to assuming a precompiled host.
|
||||
// Otherwise compilation would most likely fail!
|
||||
target != Target::System
|
||||
};
|
||||
let path = Path::new(filename);
|
||||
|
||||
// Spawn the root task
|
||||
@ -507,67 +537,76 @@ fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) {
|
||||
println!("Running wasm files not support");
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum Target {
|
||||
Host,
|
||||
X86_32,
|
||||
X86_64,
|
||||
System,
|
||||
Linux32,
|
||||
Linux64,
|
||||
Wasm32,
|
||||
}
|
||||
|
||||
impl Default for Target {
|
||||
fn default() -> Self {
|
||||
Target::Host
|
||||
Target::System
|
||||
}
|
||||
}
|
||||
|
||||
impl Target {
|
||||
const fn as_str(&self) -> &'static str {
|
||||
use Target::*;
|
||||
|
||||
match self {
|
||||
Target::Host => "host",
|
||||
Target::X86_32 => "x86_32",
|
||||
Target::X86_64 => "x86_64",
|
||||
Target::Wasm32 => "wasm32",
|
||||
System => "system",
|
||||
Linux32 => "linux32",
|
||||
Linux64 => "linux64",
|
||||
Wasm32 => "wasm32",
|
||||
}
|
||||
}
|
||||
|
||||
/// NOTE keep up to date!
|
||||
const OPTIONS: &'static [&'static str] = &[
|
||||
Target::Host.as_str(),
|
||||
Target::X86_32.as_str(),
|
||||
Target::X86_64.as_str(),
|
||||
Target::System.as_str(),
|
||||
Target::Linux32.as_str(),
|
||||
Target::Linux64.as_str(),
|
||||
Target::Wasm32.as_str(),
|
||||
];
|
||||
|
||||
fn to_triple(&self) -> Triple {
|
||||
let mut triple = Triple::unknown();
|
||||
fn to_triple(self) -> Triple {
|
||||
use Target::*;
|
||||
|
||||
match self {
|
||||
Target::Host => Triple::host(),
|
||||
Target::X86_32 => {
|
||||
triple.architecture = Architecture::X86_32(X86_32Architecture::I386);
|
||||
triple.binary_format = BinaryFormat::Elf;
|
||||
|
||||
// TODO make this user-specified?
|
||||
triple.operating_system = OperatingSystem::Linux;
|
||||
|
||||
triple
|
||||
}
|
||||
Target::X86_64 => {
|
||||
triple.architecture = Architecture::X86_64;
|
||||
triple.binary_format = BinaryFormat::Elf;
|
||||
|
||||
triple
|
||||
}
|
||||
Target::Wasm32 => {
|
||||
triple.architecture = Architecture::Wasm32;
|
||||
triple.binary_format = BinaryFormat::Wasm;
|
||||
|
||||
triple
|
||||
}
|
||||
System => Triple::host(),
|
||||
Linux32 => Triple {
|
||||
architecture: Architecture::X86_32(X86_32Architecture::I386),
|
||||
vendor: Vendor::Unknown,
|
||||
operating_system: OperatingSystem::Linux,
|
||||
environment: Environment::Musl,
|
||||
binary_format: BinaryFormat::Elf,
|
||||
},
|
||||
Linux64 => Triple {
|
||||
architecture: Architecture::X86_64,
|
||||
vendor: Vendor::Unknown,
|
||||
operating_system: OperatingSystem::Linux,
|
||||
environment: Environment::Musl,
|
||||
binary_format: BinaryFormat::Elf,
|
||||
},
|
||||
Wasm32 => Triple {
|
||||
architecture: Architecture::Wasm32,
|
||||
vendor: Vendor::Unknown,
|
||||
operating_system: OperatingSystem::Unknown,
|
||||
environment: Environment::Unknown,
|
||||
binary_format: BinaryFormat::Wasm,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Target> for Triple {
|
||||
fn from(target: &Target) -> Self {
|
||||
target.to_triple()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Target {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
@ -579,9 +618,9 @@ impl std::str::FromStr for Target {
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"host" => Ok(Target::Host),
|
||||
"x86_32" => Ok(Target::X86_32),
|
||||
"x86_64" => Ok(Target::X86_64),
|
||||
"system" => Ok(Target::System),
|
||||
"linux32" => Ok(Target::Linux32),
|
||||
"linux64" => Ok(Target::Linux64),
|
||||
"wasm32" => Ok(Target::Wasm32),
|
||||
_ => Err(()),
|
||||
}
|
||||
|
@ -2,13 +2,11 @@
|
||||
extern crate pretty_assertions;
|
||||
|
||||
extern crate bumpalo;
|
||||
extern crate indoc;
|
||||
extern crate roc_collections;
|
||||
extern crate roc_load;
|
||||
extern crate roc_module;
|
||||
|
||||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
#[cfg(test)]
|
||||
mod cli_run {
|
||||
use cli_utils::helpers::{
|
||||
@ -25,11 +23,12 @@ mod cli_run {
|
||||
use roc_collections::all::MutMap;
|
||||
|
||||
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
|
||||
const TEST_SURGICAL_LINKER: bool = true;
|
||||
const TEST_LEGACY_LINKER: bool = true;
|
||||
|
||||
// Surgical linker currently only supports linux x86_64.
|
||||
// Surgical linker currently only supports linux x86_64,
|
||||
// so we're always testing the legacy linker on other targets.
|
||||
#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
|
||||
const TEST_SURGICAL_LINKER: bool = false;
|
||||
const TEST_LEGACY_LINKER: bool = false;
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
const ALLOW_VALGRIND: bool = true;
|
||||
@ -228,6 +227,7 @@ mod cli_run {
|
||||
($($test_name:ident:$name:expr => $example:expr,)+) => {
|
||||
$(
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn $test_name() {
|
||||
let dir_name = $name;
|
||||
let example = $example;
|
||||
@ -252,12 +252,7 @@ mod cli_run {
|
||||
}
|
||||
"hello-gui" => {
|
||||
// Since this one requires opening a window, we do `roc build` on it but don't run it.
|
||||
if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
|
||||
// The surgical linker can successfully link this on Linux, but the legacy linker errors!
|
||||
build_example(&file_name, &["--optimize", "--roc-linker"]);
|
||||
} else {
|
||||
build_example(&file_name, &["--optimize"]);
|
||||
}
|
||||
build_example(&file_name, &["--optimize"]);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -288,14 +283,14 @@ mod cli_run {
|
||||
example.use_valgrind,
|
||||
);
|
||||
|
||||
// Also check with the surgical linker.
|
||||
// Also check with the legacy linker.
|
||||
|
||||
if TEST_SURGICAL_LINKER {
|
||||
if TEST_LEGACY_LINKER {
|
||||
check_output_with_stdin(
|
||||
&file_name,
|
||||
example.stdin,
|
||||
example.executable_filename,
|
||||
&["--roc-linker"],
|
||||
&["--linker", "legacy"],
|
||||
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
|
||||
example.expected_ending,
|
||||
example.use_valgrind,
|
||||
|
@ -5,7 +5,7 @@ use inkwell::{
|
||||
};
|
||||
#[cfg(feature = "llvm")]
|
||||
use roc_mono::ir::OptLevel;
|
||||
use target_lexicon::{Architecture, OperatingSystem, Triple};
|
||||
use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
|
||||
|
||||
pub fn target_triple_str(target: &Triple) -> &'static str {
|
||||
// Best guide I've found on how to determine these magic strings:
|
||||
@ -57,11 +57,23 @@ pub fn target_zig_str(target: &Triple) -> &'static str {
|
||||
// and an open proposal to unify them with the more typical "target triples":
|
||||
// https://github.com/ziglang/zig/issues/4911
|
||||
match target {
|
||||
Triple {
|
||||
architecture: Architecture::X86_64,
|
||||
operating_system: OperatingSystem::Linux,
|
||||
environment: Environment::Musl,
|
||||
..
|
||||
} => "x86_64-linux-musl",
|
||||
Triple {
|
||||
architecture: Architecture::X86_64,
|
||||
operating_system: OperatingSystem::Linux,
|
||||
..
|
||||
} => "x86_64-linux-gnu",
|
||||
Triple {
|
||||
architecture: Architecture::X86_32(target_lexicon::X86_32Architecture::I386),
|
||||
operating_system: OperatingSystem::Linux,
|
||||
environment: Environment::Musl,
|
||||
..
|
||||
} => "i386-linux-musl",
|
||||
Triple {
|
||||
architecture: Architecture::X86_32(target_lexicon::X86_32Architecture::I386),
|
||||
operating_system: OperatingSystem::Linux,
|
||||
|
@ -28,12 +28,14 @@ pub fn build(b: *Builder) void {
|
||||
// TODO allow for native target for maximum speed
|
||||
},
|
||||
});
|
||||
const i386_target = makeI386Target();
|
||||
const linux32_target = makeLinux32Target();
|
||||
const linux64_target = makeLinux64Target();
|
||||
const wasm32_target = makeWasm32Target();
|
||||
|
||||
// LLVM IR
|
||||
generateLlvmIrFile(b, mode, host_target, main_path, "ir", "builtins-host");
|
||||
generateLlvmIrFile(b, mode, i386_target, main_path, "ir-i386", "builtins-i386");
|
||||
generateLlvmIrFile(b, mode, linux32_target, main_path, "ir-i386", "builtins-i386");
|
||||
generateLlvmIrFile(b, mode, linux64_target, main_path, "ir-x86_64", "builtins-x86_64");
|
||||
generateLlvmIrFile(b, mode, wasm32_target, main_path, "ir-wasm32", "builtins-wasm32");
|
||||
|
||||
// Generate Object Files
|
||||
@ -87,7 +89,7 @@ fn generateObjectFile(
|
||||
obj_step.dependOn(&obj.step);
|
||||
}
|
||||
|
||||
fn makeI386Target() CrossTarget {
|
||||
fn makeLinux32Target() CrossTarget {
|
||||
var target = CrossTarget.parse(.{}) catch unreachable;
|
||||
|
||||
target.cpu_arch = std.Target.Cpu.Arch.i386;
|
||||
@ -97,6 +99,16 @@ fn makeI386Target() CrossTarget {
|
||||
return target;
|
||||
}
|
||||
|
||||
fn makeLinux64Target() CrossTarget {
|
||||
var target = CrossTarget.parse(.{}) catch unreachable;
|
||||
|
||||
target.cpu_arch = std.Target.Cpu.Arch.x86_64;
|
||||
target.os_tag = std.Target.Os.Tag.linux;
|
||||
target.abi = std.Target.Abi.musl;
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
fn makeWasm32Target() CrossTarget {
|
||||
var target = CrossTarget.parse(.{}) catch unreachable;
|
||||
|
||||
|
@ -468,8 +468,10 @@ fn strFromIntHelp(comptime T: type, int: T) RocStr {
|
||||
const size = comptime blk: {
|
||||
// the string representation of the minimum i128 value uses at most 40 characters
|
||||
var buf: [40]u8 = undefined;
|
||||
var result = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable;
|
||||
break :blk result.len;
|
||||
var resultMin = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable;
|
||||
var resultMax = std.fmt.bufPrint(&buf, "{}", .{std.math.maxInt(T)}) catch unreachable;
|
||||
var result = if (resultMin.len > resultMax.len) resultMin.len else resultMax.len;
|
||||
break :blk result;
|
||||
};
|
||||
|
||||
var buf: [size]u8 = undefined;
|
||||
|
@ -54,6 +54,13 @@ fn main() {
|
||||
"builtins-i386",
|
||||
);
|
||||
|
||||
generate_bc_file(
|
||||
&bitcode_path,
|
||||
&build_script_dir_path,
|
||||
"ir-x86_64",
|
||||
"builtins-x86_64",
|
||||
);
|
||||
|
||||
// OBJECT FILES
|
||||
#[cfg(windows)]
|
||||
const BUILTINS_HOST_FILE: &str = "builtins-host.obj";
|
||||
@ -115,7 +122,12 @@ fn generate_object_file(
|
||||
println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
|
||||
|
||||
// we store this .o file in rust's `target` folder (for wasm we need to leave a copy here too)
|
||||
fs::copy(src_obj, dest_obj).expect("Failed to copy object file.");
|
||||
fs::copy(src_obj, dest_obj).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Failed to copy object file {} to {}: {:?}",
|
||||
src_obj, dest_obj, err
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,16 +316,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
Box::new(SolvedType::Wildcard),
|
||||
);
|
||||
|
||||
// divInt : Int a, Int a -> Int a
|
||||
// divFloor : Int a, Int a -> Int a
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_DIV_INT,
|
||||
Symbol::NUM_DIV_FLOOR,
|
||||
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
|
||||
Box::new(int_type(flex(TVAR1)))
|
||||
);
|
||||
|
||||
// divIntChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
// divFloorChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_DIV_INT_CHECKED,
|
||||
Symbol::NUM_DIV_FLOOR_CHECKED,
|
||||
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
|
||||
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
|
||||
);
|
||||
@ -337,7 +337,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
Box::new(int_type(flex(TVAR1)))
|
||||
);
|
||||
|
||||
//divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
// divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_DIV_CEIL_CHECKED,
|
||||
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
|
||||
|
@ -1,55 +1,77 @@
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::types::Type;
|
||||
use roc_region::all::Region;
|
||||
use roc_types::{subs::Variable, types::Type};
|
||||
|
||||
use crate::annotation::HasClause;
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MemberVariables {
|
||||
pub able_vars: Vec<Variable>,
|
||||
pub rigid_vars: Vec<Variable>,
|
||||
pub flex_vars: Vec<Variable>,
|
||||
}
|
||||
|
||||
/// Stores information about an ability member definition, including the parent ability, the
|
||||
/// defining type, and what type variables need to be instantiated with instances of the ability.
|
||||
#[derive(Debug)]
|
||||
struct AbilityMemberData {
|
||||
#[allow(unused)]
|
||||
parent_ability: Symbol,
|
||||
#[allow(unused)]
|
||||
signature: Type,
|
||||
#[allow(unused)]
|
||||
bound_has_clauses: Vec<HasClause>,
|
||||
// TODO: SoA and put me in an arena
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AbilityMemberData {
|
||||
pub parent_ability: Symbol,
|
||||
pub signature: Type,
|
||||
pub variables: MemberVariables,
|
||||
pub region: Region,
|
||||
}
|
||||
|
||||
/// A particular specialization of an ability member.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MemberSpecialization {
|
||||
pub symbol: Symbol,
|
||||
pub region: Region,
|
||||
}
|
||||
|
||||
/// Stores information about what abilities exist in a scope, what it means to implement an
|
||||
/// ability, and what types implement them.
|
||||
// TODO(abilities): this should probably go on the Scope, I don't put it there for now because we
|
||||
// are only dealing with inter-module abilities for now.
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AbilitiesStore {
|
||||
/// Maps an ability to the members defining it.
|
||||
#[allow(unused)]
|
||||
members_of_ability: MutMap<Symbol, Vec<Symbol>>,
|
||||
|
||||
/// Information about all members composing abilities.
|
||||
ability_members: MutMap<Symbol, AbilityMemberData>,
|
||||
|
||||
/// Tuples of (type, member) specifying that `type` declares an implementation of an ability
|
||||
/// member `member`.
|
||||
#[allow(unused)]
|
||||
declared_implementations: MutSet<(Symbol, Symbol)>,
|
||||
/// Map of symbols that specialize an ability member to the root ability symbol name.
|
||||
/// For example, for the program
|
||||
/// Hash has hash : a -> U64 | a has Hash
|
||||
/// ^^^^ gets the symbol "#hash"
|
||||
/// hash = \@Id n -> n
|
||||
/// ^^^^ gets the symbol "#hash1"
|
||||
///
|
||||
/// We keep the mapping #hash1->#hash
|
||||
specialization_to_root: MutMap<Symbol, Symbol>,
|
||||
|
||||
/// Maps a tuple (member, type) specifying that `type` declares an implementation of an ability
|
||||
/// member `member`, to the exact symbol that implements the ability.
|
||||
declared_specializations: MutMap<(Symbol, Symbol), MemberSpecialization>,
|
||||
}
|
||||
|
||||
impl AbilitiesStore {
|
||||
/// Records the definition of an ability, including its members.
|
||||
pub fn register_ability(
|
||||
&mut self,
|
||||
ability: Symbol,
|
||||
members: Vec<(Symbol, Type, Vec<HasClause>)>,
|
||||
members: Vec<(Symbol, Region, Type, MemberVariables)>,
|
||||
) {
|
||||
let mut members_vec = Vec::with_capacity(members.len());
|
||||
for (member, signature, bound_has_clauses) in members.into_iter() {
|
||||
for (member, region, signature, variables) in members.into_iter() {
|
||||
members_vec.push(member);
|
||||
let old_member = self.ability_members.insert(
|
||||
member,
|
||||
AbilityMemberData {
|
||||
parent_ability: ability,
|
||||
signature,
|
||||
bound_has_clauses,
|
||||
region,
|
||||
variables,
|
||||
},
|
||||
);
|
||||
debug_assert!(old_member.is_none(), "Replacing existing member definition");
|
||||
@ -61,14 +83,83 @@ impl AbilitiesStore {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn register_implementation(&mut self, implementing_type: Symbol, ability_member: Symbol) {
|
||||
let old_impl = self
|
||||
.declared_implementations
|
||||
.insert((implementing_type, ability_member));
|
||||
debug_assert!(!old_impl, "Replacing existing implementation");
|
||||
/// Records a specialization of `ability_member` with specialized type `implementing_type`.
|
||||
/// Entries via this function are considered a source of truth. It must be ensured that a
|
||||
/// specialization is validated before being registered here.
|
||||
pub fn register_specialization_for_type(
|
||||
&mut self,
|
||||
ability_member: Symbol,
|
||||
implementing_type: Symbol,
|
||||
specialization: MemberSpecialization,
|
||||
) {
|
||||
let old_spec = self
|
||||
.declared_specializations
|
||||
.insert((ability_member, implementing_type), specialization);
|
||||
debug_assert!(old_spec.is_none(), "Replacing existing specialization");
|
||||
}
|
||||
|
||||
/// Checks if `name` is a root ability member symbol name.
|
||||
/// Note that this will return `false` for specializations of an ability member, which have
|
||||
/// different symbols from the root.
|
||||
pub fn is_ability_member_name(&self, name: Symbol) -> bool {
|
||||
self.ability_members.contains_key(&name)
|
||||
}
|
||||
|
||||
/// Returns information about all known ability members and their root symbols.
|
||||
pub fn root_ability_members(&self) -> &MutMap<Symbol, AbilityMemberData> {
|
||||
&self.ability_members
|
||||
}
|
||||
|
||||
/// Records that the symbol `specializing_symbol` claims to specialize `ability_member`; for
|
||||
/// example the symbol of `hash : Id -> U64` specializing `hash : a -> U64 | a has Hash`.
|
||||
pub fn register_specializing_symbol(
|
||||
&mut self,
|
||||
specializing_symbol: Symbol,
|
||||
ability_member: Symbol,
|
||||
) {
|
||||
self.specialization_to_root
|
||||
.insert(specializing_symbol, ability_member);
|
||||
}
|
||||
|
||||
/// Returns whether a symbol is declared to specialize an ability member.
|
||||
pub fn is_specialization_name(&self, symbol: Symbol) -> bool {
|
||||
self.specialization_to_root.contains_key(&symbol)
|
||||
}
|
||||
|
||||
/// Finds the symbol name and ability member definition for a symbol specializing the ability
|
||||
/// member, if it specializes any.
|
||||
/// For example, suppose `hash : Id -> U64` has symbol #hash1 and specializes
|
||||
/// `hash : a -> U64 | a has Hash` with symbol #hash. Calling this with #hash1 would retrieve
|
||||
/// the ability member data for #hash.
|
||||
pub fn root_name_and_def(
|
||||
&self,
|
||||
specializing_symbol: Symbol,
|
||||
) -> Option<(Symbol, &AbilityMemberData)> {
|
||||
let root_symbol = self.specialization_to_root.get(&specializing_symbol)?;
|
||||
debug_assert!(self.ability_members.contains_key(root_symbol));
|
||||
let root_data = self.ability_members.get(root_symbol).unwrap();
|
||||
Some((*root_symbol, root_data))
|
||||
}
|
||||
|
||||
/// Finds the ability member definition for a member name.
|
||||
pub fn member_def(&self, member: Symbol) -> Option<&AbilityMemberData> {
|
||||
self.ability_members.get(&member)
|
||||
}
|
||||
|
||||
/// Returns an iterator over pairs (ability member, type) specifying that
|
||||
/// "ability member" has a specialization with type "type".
|
||||
pub fn get_known_specializations(&self) -> impl Iterator<Item = (Symbol, Symbol)> + '_ {
|
||||
self.declared_specializations.keys().copied()
|
||||
}
|
||||
|
||||
/// Retrieves the specialization of `member` for `typ`, if it exists.
|
||||
pub fn get_specialization(&self, member: Symbol, typ: Symbol) -> Option<MemberSpecialization> {
|
||||
self.declared_specializations.get(&(member, typ)).copied()
|
||||
}
|
||||
|
||||
/// Returns pairs of (type, ability member) specifying that "ability member" has a
|
||||
/// specialization with type "type".
|
||||
pub fn members_of_ability(&self, ability: Symbol) -> Option<&[Symbol]> {
|
||||
self.members_of_ability.get(&ability).map(|v| v.as_ref())
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,22 @@ pub struct Annotation {
|
||||
pub aliases: SendMap<Symbol, Alias>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum NamedOrAbleVariable<'a> {
|
||||
Named(&'a NamedVariable),
|
||||
Able(&'a AbleVariable),
|
||||
}
|
||||
|
||||
impl<'a> NamedOrAbleVariable<'a> {
|
||||
pub fn first_seen(&self) -> Region {
|
||||
match self {
|
||||
NamedOrAbleVariable::Named(nv) => nv.first_seen,
|
||||
NamedOrAbleVariable::Able(av) => av.first_seen,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A named type variable, not bound to an ability.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct NamedVariable {
|
||||
pub variable: Variable,
|
||||
@ -27,21 +43,40 @@ pub struct NamedVariable {
|
||||
pub first_seen: Region,
|
||||
}
|
||||
|
||||
/// A type variable bound to an ability, like "a has Hash".
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct AbleVariable {
|
||||
pub variable: Variable,
|
||||
pub name: Lowercase,
|
||||
pub ability: Symbol,
|
||||
// NB: there may be multiple occurrences of a variable
|
||||
pub first_seen: Region,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
pub struct IntroducedVariables {
|
||||
pub wildcards: Vec<Loc<Variable>>,
|
||||
pub lambda_sets: Vec<Variable>,
|
||||
pub inferred: Vec<Loc<Variable>>,
|
||||
pub named: Vec<NamedVariable>,
|
||||
pub able: Vec<AbleVariable>,
|
||||
pub host_exposed_aliases: MutMap<Symbol, Variable>,
|
||||
}
|
||||
|
||||
impl IntroducedVariables {
|
||||
#[inline(always)]
|
||||
fn debug_assert_not_already_present(&self, var: Variable) {
|
||||
debug_assert!((self.wildcards.iter().map(|v| &v.value))
|
||||
.chain(self.lambda_sets.iter())
|
||||
.chain(self.inferred.iter().map(|v| &v.value))
|
||||
.chain(self.named.iter().map(|nv| &nv.variable))
|
||||
.chain(self.able.iter().map(|av| &av.variable))
|
||||
.chain(self.host_exposed_aliases.values())
|
||||
.all(|&v| v != var));
|
||||
}
|
||||
|
||||
pub fn insert_named(&mut self, name: Lowercase, var: Loc<Variable>) {
|
||||
debug_assert!(!self
|
||||
.named
|
||||
.iter()
|
||||
.any(|nv| nv.name == name || nv.variable == var.value));
|
||||
self.debug_assert_not_already_present(var.value);
|
||||
|
||||
let named_variable = NamedVariable {
|
||||
name,
|
||||
@ -52,19 +87,36 @@ impl IntroducedVariables {
|
||||
self.named.push(named_variable);
|
||||
}
|
||||
|
||||
pub fn insert_able(&mut self, name: Lowercase, var: Loc<Variable>, ability: Symbol) {
|
||||
self.debug_assert_not_already_present(var.value);
|
||||
|
||||
let able_variable = AbleVariable {
|
||||
name,
|
||||
ability,
|
||||
variable: var.value,
|
||||
first_seen: var.region,
|
||||
};
|
||||
|
||||
self.able.push(able_variable);
|
||||
}
|
||||
|
||||
pub fn insert_wildcard(&mut self, var: Loc<Variable>) {
|
||||
self.debug_assert_not_already_present(var.value);
|
||||
self.wildcards.push(var);
|
||||
}
|
||||
|
||||
pub fn insert_inferred(&mut self, var: Loc<Variable>) {
|
||||
self.debug_assert_not_already_present(var.value);
|
||||
self.inferred.push(var);
|
||||
}
|
||||
|
||||
fn insert_lambda_set(&mut self, var: Variable) {
|
||||
self.debug_assert_not_already_present(var);
|
||||
self.lambda_sets.push(var);
|
||||
}
|
||||
|
||||
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
|
||||
self.debug_assert_not_already_present(var);
|
||||
self.host_exposed_aliases.insert(symbol, var);
|
||||
}
|
||||
|
||||
@ -78,6 +130,10 @@ impl IntroducedVariables {
|
||||
self.named.extend(other.named.iter().cloned());
|
||||
self.named.sort();
|
||||
self.named.dedup();
|
||||
|
||||
self.able.extend(other.able.iter().cloned());
|
||||
self.able.sort();
|
||||
self.able.dedup();
|
||||
}
|
||||
|
||||
pub fn union_owned(&mut self, other: Self) {
|
||||
@ -91,22 +147,42 @@ impl IntroducedVariables {
|
||||
self.named.dedup();
|
||||
}
|
||||
|
||||
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> {
|
||||
self.named
|
||||
pub fn var_by_name(&self, name: &Lowercase) -> Option<Variable> {
|
||||
(self.named.iter().map(|nv| (&nv.name, nv.variable)))
|
||||
.chain(self.able.iter().map(|av| (&av.name, av.variable)))
|
||||
.find(|(cand, _)| cand == &name)
|
||||
.map(|(_, var)| var)
|
||||
}
|
||||
|
||||
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<NamedOrAbleVariable> {
|
||||
if let Some(nav) = self
|
||||
.named
|
||||
.iter()
|
||||
.find(|nv| &nv.name == name)
|
||||
.map(|nv| &nv.variable)
|
||||
}
|
||||
|
||||
pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
|
||||
self.named
|
||||
.map(NamedOrAbleVariable::Named)
|
||||
{
|
||||
return Some(nav);
|
||||
}
|
||||
self.able
|
||||
.iter()
|
||||
.find(|nv| nv.variable == var)
|
||||
.map(|nv| &nv.name)
|
||||
.find(|av| &av.name == name)
|
||||
.map(NamedOrAbleVariable::Able)
|
||||
}
|
||||
|
||||
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<&NamedVariable> {
|
||||
self.named.iter().find(|nv| &nv.name == name)
|
||||
pub fn collect_able(&self) -> Vec<Variable> {
|
||||
self.able.iter().map(|av| av.variable).collect()
|
||||
}
|
||||
|
||||
pub fn collect_rigid(&self) -> Vec<Variable> {
|
||||
(self.named.iter().map(|nv| nv.variable))
|
||||
.chain(self.wildcards.iter().map(|wc| wc.value))
|
||||
// For our purposes, lambda set vars are treated like rigids
|
||||
.chain(self.lambda_sets.iter().copied())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn collect_flex(&self) -> Vec<Variable> {
|
||||
self.inferred.iter().map(|iv| iv.value).collect()
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,13 +223,6 @@ pub fn canonicalize_annotation(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HasClause {
|
||||
pub var_name: Lowercase,
|
||||
pub var: Variable,
|
||||
pub ability: Symbol,
|
||||
}
|
||||
|
||||
pub fn canonicalize_annotation_with_possible_clauses(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
@ -161,16 +230,17 @@ pub fn canonicalize_annotation_with_possible_clauses(
|
||||
region: Region,
|
||||
var_store: &mut VarStore,
|
||||
abilities_in_scope: &[Symbol],
|
||||
) -> (Annotation, Vec<Loc<HasClause>>) {
|
||||
) -> Annotation {
|
||||
let mut introduced_variables = IntroducedVariables::default();
|
||||
let mut references = MutSet::default();
|
||||
let mut aliases = SendMap::default();
|
||||
|
||||
let (annotation, region, clauses) = match annotation {
|
||||
let (annotation, region) = match annotation {
|
||||
TypeAnnotation::Where(annotation, clauses) => {
|
||||
let mut can_clauses = Vec::with_capacity(clauses.len());
|
||||
// Add each "has" clause. The association of a variable to an ability will be saved on
|
||||
// `introduced_variables`, which we'll process later.
|
||||
for clause in clauses.iter() {
|
||||
match canonicalize_has_clause(
|
||||
let opt_err = canonicalize_has_clause(
|
||||
env,
|
||||
scope,
|
||||
var_store,
|
||||
@ -178,24 +248,19 @@ pub fn canonicalize_annotation_with_possible_clauses(
|
||||
clause,
|
||||
abilities_in_scope,
|
||||
&mut references,
|
||||
) {
|
||||
Ok(result) => can_clauses.push(Loc::at(clause.region, result)),
|
||||
Err(err_type) => {
|
||||
return (
|
||||
Annotation {
|
||||
typ: err_type,
|
||||
introduced_variables,
|
||||
references,
|
||||
aliases,
|
||||
},
|
||||
can_clauses,
|
||||
)
|
||||
}
|
||||
};
|
||||
);
|
||||
if let Err(err_type) = opt_err {
|
||||
return Annotation {
|
||||
typ: err_type,
|
||||
introduced_variables,
|
||||
references,
|
||||
aliases,
|
||||
};
|
||||
}
|
||||
}
|
||||
(&annotation.value, annotation.region, can_clauses)
|
||||
(&annotation.value, annotation.region)
|
||||
}
|
||||
annot => (annot, region, vec![]),
|
||||
annot => (annot, region),
|
||||
};
|
||||
|
||||
let typ = can_annotation_help(
|
||||
@ -209,14 +274,12 @@ pub fn canonicalize_annotation_with_possible_clauses(
|
||||
&mut references,
|
||||
);
|
||||
|
||||
let annot = Annotation {
|
||||
Annotation {
|
||||
typ,
|
||||
introduced_variables,
|
||||
references,
|
||||
aliases,
|
||||
};
|
||||
|
||||
(annot, clauses)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_apply_symbol(
|
||||
@ -502,7 +565,7 @@ fn can_annotation_help(
|
||||
let name = Lowercase::from(*v);
|
||||
|
||||
match introduced_variables.var_by_name(&name) {
|
||||
Some(var) => Type::Variable(*var),
|
||||
Some(var) => Type::Variable(var),
|
||||
None => {
|
||||
let var = var_store.fresh();
|
||||
|
||||
@ -566,8 +629,8 @@ fn can_annotation_help(
|
||||
let var_name = Lowercase::from(var);
|
||||
|
||||
if let Some(var) = introduced_variables.var_by_name(&var_name) {
|
||||
vars.push((var_name.clone(), Type::Variable(*var)));
|
||||
lowercase_vars.push(Loc::at(loc_var.region, (var_name, *var)));
|
||||
vars.push((var_name.clone(), Type::Variable(var)));
|
||||
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
|
||||
} else {
|
||||
let var = var_store.fresh();
|
||||
|
||||
@ -799,7 +862,7 @@ fn canonicalize_has_clause(
|
||||
clause: &Loc<roc_parse::ast::HasClause<'_>>,
|
||||
abilities_in_scope: &[Symbol],
|
||||
references: &mut MutSet<Symbol>,
|
||||
) -> Result<HasClause, Type> {
|
||||
) -> Result<(), Type> {
|
||||
let Loc {
|
||||
region,
|
||||
value: roc_parse::ast::HasClause { var, ability },
|
||||
@ -836,25 +899,21 @@ fn canonicalize_has_clause(
|
||||
let var_name_ident = var_name.to_string().into();
|
||||
let shadow = Loc::at(region, var_name_ident);
|
||||
env.problem(roc_problem::can::Problem::Shadowing {
|
||||
original_region: shadowing.first_seen,
|
||||
original_region: shadowing.first_seen(),
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
});
|
||||
return Err(Type::Erroneous(Problem::Shadowed(
|
||||
shadowing.first_seen,
|
||||
shadowing.first_seen(),
|
||||
shadow,
|
||||
)));
|
||||
}
|
||||
|
||||
let var = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_named(var_name.clone(), Loc::at(region, var));
|
||||
introduced_variables.insert_able(var_name, Loc::at(region, var), ability);
|
||||
|
||||
Ok(HasClause {
|
||||
var_name,
|
||||
var,
|
||||
ability,
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -1105,7 +1164,7 @@ fn can_assigned_fields<'a>(
|
||||
let field_name = Lowercase::from(loc_field_name.value);
|
||||
let field_type = {
|
||||
if let Some(var) = introduced_variables.var_by_name(&field_name) {
|
||||
Type::Variable(*var)
|
||||
Type::Variable(var)
|
||||
} else {
|
||||
let field_var = var_store.fresh();
|
||||
introduced_variables.insert_named(
|
||||
|
@ -197,8 +197,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||
NUM_TAN => num_tan,
|
||||
NUM_DIV_FLOAT => num_div_float,
|
||||
NUM_DIV_FLOAT_CHECKED => num_div_float_checked,
|
||||
NUM_DIV_INT => num_div_int,
|
||||
NUM_DIV_INT_CHECKED => num_div_int_checked,
|
||||
NUM_DIV_FLOOR => num_div_floor,
|
||||
NUM_DIV_FLOOR_CHECKED => num_div_floor_checked,
|
||||
NUM_DIV_CEIL => num_div_ceil,
|
||||
NUM_DIV_CEIL_CHECKED => num_div_ceil_checked,
|
||||
NUM_ABS => num_abs,
|
||||
@ -4257,13 +4257,13 @@ fn num_div_float_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.div : Int a, Int a -> Int a
|
||||
fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
/// Num.divFloor : Int a, Int a -> Int a
|
||||
fn num_div_floor(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
num_binop(symbol, var_store, LowLevel::NumDivUnchecked)
|
||||
}
|
||||
|
||||
/// Num.divChecked : Int a , Int a -> Result (Int a) [ DivByZero ]*
|
||||
fn num_div_int_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
/// Num.divFloorChecked : Int a , Int a -> Result (Int a) [ DivByZero ]*
|
||||
fn num_div_floor_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let bool_var = var_store.fresh();
|
||||
let num_var = var_store.fresh();
|
||||
let unbound_zero_var = var_store.fresh();
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::abilities::AbilitiesStore;
|
||||
use crate::abilities::MemberVariables;
|
||||
use crate::annotation::canonicalize_annotation;
|
||||
use crate::annotation::canonicalize_annotation_with_possible_clauses;
|
||||
use crate::annotation::IntroducedVariables;
|
||||
@ -430,12 +430,11 @@ pub fn canonicalize_defs<'a>(
|
||||
}
|
||||
|
||||
// Now we can go through and resolve all pending abilities, to add them to scope.
|
||||
let mut abilities_store = AbilitiesStore::default();
|
||||
for (loc_ability_name, members) in abilities.into_values() {
|
||||
let mut can_members = Vec::with_capacity(members.len());
|
||||
|
||||
for member in members {
|
||||
let (member_annot, clauses) = canonicalize_annotation_with_possible_clauses(
|
||||
let member_annot = canonicalize_annotation_with_possible_clauses(
|
||||
env,
|
||||
&mut scope,
|
||||
&member.typ.value,
|
||||
@ -450,13 +449,14 @@ pub fn canonicalize_defs<'a>(
|
||||
output.references.referenced_type_defs.insert(symbol);
|
||||
}
|
||||
|
||||
let name_region = member.name.region;
|
||||
let member_name = member.name.extract_spaces().item;
|
||||
|
||||
let member_sym = match scope.introduce(
|
||||
member_name.into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
member.name.region,
|
||||
name_region,
|
||||
) {
|
||||
Ok(sym) => sym,
|
||||
Err((original_region, shadow, _new_symbol)) => {
|
||||
@ -473,9 +473,11 @@ pub fn canonicalize_defs<'a>(
|
||||
// What variables in the annotation are bound to the parent ability, and what variables
|
||||
// are bound to some other ability?
|
||||
let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) =
|
||||
clauses
|
||||
.into_iter()
|
||||
.partition(|has_clause| has_clause.value.ability == loc_ability_name.value);
|
||||
member_annot
|
||||
.introduced_variables
|
||||
.able
|
||||
.iter()
|
||||
.partition(|av| av.ability == loc_ability_name.value);
|
||||
|
||||
let mut bad_has_clauses = false;
|
||||
|
||||
@ -485,18 +487,38 @@ pub fn canonicalize_defs<'a>(
|
||||
env.problem(Problem::AbilityMemberMissingHasClause {
|
||||
member: member_sym,
|
||||
ability: loc_ability_name.value,
|
||||
region: member.name.region,
|
||||
region: name_region,
|
||||
});
|
||||
bad_has_clauses = true;
|
||||
}
|
||||
|
||||
if variables_bound_to_ability.len() > 1 {
|
||||
// There is more than one variable bound to the member signature, so something like
|
||||
// Eq has eq : a, b -> Bool | a has Eq, b has Eq
|
||||
// We have no way of telling what type implements a particular instance of Eq in
|
||||
// this case (a or b?), so disallow it.
|
||||
let span_has_clauses =
|
||||
Region::across_all(variables_bound_to_ability.iter().map(|v| &v.first_seen));
|
||||
let bound_var_names = variables_bound_to_ability
|
||||
.iter()
|
||||
.map(|v| v.name.clone())
|
||||
.collect();
|
||||
env.problem(Problem::AbilityMemberMultipleBoundVars {
|
||||
member: member_sym,
|
||||
ability: loc_ability_name.value,
|
||||
span_has_clauses,
|
||||
bound_var_names,
|
||||
});
|
||||
bad_has_clauses = true;
|
||||
}
|
||||
|
||||
if !variables_bound_to_other_abilities.is_empty() {
|
||||
// Disallow variables bound to other abilities, for now.
|
||||
for bad_clause in variables_bound_to_other_abilities.iter() {
|
||||
for bad_variable in variables_bound_to_other_abilities.iter() {
|
||||
env.problem(Problem::AbilityMemberBindsExternalAbility {
|
||||
member: member_sym,
|
||||
ability: loc_ability_name.value,
|
||||
region: bad_clause.region,
|
||||
region: bad_variable.first_seen,
|
||||
});
|
||||
}
|
||||
bad_has_clauses = true;
|
||||
@ -507,15 +529,26 @@ pub fn canonicalize_defs<'a>(
|
||||
continue;
|
||||
}
|
||||
|
||||
let has_clauses = variables_bound_to_ability
|
||||
.into_iter()
|
||||
.map(|clause| clause.value)
|
||||
.collect();
|
||||
can_members.push((member_sym, member_annot.typ, has_clauses));
|
||||
// The introduced variables are good; add them to the output.
|
||||
output
|
||||
.introduced_variables
|
||||
.union(&member_annot.introduced_variables);
|
||||
|
||||
let iv = member_annot.introduced_variables;
|
||||
|
||||
let variables = MemberVariables {
|
||||
able_vars: iv.collect_able(),
|
||||
rigid_vars: iv.collect_rigid(),
|
||||
flex_vars: iv.collect_flex(),
|
||||
};
|
||||
|
||||
can_members.push((member_sym, name_region, member_annot.typ, variables));
|
||||
}
|
||||
|
||||
// Store what symbols a type must define implementations for to have this ability.
|
||||
abilities_store.register_ability(loc_ability_name.value, can_members);
|
||||
scope
|
||||
.abilities_store
|
||||
.register_ability(loc_ability_name.value, can_members);
|
||||
}
|
||||
|
||||
// Now that we have the scope completely assembled, and shadowing resolved,
|
||||
@ -526,14 +559,7 @@ pub fn canonicalize_defs<'a>(
|
||||
// once we've finished assembling the entire scope.
|
||||
let mut pending_value_defs = Vec::with_capacity(value_defs.len());
|
||||
for loc_def in value_defs.into_iter() {
|
||||
match to_pending_value_def(
|
||||
env,
|
||||
var_store,
|
||||
loc_def.value,
|
||||
&mut scope,
|
||||
&abilities_store,
|
||||
pattern_type,
|
||||
) {
|
||||
match to_pending_value_def(env, var_store, loc_def.value, &mut scope, pattern_type) {
|
||||
None => { /* skip */ }
|
||||
Some((new_output, pending_def)) => {
|
||||
// store the top-level defs, used to ensure that closures won't capture them
|
||||
@ -1201,7 +1227,9 @@ fn canonicalize_pending_value_def<'a>(
|
||||
}
|
||||
};
|
||||
|
||||
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
|
||||
if let Pattern::Identifier(symbol)
|
||||
| Pattern::AbilityMemberSpecialization { ident: symbol, .. } = loc_can_pattern.value
|
||||
{
|
||||
let def = single_can_def(
|
||||
loc_can_pattern,
|
||||
loc_can_expr,
|
||||
@ -1289,7 +1317,9 @@ fn canonicalize_pending_value_def<'a>(
|
||||
// which also implies it's not a self tail call!
|
||||
//
|
||||
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
|
||||
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
|
||||
if let Pattern::Identifier(symbol)
|
||||
| Pattern::AbilityMemberSpecialization { ident: symbol, .. } = loc_can_pattern.value
|
||||
{
|
||||
if let Closure(ClosureData {
|
||||
function_type,
|
||||
closure_type,
|
||||
@ -1561,7 +1591,9 @@ pub fn can_defs_with_return<'a>(
|
||||
// Now that we've collected all the references, check to see if any of the new idents
|
||||
// we defined went unused by the return expression. If any were unused, report it.
|
||||
for (symbol, region) in symbols_introduced {
|
||||
if !output.references.has_value_lookup(symbol) && !output.references.has_type_lookup(symbol)
|
||||
if !output.references.has_value_lookup(symbol)
|
||||
&& !output.references.has_type_lookup(symbol)
|
||||
&& !scope.abilities_store.is_specialization_name(symbol)
|
||||
{
|
||||
env.problem(Problem::UnusedDef(symbol, region));
|
||||
}
|
||||
@ -1772,7 +1804,6 @@ fn to_pending_value_def<'a>(
|
||||
var_store: &mut VarStore,
|
||||
def: &'a ast::ValueDef<'a>,
|
||||
scope: &mut Scope,
|
||||
abilities_store: &AbilitiesStore,
|
||||
pattern_type: PatternType,
|
||||
) -> Option<(Output, PendingValueDef<'a>)> {
|
||||
use ast::ValueDef::*;
|
||||
@ -1784,7 +1815,6 @@ fn to_pending_value_def<'a>(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
abilities_store,
|
||||
pattern_type,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
@ -1801,7 +1831,6 @@ fn to_pending_value_def<'a>(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
abilities_store,
|
||||
pattern_type,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
@ -1832,7 +1861,6 @@ fn to_pending_value_def<'a>(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
abilities_store,
|
||||
pattern_type,
|
||||
&body_pattern.value,
|
||||
body_pattern.region,
|
||||
|
@ -44,6 +44,15 @@ impl<T> PExpected<T> {
|
||||
PExpected::ForReason(reason, _val, region) => PExpected::ForReason(reason, new, region),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_ref<U>(&self, new: U) -> PExpected<U> {
|
||||
match self {
|
||||
PExpected::NoExpectation(_val) => PExpected::NoExpectation(new),
|
||||
PExpected::ForReason(reason, _val, region) => {
|
||||
PExpected::ForReason(reason.clone(), new, *region)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Expected<T> {
|
||||
|
@ -1083,6 +1083,7 @@ fn canonicalize_when_branch<'a>(
|
||||
&& !branch_output.references.has_value_lookup(symbol)
|
||||
&& !branch_output.references.has_type_lookup(symbol)
|
||||
&& !original_scope.contains_symbol(symbol)
|
||||
&& !scope.abilities_store.is_specialization_name(symbol)
|
||||
{
|
||||
env.problem(Problem::UnusedDef(symbol, *region));
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::abilities::AbilitiesStore;
|
||||
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
|
||||
use crate::effect_module::HostedGeneratedFunctions;
|
||||
use crate::env::Env;
|
||||
@ -28,11 +29,13 @@ pub struct Module {
|
||||
/// all aliases. `bool` indicates whether it is exposed
|
||||
pub aliases: MutMap<Symbol, (bool, Alias)>,
|
||||
pub rigid_variables: RigidVariables,
|
||||
pub abilities_store: AbilitiesStore,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RigidVariables {
|
||||
pub named: MutMap<Variable, Lowercase>,
|
||||
pub able: MutMap<Variable, (Lowercase, Symbol)>,
|
||||
pub wildcards: MutSet<Variable>,
|
||||
}
|
||||
|
||||
@ -302,6 +305,7 @@ pub fn canonicalize_module_defs<'a>(
|
||||
if !output.references.has_value_lookup(symbol)
|
||||
&& !output.references.has_type_lookup(symbol)
|
||||
&& !exposed_symbols.contains(&symbol)
|
||||
&& !scope.abilities_store.is_specialization_name(symbol)
|
||||
{
|
||||
env.problem(Problem::UnusedDef(symbol, region));
|
||||
}
|
||||
@ -311,6 +315,12 @@ pub fn canonicalize_module_defs<'a>(
|
||||
rigid_variables.named.insert(named.variable, named.name);
|
||||
}
|
||||
|
||||
for able in output.introduced_variables.able {
|
||||
rigid_variables
|
||||
.able
|
||||
.insert(able.variable, (able.name, able.ability));
|
||||
}
|
||||
|
||||
for var in output.introduced_variables.wildcards {
|
||||
rigid_variables.wildcards.insert(var.value);
|
||||
}
|
||||
@ -489,6 +499,10 @@ pub fn canonicalize_module_defs<'a>(
|
||||
aliases.insert(symbol, alias);
|
||||
}
|
||||
|
||||
for member in scope.abilities_store.root_ability_members().keys() {
|
||||
exposed_but_not_defined.remove(member);
|
||||
}
|
||||
|
||||
// By this point, all exposed symbols should have been removed from
|
||||
// exposed_symbols and added to exposed_vars_by_symbol. If any were
|
||||
// not, that means they were declared as exposed but there was
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::abilities::AbilitiesStore;
|
||||
use crate::annotation::freshen_opaque_def;
|
||||
use crate::env::Env;
|
||||
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
|
||||
@ -157,7 +156,6 @@ pub fn canonicalize_def_header_pattern<'a>(
|
||||
env: &mut Env<'a>,
|
||||
var_store: &mut VarStore,
|
||||
scope: &mut Scope,
|
||||
abilities_store: &AbilitiesStore,
|
||||
pattern_type: PatternType,
|
||||
pattern: &ast::Pattern<'a>,
|
||||
region: Region,
|
||||
@ -172,7 +170,6 @@ pub fn canonicalize_def_header_pattern<'a>(
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
region,
|
||||
abilities_store,
|
||||
) {
|
||||
Ok((symbol, shadowing_ability_member)) => {
|
||||
output.references.bound_symbols.insert(symbol);
|
||||
|
@ -22,7 +22,7 @@ pub struct Scope {
|
||||
pub aliases: SendMap<Symbol, Alias>,
|
||||
|
||||
/// The abilities currently in scope, and their implementors.
|
||||
pub abilities: SendMap<Symbol, Region>,
|
||||
pub abilities_store: AbilitiesStore,
|
||||
|
||||
/// The current module being processed. This will be used to turn
|
||||
/// unqualified idents into Symbols.
|
||||
@ -68,7 +68,7 @@ impl Scope {
|
||||
symbols: SendMap::default(),
|
||||
aliases,
|
||||
// TODO(abilities): default abilities in scope
|
||||
abilities: SendMap::default(),
|
||||
abilities_store: AbilitiesStore::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -251,7 +251,6 @@ impl Scope {
|
||||
exposed_ident_ids: &IdentIds,
|
||||
all_ident_ids: &mut IdentIds,
|
||||
region: Region,
|
||||
abilities_store: &AbilitiesStore,
|
||||
) -> Result<(Symbol, Option<Symbol>), (Region, Loc<Ident>, Symbol)> {
|
||||
match self.idents.get(&ident) {
|
||||
Some(&(original_symbol, original_region)) => {
|
||||
@ -260,7 +259,9 @@ impl Scope {
|
||||
|
||||
self.symbols.insert(shadow_symbol, region);
|
||||
|
||||
if abilities_store.is_ability_member_name(original_symbol) {
|
||||
if self.abilities_store.is_ability_member_name(original_symbol) {
|
||||
self.abilities_store
|
||||
.register_specializing_symbol(shadow_symbol, original_symbol);
|
||||
// Add a symbol for the shadow, but don't re-associate the member name.
|
||||
Ok((shadow_symbol, Some(original_symbol)))
|
||||
} else {
|
||||
|
@ -1,4 +1,5 @@
|
||||
use roc_builtins::std::StdLib;
|
||||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::constraint::{Constraint, Constraints};
|
||||
use roc_can::def::Declaration;
|
||||
use roc_collections::all::MutMap;
|
||||
@ -91,10 +92,32 @@ pub enum ExposedModuleTypes {
|
||||
|
||||
pub fn constrain_module(
|
||||
constraints: &mut Constraints,
|
||||
abilities_store: &AbilitiesStore,
|
||||
declarations: &[Declaration],
|
||||
home: ModuleId,
|
||||
) -> Constraint {
|
||||
crate::expr::constrain_decls(constraints, home, declarations)
|
||||
let mut constraint = crate::expr::constrain_decls(constraints, home, declarations);
|
||||
|
||||
for (member_name, member_data) in abilities_store.root_ability_members().iter() {
|
||||
let vars = &member_data.variables;
|
||||
let rigids = (vars.rigid_vars.iter())
|
||||
// For our purposes, in the let constraint, able vars are treated like rigids.
|
||||
.chain(vars.able_vars.iter())
|
||||
.copied();
|
||||
let flex = vars.flex_vars.iter().copied();
|
||||
constraint = constraints.let_constraint(
|
||||
rigids,
|
||||
flex,
|
||||
[(*member_name, Loc::at_zero(member_data.signature.clone()))],
|
||||
Constraint::True,
|
||||
constraint,
|
||||
);
|
||||
}
|
||||
|
||||
// The module constraint should always save the environment at the end.
|
||||
debug_assert!(constraints.contains_save_the_environment(&constraint));
|
||||
|
||||
constraint
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -188,9 +188,23 @@ pub fn constrain_pattern(
|
||||
// Erroneous patterns don't add any constraints.
|
||||
}
|
||||
|
||||
Identifier(symbol) | Shadowed(_, _, symbol)
|
||||
// TODO(abilities): handle linking the member def to the specialization ident
|
||||
| AbilityMemberSpecialization {
|
||||
Identifier(symbol) | Shadowed(_, _, symbol) => {
|
||||
if could_be_a_tag_union(expected.get_type_ref()) {
|
||||
state
|
||||
.constraints
|
||||
.push(constraints.is_open_type(expected.get_type_ref().clone()));
|
||||
}
|
||||
|
||||
state.headers.insert(
|
||||
*symbol,
|
||||
Loc {
|
||||
region,
|
||||
value: expected.get_type(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
AbilityMemberSpecialization {
|
||||
ident: symbol,
|
||||
specializes: _,
|
||||
} => {
|
||||
|
@ -427,6 +427,13 @@ pub fn module_from_builtins<'ctx>(
|
||||
} => {
|
||||
include_bytes!("../../../builtins/bitcode/builtins-i386.bc")
|
||||
}
|
||||
Triple {
|
||||
architecture: Architecture::X86_64,
|
||||
operating_system: OperatingSystem::Linux,
|
||||
..
|
||||
} => {
|
||||
include_bytes!("../../../builtins/bitcode/builtins-x86_64.bc")
|
||||
}
|
||||
_ => panic!(
|
||||
"The zig builtins are not currently built for this target: {:?}",
|
||||
target
|
||||
|
@ -13,10 +13,12 @@ roc_constrain= { path = "../constrain" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
|
||||
[build-dependencies]
|
||||
roc_load_internal = { path = "../load_internal" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
@ -36,6 +36,7 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) {
|
||||
&src_dir,
|
||||
Default::default(),
|
||||
target_info,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
);
|
||||
|
||||
let module = res_module.unwrap();
|
||||
|
@ -2,6 +2,7 @@ use bumpalo::Bump;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_constrain::module::ExposedByModule;
|
||||
use roc_module::symbol::{ModuleId, Symbol};
|
||||
use roc_reporting::report::RenderTarget;
|
||||
use roc_target::TargetInfo;
|
||||
use roc_types::subs::{Subs, Variable};
|
||||
use std::path::{Path, PathBuf};
|
||||
@ -18,6 +19,7 @@ fn load<'a>(
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let cached_subs = read_cached_subs();
|
||||
|
||||
@ -29,6 +31,7 @@ fn load<'a>(
|
||||
goal_phase,
|
||||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
)
|
||||
}
|
||||
|
||||
@ -40,6 +43,7 @@ pub fn load_single_threaded<'a>(
|
||||
exposed_types: ExposedByModule,
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let cached_subs = read_cached_subs();
|
||||
|
||||
@ -51,6 +55,7 @@ pub fn load_single_threaded<'a>(
|
||||
goal_phase,
|
||||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
)
|
||||
}
|
||||
|
||||
@ -61,6 +66,7 @@ pub fn load_and_monomorphize_from_str<'a>(
|
||||
src_dir: &Path,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
||||
@ -73,6 +79,7 @@ pub fn load_and_monomorphize_from_str<'a>(
|
||||
exposed_types,
|
||||
Phase::MakeSpecializations,
|
||||
target_info,
|
||||
render,
|
||||
)? {
|
||||
Monomorphized(module) => Ok(module),
|
||||
TypeChecked(_) => unreachable!(""),
|
||||
@ -85,10 +92,11 @@ pub fn load_and_monomorphize<'a>(
|
||||
src_dir: &Path,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_path(arena, filename)?;
|
||||
let load_start = LoadStart::from_path(arena, filename, render)?;
|
||||
|
||||
match load(
|
||||
arena,
|
||||
@ -97,6 +105,7 @@ pub fn load_and_monomorphize<'a>(
|
||||
exposed_types,
|
||||
Phase::MakeSpecializations,
|
||||
target_info,
|
||||
render,
|
||||
)? {
|
||||
Monomorphized(module) => Ok(module),
|
||||
TypeChecked(_) => unreachable!(""),
|
||||
@ -109,10 +118,11 @@ pub fn load_and_typecheck<'a>(
|
||||
src_dir: &Path,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
) -> Result<LoadedModule, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_path(arena, filename)?;
|
||||
let load_start = LoadStart::from_path(arena, filename, render)?;
|
||||
|
||||
match load(
|
||||
arena,
|
||||
@ -121,6 +131,7 @@ pub fn load_and_typecheck<'a>(
|
||||
exposed_types,
|
||||
Phase::SolveTypes,
|
||||
target_info,
|
||||
render,
|
||||
)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
@ -134,6 +145,7 @@ pub fn load_and_typecheck_str<'a>(
|
||||
src_dir: &Path,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
) -> Result<LoadedModule, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
||||
@ -149,6 +161,7 @@ pub fn load_and_typecheck_str<'a>(
|
||||
exposed_types,
|
||||
Phase::SolveTypes,
|
||||
target_info,
|
||||
render,
|
||||
)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
|
@ -33,4 +33,3 @@ tempfile = "3.2.0"
|
||||
pretty_assertions = "1.0.0"
|
||||
maplit = "1.0.2"
|
||||
indoc = "1.0.3"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
|
@ -6,6 +6,7 @@ use crossbeam::thread;
|
||||
use parking_lot::Mutex;
|
||||
use roc_builtins::roc::module_source;
|
||||
use roc_builtins::std::borrow_stdlib;
|
||||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
|
||||
use roc_can::def::Declaration;
|
||||
use roc_can::module::{canonicalize_module_defs, Module};
|
||||
@ -32,6 +33,7 @@ use roc_parse::ident::UppercaseIdent;
|
||||
use roc_parse::module::module_defs;
|
||||
use roc_parse::parser::{FileError, Parser, SyntaxError};
|
||||
use roc_region::all::{LineInfo, Loc, Region};
|
||||
use roc_reporting::report::RenderTarget;
|
||||
use roc_solve::module::SolvedModule;
|
||||
use roc_solve::solve;
|
||||
use roc_target::TargetInfo;
|
||||
@ -411,6 +413,7 @@ pub struct LoadedModule {
|
||||
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
|
||||
pub timings: MutMap<ModuleId, ModuleTiming>,
|
||||
pub documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
pub abilities_store: AbilitiesStore,
|
||||
}
|
||||
|
||||
impl LoadedModule {
|
||||
@ -572,6 +575,7 @@ enum Msg<'a> {
|
||||
decls: Vec<Declaration>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
module_timing: ModuleTiming,
|
||||
abilities_store: AbilitiesStore,
|
||||
},
|
||||
FinishedAllTypeChecking {
|
||||
solved_subs: Solved<Subs>,
|
||||
@ -579,6 +583,7 @@ enum Msg<'a> {
|
||||
exposed_aliases_by_symbol: MutMap<Symbol, (bool, Alias)>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
abilities_store: AbilitiesStore,
|
||||
},
|
||||
FoundSpecializations {
|
||||
module_id: ModuleId,
|
||||
@ -669,6 +674,8 @@ struct State<'a> {
|
||||
// (Granted, this has not been attempted or measured!)
|
||||
pub layout_caches: std::vec::Vec<LayoutCache<'a>>,
|
||||
|
||||
pub render: RenderTarget,
|
||||
|
||||
// cached subs (used for builtin modules, could include packages in the future too)
|
||||
cached_subs: CachedSubs,
|
||||
}
|
||||
@ -676,6 +683,7 @@ struct State<'a> {
|
||||
type CachedSubs = Arc<Mutex<MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>>>;
|
||||
|
||||
impl<'a> State<'a> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
root_id: ModuleId,
|
||||
target_info: TargetInfo,
|
||||
@ -684,6 +692,7 @@ impl<'a> State<'a> {
|
||||
arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
) -> Self {
|
||||
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
|
||||
|
||||
@ -709,6 +718,7 @@ impl<'a> State<'a> {
|
||||
timings: MutMap::default(),
|
||||
layout_caches: std::vec::Vec::with_capacity(num_cpus::get()),
|
||||
cached_subs: Arc::new(Mutex::new(cached_subs)),
|
||||
render,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -890,6 +900,7 @@ pub fn load_and_typecheck_str<'a>(
|
||||
src_dir: &Path,
|
||||
exposed_types: ExposedByModule,
|
||||
target_info: TargetInfo,
|
||||
render: RenderTarget,
|
||||
) -> Result<LoadedModule, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
||||
@ -907,12 +918,19 @@ pub fn load_and_typecheck_str<'a>(
|
||||
Phase::SolveTypes,
|
||||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum PrintTarget {
|
||||
ColorTerminal,
|
||||
Generic,
|
||||
}
|
||||
|
||||
pub struct LoadStart<'a> {
|
||||
arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
|
||||
@ -921,7 +939,11 @@ pub struct LoadStart<'a> {
|
||||
}
|
||||
|
||||
impl<'a> LoadStart<'a> {
|
||||
pub fn from_path(arena: &'a Bump, filename: PathBuf) -> Result<Self, LoadingProblem<'a>> {
|
||||
pub fn from_path(
|
||||
arena: &'a Bump,
|
||||
filename: PathBuf,
|
||||
render: RenderTarget,
|
||||
) -> Result<Self, LoadingProblem<'a>> {
|
||||
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
|
||||
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
|
||||
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
|
||||
@ -953,7 +975,12 @@ impl<'a> LoadStart<'a> {
|
||||
|
||||
// if parsing failed, this module did not add any identifiers
|
||||
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
|
||||
let buf = to_parse_problem_report(problem, module_ids, root_exposed_ident_ids);
|
||||
let buf = to_parse_problem_report(
|
||||
problem,
|
||||
module_ids,
|
||||
root_exposed_ident_ids,
|
||||
render,
|
||||
);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
Err(LoadingProblem::FileProblem { filename, error }) => {
|
||||
@ -1061,6 +1088,7 @@ pub fn load<'a>(
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
// When compiling to wasm, we cannot spawn extra threads
|
||||
// so we have a single-threaded implementation
|
||||
@ -1073,6 +1101,7 @@ pub fn load<'a>(
|
||||
goal_phase,
|
||||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
)
|
||||
} else {
|
||||
load_multi_threaded(
|
||||
@ -1083,6 +1112,7 @@ pub fn load<'a>(
|
||||
goal_phase,
|
||||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1097,12 +1127,14 @@ pub fn load_single_threaded<'a>(
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let LoadStart {
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
root_id,
|
||||
root_msg,
|
||||
..
|
||||
} = load_start;
|
||||
|
||||
let (msg_tx, msg_rx) = bounded(1024);
|
||||
@ -1119,6 +1151,7 @@ pub fn load_single_threaded<'a>(
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
render,
|
||||
);
|
||||
|
||||
// We'll add tasks to this, and then worker threads will take tasks from it.
|
||||
@ -1181,6 +1214,7 @@ fn state_thread_step<'a>(
|
||||
exposed_aliases_by_symbol,
|
||||
dep_idents,
|
||||
documentation,
|
||||
abilities_store,
|
||||
} => {
|
||||
// We're done! There should be no more messages pending.
|
||||
debug_assert!(msg_rx.is_empty());
|
||||
@ -1197,6 +1231,7 @@ fn state_thread_step<'a>(
|
||||
exposed_vars_by_symbol,
|
||||
dep_idents,
|
||||
documentation,
|
||||
abilities_store,
|
||||
);
|
||||
|
||||
Ok(ControlFlow::Break(LoadResult::TypeChecked(typechecked)))
|
||||
@ -1219,8 +1254,12 @@ fn state_thread_step<'a>(
|
||||
|
||||
Msg::FailedToParse(problem) => {
|
||||
let module_ids = (*state.arc_modules).lock().clone().into_module_ids();
|
||||
let buf =
|
||||
to_parse_problem_report(problem, module_ids, state.constrained_ident_ids);
|
||||
let buf = to_parse_problem_report(
|
||||
problem,
|
||||
module_ids,
|
||||
state.constrained_ident_ids,
|
||||
state.render,
|
||||
);
|
||||
Err(LoadingProblem::FormattedReport(buf))
|
||||
}
|
||||
msg => {
|
||||
@ -1229,6 +1268,8 @@ fn state_thread_step<'a>(
|
||||
// system which lets this logic work efficiently.
|
||||
let arc_modules = state.arc_modules.clone();
|
||||
|
||||
let render = state.render;
|
||||
|
||||
let res_state = update(
|
||||
state,
|
||||
msg,
|
||||
@ -1256,6 +1297,7 @@ fn state_thread_step<'a>(
|
||||
problem,
|
||||
module_ids,
|
||||
root_exposed_ident_ids,
|
||||
render,
|
||||
);
|
||||
Err(LoadingProblem::FormattedReport(buf))
|
||||
}
|
||||
@ -1280,12 +1322,14 @@ fn load_multi_threaded<'a>(
|
||||
goal_phase: Phase,
|
||||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let LoadStart {
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
root_id,
|
||||
root_msg,
|
||||
..
|
||||
} = load_start;
|
||||
|
||||
let mut state = State::new(
|
||||
@ -1296,6 +1340,7 @@ fn load_multi_threaded<'a>(
|
||||
arc_modules,
|
||||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
render,
|
||||
);
|
||||
|
||||
let (msg_tx, msg_rx) = bounded(1024);
|
||||
@ -1938,6 +1983,7 @@ fn update<'a>(
|
||||
decls,
|
||||
dep_idents,
|
||||
mut module_timing,
|
||||
abilities_store,
|
||||
} => {
|
||||
log!("solved types for {:?}", module_id);
|
||||
module_timing.end_time = SystemTime::now();
|
||||
@ -1990,6 +2036,7 @@ fn update<'a>(
|
||||
exposed_aliases_by_symbol: solved_module.aliases,
|
||||
dep_idents,
|
||||
documentation,
|
||||
abilities_store,
|
||||
})
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
|
||||
@ -2332,6 +2379,7 @@ fn finish(
|
||||
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
abilities_store: AbilitiesStore,
|
||||
) -> LoadedModule {
|
||||
let module_ids = Arc::try_unwrap(state.arc_modules)
|
||||
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
|
||||
@ -2366,6 +2414,7 @@ fn finish(
|
||||
sources,
|
||||
timings: state.timings,
|
||||
documentation,
|
||||
abilities_store,
|
||||
}
|
||||
}
|
||||
|
||||
@ -3487,6 +3536,10 @@ fn add_imports(
|
||||
rigid_vars.extend(copied_import.rigid);
|
||||
rigid_vars.extend(copied_import.flex);
|
||||
|
||||
// Rigid vars bound to abilities are also treated like rigids.
|
||||
rigid_vars.extend(copied_import.rigid_able);
|
||||
rigid_vars.extend(copied_import.flex_able);
|
||||
|
||||
import_variables.extend(copied_import.registered);
|
||||
|
||||
def_types.push((
|
||||
@ -3504,6 +3557,7 @@ fn add_imports(
|
||||
import_variables
|
||||
}
|
||||
|
||||
#[allow(clippy::complexity)]
|
||||
fn run_solve_solve(
|
||||
imported_builtins: Vec<Symbol>,
|
||||
exposed_for_module: ExposedForModule,
|
||||
@ -3511,11 +3565,17 @@ fn run_solve_solve(
|
||||
constraint: ConstraintSoa,
|
||||
mut var_store: VarStore,
|
||||
module: Module,
|
||||
) -> (Solved<Subs>, Vec<(Symbol, Variable)>, Vec<solve::TypeError>) {
|
||||
) -> (
|
||||
Solved<Subs>,
|
||||
Vec<(Symbol, Variable)>,
|
||||
Vec<solve::TypeError>,
|
||||
AbilitiesStore,
|
||||
) {
|
||||
let Module {
|
||||
exposed_symbols,
|
||||
aliases,
|
||||
rigid_variables,
|
||||
abilities_store,
|
||||
..
|
||||
} = module;
|
||||
|
||||
@ -3540,13 +3600,14 @@ fn run_solve_solve(
|
||||
solve_aliases.insert(*name, alias.clone());
|
||||
}
|
||||
|
||||
let (solved_subs, exposed_vars_by_symbol, problems) = {
|
||||
let (solved_subs, solved_env, problems) = roc_solve::module::run_solve(
|
||||
let (solved_subs, exposed_vars_by_symbol, problems, abilities_store) = {
|
||||
let (solved_subs, solved_env, problems, abilities_store) = roc_solve::module::run_solve(
|
||||
&constraints,
|
||||
actual_constraint,
|
||||
rigid_variables,
|
||||
subs,
|
||||
solve_aliases,
|
||||
abilities_store,
|
||||
);
|
||||
|
||||
let solved_subs = if true {
|
||||
@ -3568,10 +3629,20 @@ fn run_solve_solve(
|
||||
.filter(|(k, _)| exposed_symbols.contains(k))
|
||||
.collect();
|
||||
|
||||
(solved_subs, exposed_vars_by_symbol, problems)
|
||||
(
|
||||
solved_subs,
|
||||
exposed_vars_by_symbol,
|
||||
problems,
|
||||
abilities_store,
|
||||
)
|
||||
};
|
||||
|
||||
(solved_subs, exposed_vars_by_symbol, problems)
|
||||
(
|
||||
solved_subs,
|
||||
exposed_vars_by_symbol,
|
||||
problems,
|
||||
abilities_store,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -3595,7 +3666,7 @@ fn run_solve<'a>(
|
||||
// TODO remove when we write builtins in roc
|
||||
let aliases = module.aliases.clone();
|
||||
|
||||
let (solved_subs, exposed_vars_by_symbol, problems) = {
|
||||
let (solved_subs, exposed_vars_by_symbol, problems, abilities_store) = {
|
||||
if module_id.is_builtin() {
|
||||
match cached_subs.lock().remove(&module_id) {
|
||||
None => run_solve_solve(
|
||||
@ -3607,7 +3678,13 @@ fn run_solve<'a>(
|
||||
module,
|
||||
),
|
||||
Some((subs, exposed_vars_by_symbol)) => {
|
||||
(Solved(subs), exposed_vars_by_symbol.to_vec(), vec![])
|
||||
(
|
||||
Solved(subs),
|
||||
exposed_vars_by_symbol.to_vec(),
|
||||
vec![],
|
||||
// TODO(abilities) replace when we have abilities for builtins
|
||||
AbilitiesStore::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -3647,6 +3724,7 @@ fn run_solve<'a>(
|
||||
dep_idents,
|
||||
solved_module,
|
||||
module_timing,
|
||||
abilities_store,
|
||||
}
|
||||
}
|
||||
|
||||
@ -3775,8 +3853,12 @@ fn canonicalize_and_constrain<'a>(
|
||||
let mut constraints = Constraints::new();
|
||||
|
||||
// TODO: don't generate constraints for a builtin module if it's cached
|
||||
let constraint =
|
||||
constrain_module(&mut constraints, &module_output.declarations, module_id);
|
||||
let constraint = constrain_module(
|
||||
&mut constraints,
|
||||
&module_output.scope.abilities_store,
|
||||
&module_output.declarations,
|
||||
module_id,
|
||||
);
|
||||
|
||||
let after = roc_types::types::get_type_clone_count();
|
||||
|
||||
@ -3816,6 +3898,7 @@ fn canonicalize_and_constrain<'a>(
|
||||
referenced_types: module_output.referenced_types,
|
||||
aliases,
|
||||
rigid_variables: module_output.rigid_variables,
|
||||
abilities_store: module_output.scope.abilities_store,
|
||||
};
|
||||
|
||||
let constrained_module = ConstrainedModule {
|
||||
@ -4254,6 +4337,7 @@ fn add_def_to_module<'a>(
|
||||
// This is a top-level definition, so it cannot capture anything
|
||||
captured_symbols: CapturedSymbols::None,
|
||||
body,
|
||||
body_var: def.expr_var,
|
||||
// This is a 0-arity thunk, so it cannot be recursive
|
||||
is_self_recursive: false,
|
||||
};
|
||||
@ -4463,6 +4547,7 @@ fn to_parse_problem_report<'a>(
|
||||
problem: FileError<'a, SyntaxError<'a>>,
|
||||
mut module_ids: ModuleIds,
|
||||
all_ident_ids: MutMap<ModuleId, IdentIds>,
|
||||
render: RenderTarget,
|
||||
) -> String {
|
||||
use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE};
|
||||
|
||||
@ -4497,7 +4582,7 @@ fn to_parse_problem_report<'a>(
|
||||
let mut buf = String::new();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
report.render(render, &mut buf, &alloc, &palette);
|
||||
|
||||
buf
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ mod test_load {
|
||||
use roc_problem::can::Problem;
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_reporting::report::can_problem;
|
||||
use roc_reporting::report::RenderTarget;
|
||||
use roc_reporting::report::RocDocAllocator;
|
||||
use roc_target::TargetInfo;
|
||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||
@ -41,7 +42,7 @@ mod test_load {
|
||||
) -> Result<LoadedModule, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_path(arena, filename)?;
|
||||
let load_start = LoadStart::from_path(arena, filename, RenderTarget::Generic)?;
|
||||
|
||||
match roc_load_internal::file::load(
|
||||
arena,
|
||||
@ -51,6 +52,7 @@ mod test_load {
|
||||
Phase::SolveTypes,
|
||||
target_info,
|
||||
Default::default(), // these tests will re-compile the builtins
|
||||
RenderTarget::Generic,
|
||||
)? {
|
||||
Monomorphized(_) => unreachable!(""),
|
||||
TypeChecked(module) => Ok(module),
|
||||
@ -88,8 +90,6 @@ mod test_load {
|
||||
}
|
||||
|
||||
fn multiple_modules(files: Vec<(&str, &str)>) -> Result<LoadedModule, String> {
|
||||
use roc_load_internal::file::LoadingProblem;
|
||||
|
||||
let arena = Bump::new();
|
||||
let arena = &arena;
|
||||
|
||||
@ -589,18 +589,18 @@ mod test_load {
|
||||
report,
|
||||
indoc!(
|
||||
"
|
||||
\u{1b}[36m── UNFINISHED LIST ─────────────────────────────────────────────────────────────\u{1b}[0m
|
||||
── UNFINISHED LIST ─────────────────────────────────────────────────────────────
|
||||
|
||||
I cannot find the end of this list:
|
||||
I cannot find the end of this list:
|
||||
|
||||
\u{1b}[36m3\u{1b}[0m\u{1b}[36m│\u{1b}[0m \u{1b}[37mmain = [\u{1b}[0m
|
||||
\u{1b}[31m^\u{1b}[0m
|
||||
3│ main = [
|
||||
^
|
||||
|
||||
You could change it to something like \u{1b}[33m[ 1, 2, 3 ]\u{1b}[0m or even just \u{1b}[33m[]\u{1b}[0m.
|
||||
Anything where there is an open and a close square bracket, and where
|
||||
the elements of the list are separated by commas.
|
||||
You could change it to something like [ 1, 2, 3 ] or even just [].
|
||||
Anything where there is an open and a close square bracket, and where
|
||||
the elements of the list are separated by commas.
|
||||
|
||||
\u{1b}[4mNote\u{1b}[0m: I may be confused by indentation"
|
||||
Note: I may be confused by indentation"
|
||||
)
|
||||
),
|
||||
Ok(_) => unreachable!("we expect failure here"),
|
||||
@ -769,8 +769,6 @@ mod test_load {
|
||||
];
|
||||
|
||||
let err = multiple_modules(modules).unwrap_err();
|
||||
let err = strip_ansi_escapes::strip(err).unwrap();
|
||||
let err = String::from_utf8(err).unwrap();
|
||||
assert_eq!(
|
||||
err,
|
||||
indoc!(
|
||||
@ -817,4 +815,65 @@ mod test_load {
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_2863_module_type_does_not_exist() {
|
||||
let modules = vec![
|
||||
(
|
||||
"platform/Package-Config.roc",
|
||||
indoc!(
|
||||
r#"
|
||||
platform "testplatform"
|
||||
requires {} { main : Str }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
|
||||
mainForHost : Str
|
||||
mainForHost = main
|
||||
"#
|
||||
),
|
||||
),
|
||||
(
|
||||
"Main",
|
||||
indoc!(
|
||||
r#"
|
||||
app "test"
|
||||
packages { pf: "platform" }
|
||||
provides [ main ] to pf
|
||||
|
||||
main : DoesNotExist
|
||||
main = 1
|
||||
"#
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
match multiple_modules(modules) {
|
||||
Err(report) => {
|
||||
assert_eq!(
|
||||
report,
|
||||
indoc!(
|
||||
"
|
||||
── UNRECOGNIZED NAME ───────────────────────────────────────────────────────────
|
||||
|
||||
I cannot find a `DoesNotExist` value
|
||||
|
||||
5│ main : DoesNotExist
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Did you mean one of these?
|
||||
|
||||
Dict
|
||||
Result
|
||||
List
|
||||
Nat
|
||||
"
|
||||
)
|
||||
)
|
||||
}
|
||||
Ok(_) => unreachable!("we expect failure here"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -948,8 +948,8 @@ define_builtins! {
|
||||
39 NUM_REM_CHECKED: "remChecked"
|
||||
40 NUM_DIV_FLOAT: "div"
|
||||
41 NUM_DIV_FLOAT_CHECKED: "divChecked"
|
||||
42 NUM_DIV_INT: "divFloor"
|
||||
43 NUM_DIV_INT_CHECKED: "divFloorChecked"
|
||||
42 NUM_DIV_FLOOR: "divFloor"
|
||||
43 NUM_DIV_FLOOR_CHECKED: "divFloorChecked"
|
||||
44 NUM_MOD_INT: "modInt"
|
||||
45 NUM_MOD_INT_CHECKED: "modIntChecked"
|
||||
46 NUM_MOD_FLOAT: "modFloat"
|
||||
|
@ -195,6 +195,7 @@ pub struct PartialProc<'a> {
|
||||
pub pattern_symbols: &'a [Symbol],
|
||||
pub captured_symbols: CapturedSymbols<'a>,
|
||||
pub body: roc_can::expr::Expr,
|
||||
pub body_var: Variable,
|
||||
pub is_self_recursive: bool,
|
||||
}
|
||||
|
||||
@ -224,6 +225,7 @@ impl<'a> PartialProc<'a> {
|
||||
pattern_symbols,
|
||||
captured_symbols,
|
||||
body: body.value,
|
||||
body_var: ret_var,
|
||||
is_self_recursive,
|
||||
}
|
||||
}
|
||||
@ -240,6 +242,7 @@ impl<'a> PartialProc<'a> {
|
||||
pattern_symbols: pattern_symbols.into_bump_slice(),
|
||||
captured_symbols: CapturedSymbols::None,
|
||||
body: roc_can::expr::Expr::RuntimeError(error.value),
|
||||
body_var: ret_var,
|
||||
is_self_recursive: false,
|
||||
}
|
||||
}
|
||||
@ -902,6 +905,7 @@ impl<'a> Procs<'a> {
|
||||
pattern_symbols,
|
||||
captured_symbols,
|
||||
body: body.value,
|
||||
body_var: ret_var,
|
||||
is_self_recursive,
|
||||
};
|
||||
|
||||
@ -939,6 +943,7 @@ impl<'a> Procs<'a> {
|
||||
pattern_symbols,
|
||||
captured_symbols,
|
||||
body: body.value,
|
||||
body_var: ret_var,
|
||||
is_self_recursive,
|
||||
};
|
||||
|
||||
@ -2476,7 +2481,7 @@ fn specialize_external<'a>(
|
||||
};
|
||||
|
||||
let body = partial_proc.body.clone();
|
||||
let mut specialized_body = from_can(env, fn_var, body, procs, layout_cache);
|
||||
let mut specialized_body = from_can(env, partial_proc.body_var, body, procs, layout_cache);
|
||||
|
||||
match specialized {
|
||||
SpecializedLayout::FunctionPointerBody {
|
||||
|
@ -3,6 +3,7 @@ use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_collections::all::{default_hasher, MutMap};
|
||||
use roc_error_macros::todo_abilities;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_problem::can::RuntimeError;
|
||||
@ -10,7 +11,7 @@ use roc_target::{PtrWidth, TargetInfo};
|
||||
use roc_types::subs::{
|
||||
Content, FlatType, RecordFields, Subs, UnionTags, UnsortedUnionTags, Variable,
|
||||
};
|
||||
use roc_types::types::{gather_fields_unsorted_iter, RecordField};
|
||||
use roc_types::types::{gather_fields_unsorted_iter, RecordField, RecordFieldsError};
|
||||
use std::collections::hash_map::{DefaultHasher, Entry};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
@ -72,6 +73,7 @@ impl<'a> RawFunctionLayout<'a> {
|
||||
use roc_types::subs::Content::*;
|
||||
match content {
|
||||
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
|
||||
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
|
||||
RecursionVar { structure, .. } => {
|
||||
let structure_content = env.subs.get_content_without_compacting(structure);
|
||||
Self::new_help(env, structure, *structure_content)
|
||||
@ -952,6 +954,7 @@ impl<'a> Layout<'a> {
|
||||
use roc_types::subs::Content::*;
|
||||
match content {
|
||||
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
|
||||
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
|
||||
RecursionVar { structure, .. } => {
|
||||
let structure_content = env.subs.get_content_without_compacting(structure);
|
||||
Self::new_help(env, structure, *structure_content)
|
||||
@ -1683,7 +1686,11 @@ fn layout_from_flat_type<'a>(
|
||||
// extract any values from the ext_var
|
||||
|
||||
let mut pairs = Vec::with_capacity_in(fields.len(), arena);
|
||||
for (label, field) in fields.unsorted_iterator(subs, ext_var) {
|
||||
let it = match fields.unsorted_iterator(subs, ext_var) {
|
||||
Ok(it) => it,
|
||||
Err(RecordFieldsError) => return Err(LayoutProblem::Erroneous),
|
||||
};
|
||||
for (label, field) in it {
|
||||
// drop optional fields
|
||||
let var = match field {
|
||||
RecordField::Optional(_) => continue,
|
||||
@ -2657,6 +2664,7 @@ fn layout_from_num_content<'a>(
|
||||
// (e.g. for (5 + 5) assume both 5s are 64-bit integers.)
|
||||
Ok(Layout::default_integer())
|
||||
}
|
||||
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
|
||||
Structure(Apply(symbol, args)) => match *symbol {
|
||||
// Ints
|
||||
Symbol::NUM_NAT => Ok(Layout::usize(target_info)),
|
||||
|
@ -137,8 +137,10 @@ impl FunctionLayout {
|
||||
use LayoutError::*;
|
||||
|
||||
match content {
|
||||
Content::FlexVar(_) => Err(UnresolvedVariable(var)),
|
||||
Content::RigidVar(_) => Err(UnresolvedVariable(var)),
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)),
|
||||
Content::RecursionVar { .. } => Err(TypeError(())),
|
||||
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
|
||||
Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual),
|
||||
@ -243,8 +245,10 @@ impl LambdaSet {
|
||||
use LayoutError::*;
|
||||
|
||||
match content {
|
||||
Content::FlexVar(_) => Err(UnresolvedVariable(var)),
|
||||
Content::RigidVar(_) => Err(UnresolvedVariable(var)),
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)),
|
||||
Content::RecursionVar { .. } => {
|
||||
unreachable!("lambda sets cannot currently be recursive")
|
||||
}
|
||||
@ -627,8 +631,10 @@ impl Layout {
|
||||
use LayoutError::*;
|
||||
|
||||
match content {
|
||||
Content::FlexVar(_) => Err(UnresolvedVariable(var)),
|
||||
Content::RigidVar(_) => Err(UnresolvedVariable(var)),
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)),
|
||||
Content::RecursionVar {
|
||||
structure,
|
||||
opt_name: _,
|
||||
|
@ -2,7 +2,9 @@ use crate::ast::{
|
||||
AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Has, Pattern, Spaceable,
|
||||
TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
};
|
||||
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
|
||||
use crate::blankspace::{
|
||||
space0_after_e, space0_around_ee, space0_before_e, space0_before_optional_after, space0_e,
|
||||
};
|
||||
use crate::ident::{lowercase_ident, parse_ident, Ident};
|
||||
use crate::keyword;
|
||||
use crate::parser::{
|
||||
@ -2591,14 +2593,15 @@ fn record_help<'a>(
|
||||
and!(
|
||||
trailing_sep_by0(
|
||||
word1(b',', ERecord::End),
|
||||
space0_around_ee(
|
||||
space0_before_optional_after(
|
||||
loc!(record_field_help(min_indent)),
|
||||
min_indent,
|
||||
ERecord::IndentEnd,
|
||||
ERecord::IndentEnd
|
||||
),
|
||||
),
|
||||
space0_e(min_indent, ERecord::IndentEnd)
|
||||
// Allow outdented closing braces
|
||||
space0_e(0, ERecord::IndentEnd)
|
||||
),
|
||||
word1(b'}', ERecord::End)
|
||||
)
|
||||
|
@ -0,0 +1,62 @@
|
||||
Defs(
|
||||
[
|
||||
@0-29 Value(
|
||||
Body(
|
||||
@0-1 Identifier(
|
||||
"x",
|
||||
),
|
||||
@4-29 Apply(
|
||||
@4-7 Var {
|
||||
module_name: "",
|
||||
ident: "foo",
|
||||
},
|
||||
[
|
||||
@9-28 ParensAround(
|
||||
Apply(
|
||||
@9-12 Var {
|
||||
module_name: "",
|
||||
ident: "baz",
|
||||
},
|
||||
[
|
||||
@13-28 Record(
|
||||
Collection {
|
||||
items: [
|
||||
@17-26 SpaceBefore(
|
||||
RequiredValue(
|
||||
@17-20 "bar",
|
||||
[],
|
||||
@22-26 Var {
|
||||
module_name: "",
|
||||
ident: "blah",
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
final_comments: [
|
||||
Newline,
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
Space,
|
||||
),
|
||||
),
|
||||
],
|
||||
Space,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@30-31 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
@ -0,0 +1,4 @@
|
||||
x = foo (baz {
|
||||
bar: blah
|
||||
})
|
||||
x
|
@ -0,0 +1,43 @@
|
||||
Defs(
|
||||
[
|
||||
@0-17 Value(
|
||||
Body(
|
||||
@0-1 Identifier(
|
||||
"a",
|
||||
),
|
||||
@4-17 List(
|
||||
Collection {
|
||||
items: [
|
||||
@8-9 SpaceBefore(
|
||||
Num(
|
||||
"1",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@11-12 Num(
|
||||
"2",
|
||||
),
|
||||
@14-15 Num(
|
||||
"3",
|
||||
),
|
||||
],
|
||||
final_comments: [
|
||||
Newline,
|
||||
],
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@18-19 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "a",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
@ -0,0 +1,4 @@
|
||||
a = [
|
||||
1, 2, 3
|
||||
]
|
||||
a
|
@ -0,0 +1,51 @@
|
||||
Defs(
|
||||
[
|
||||
@0-23 Value(
|
||||
Body(
|
||||
@0-1 Identifier(
|
||||
"x",
|
||||
),
|
||||
@4-23 Apply(
|
||||
@4-7 Var {
|
||||
module_name: "",
|
||||
ident: "foo",
|
||||
},
|
||||
[
|
||||
@8-23 Record(
|
||||
Collection {
|
||||
items: [
|
||||
@12-21 SpaceBefore(
|
||||
RequiredValue(
|
||||
@12-15 "bar",
|
||||
[],
|
||||
@17-21 Var {
|
||||
module_name: "",
|
||||
ident: "blah",
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
final_comments: [
|
||||
Newline,
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
Space,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@24-25 SpaceBefore(
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
@ -0,0 +1,4 @@
|
||||
x = foo {
|
||||
bar: blah
|
||||
}
|
||||
x
|
@ -218,6 +218,9 @@ mod test_parse {
|
||||
pass/opaque_reference_pattern.expr,
|
||||
pass/opaque_reference_pattern_with_arguments.expr,
|
||||
pass/ops_with_newlines.expr,
|
||||
pass/outdented_list.expr,
|
||||
pass/outdented_record.expr,
|
||||
pass/outdented_app_with_record.expr,
|
||||
pass/packed_singleton_list.expr,
|
||||
pass/parenthetical_apply.expr,
|
||||
pass/parenthetical_basic_field.expr,
|
||||
|
@ -119,6 +119,13 @@ pub enum Problem {
|
||||
ability: Symbol,
|
||||
region: Region,
|
||||
},
|
||||
AbilityMemberMultipleBoundVars {
|
||||
member: Symbol,
|
||||
ability: Symbol,
|
||||
span_has_clauses: Region,
|
||||
bound_var_names: Vec<Lowercase>,
|
||||
},
|
||||
// TODO(abilities): remove me when ability hierarchies are supported
|
||||
AbilityMemberBindsExternalAbility {
|
||||
member: Symbol,
|
||||
ability: Symbol,
|
||||
|
@ -7,6 +7,7 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
@ -22,6 +23,7 @@ roc_problem = { path = "../problem" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
tempfile = "3.2.0"
|
||||
|
156
compiler/solve/src/ability.rs
Normal file
156
compiler/solve/src/ability.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::Subs;
|
||||
use roc_types::subs::Variable;
|
||||
use roc_types::types::{Category, PatternCategory};
|
||||
use roc_unify::unify::MustImplementAbility;
|
||||
|
||||
use crate::solve::{IncompleteAbilityImplementation, TypeError};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AbilityImplError {
|
||||
/// Promote this to an error that the type does not fully implement an ability
|
||||
IncompleteAbility,
|
||||
/// Promote this error to a `TypeError::BadExpr` from elsewhere
|
||||
BadExpr(Region, Category, Variable),
|
||||
/// Promote this error to a `TypeError::BadPattern` from elsewhere
|
||||
BadPattern(Region, PatternCategory, Variable),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DeferredMustImplementAbility(Vec<(Vec<MustImplementAbility>, AbilityImplError)>);
|
||||
|
||||
impl DeferredMustImplementAbility {
|
||||
pub fn add(&mut self, must_implement: Vec<MustImplementAbility>, on_error: AbilityImplError) {
|
||||
self.0.push((must_implement, on_error));
|
||||
}
|
||||
|
||||
pub fn check(self, subs: &mut Subs, abilities_store: &AbilitiesStore) -> Vec<TypeError> {
|
||||
// Two passes here. First up let's build up records of what types fully implement
|
||||
// abilities, and what specializations are available/missing for the ones that don't.
|
||||
// Use a vec since these lists should usually be pretty small.
|
||||
let mut good = vec![];
|
||||
let mut bad = vec![];
|
||||
|
||||
macro_rules! is_good {
|
||||
($e:expr) => {
|
||||
good.contains($e)
|
||||
};
|
||||
}
|
||||
macro_rules! get_bad {
|
||||
($e:expr) => {
|
||||
bad.iter()
|
||||
.find(|(cand, _)| $e == *cand)
|
||||
.map(|(_, incomplete)| incomplete)
|
||||
};
|
||||
}
|
||||
|
||||
for (mias, _) in self.0.iter() {
|
||||
for &mia @ MustImplementAbility { typ, ability } in mias {
|
||||
if is_good!(&mia) || get_bad!(mia).is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let members_of_ability = abilities_store.members_of_ability(ability).unwrap();
|
||||
let mut specialized_members = Vec::with_capacity(members_of_ability.len());
|
||||
let mut missing_members = Vec::with_capacity(members_of_ability.len());
|
||||
for &member in members_of_ability {
|
||||
match abilities_store.get_specialization(member, typ) {
|
||||
None => {
|
||||
let root_data = abilities_store.member_def(member).unwrap();
|
||||
missing_members.push(Loc::at(root_data.region, member));
|
||||
}
|
||||
Some(specialization) => {
|
||||
specialized_members.push(Loc::at(specialization.region, member));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if missing_members.is_empty() {
|
||||
good.push(mia);
|
||||
} else {
|
||||
bad.push((
|
||||
mia,
|
||||
IncompleteAbilityImplementation {
|
||||
typ,
|
||||
ability,
|
||||
specialized_members,
|
||||
missing_members,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now figure out what errors we need to report.
|
||||
let mut problems = vec![];
|
||||
|
||||
// Keep track of which types that have an incomplete ability were reported as part of
|
||||
// another type error (from an expression or pattern). If we reported an error for a type
|
||||
// that doesn't implement an ability in that context, we don't want to repeat the error
|
||||
// message.
|
||||
let mut reported_in_context = vec![];
|
||||
let mut incomplete_not_in_context = vec![];
|
||||
|
||||
for (must_implement, on_error) in self.0.into_iter() {
|
||||
use AbilityImplError::*;
|
||||
match on_error {
|
||||
IncompleteAbility => {
|
||||
incomplete_not_in_context.extend(must_implement);
|
||||
}
|
||||
BadExpr(region, category, var) => {
|
||||
let incomplete_types = must_implement
|
||||
.iter()
|
||||
.filter_map(|e| get_bad!(*e))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if !incomplete_types.is_empty() {
|
||||
// Demote the bad variable that exposed this problem to an error, both so
|
||||
// that we have an ErrorType to report and so that codegen knows to deal
|
||||
// with the error later.
|
||||
let (error_type, _moar_ghosts_n_stuff) = subs.var_to_error_type(var);
|
||||
problems.push(TypeError::BadExprMissingAbility(
|
||||
region,
|
||||
category,
|
||||
error_type,
|
||||
incomplete_types,
|
||||
));
|
||||
reported_in_context.extend(must_implement);
|
||||
}
|
||||
}
|
||||
BadPattern(region, category, var) => {
|
||||
let incomplete_types = must_implement
|
||||
.iter()
|
||||
.filter_map(|e| get_bad!(*e))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if !incomplete_types.is_empty() {
|
||||
// Demote the bad variable that exposed this problem to an error, both so
|
||||
// that we have an ErrorType to report and so that codegen knows to deal
|
||||
// with the error later.
|
||||
let (error_type, _moar_ghosts_n_stuff) = subs.var_to_error_type(var);
|
||||
problems.push(TypeError::BadPatternMissingAbility(
|
||||
region,
|
||||
category,
|
||||
error_type,
|
||||
incomplete_types,
|
||||
));
|
||||
reported_in_context.extend(must_implement);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for mia in incomplete_not_in_context.into_iter() {
|
||||
if let Some(must_implement) = get_bad!(mia) {
|
||||
if !reported_in_context.contains(&mia) {
|
||||
problems.push(TypeError::IncompleteAbilityImplementation(
|
||||
must_implement.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
problems
|
||||
}
|
||||
}
|
@ -2,5 +2,6 @@
|
||||
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
|
||||
mod ability;
|
||||
pub mod module;
|
||||
pub mod solve;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::solve::{self, Aliases};
|
||||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
|
||||
use roc_can::module::RigidVariables;
|
||||
use roc_collections::all::MutMap;
|
||||
@ -32,13 +33,23 @@ pub fn run_solve(
|
||||
rigid_variables: RigidVariables,
|
||||
mut subs: Subs,
|
||||
mut aliases: Aliases,
|
||||
) -> (Solved<Subs>, solve::Env, Vec<solve::TypeError>) {
|
||||
mut abilities_store: AbilitiesStore,
|
||||
) -> (
|
||||
Solved<Subs>,
|
||||
solve::Env,
|
||||
Vec<solve::TypeError>,
|
||||
AbilitiesStore,
|
||||
) {
|
||||
let env = solve::Env::default();
|
||||
|
||||
for (var, name) in rigid_variables.named {
|
||||
subs.rigid_var(var, name);
|
||||
}
|
||||
|
||||
for (var, (name, ability)) in rigid_variables.able {
|
||||
subs.rigid_able_var(var, name, ability);
|
||||
}
|
||||
|
||||
for var in rigid_variables.wildcards {
|
||||
subs.rigid_var(var, "*".into());
|
||||
}
|
||||
@ -55,9 +66,10 @@ pub fn run_solve(
|
||||
subs,
|
||||
&mut aliases,
|
||||
&constraint,
|
||||
&mut abilities_store,
|
||||
);
|
||||
|
||||
(solved_subs, solved_env, problems)
|
||||
(solved_subs, solved_env, problems, abilities_store)
|
||||
}
|
||||
|
||||
pub fn exposed_types_storage_subs(
|
||||
|
@ -1,4 +1,6 @@
|
||||
use crate::ability::{AbilityImplError, DeferredMustImplementAbility};
|
||||
use bumpalo::Bump;
|
||||
use roc_can::abilities::{AbilitiesStore, MemberSpecialization};
|
||||
use roc_can::constraint::Constraint::{self, *};
|
||||
use roc_can::constraint::{Constraints, LetConstraint};
|
||||
use roc_can::expected::{Expected, PExpected};
|
||||
@ -14,7 +16,7 @@ use roc_types::subs::{
|
||||
use roc_types::types::Type::{self, *};
|
||||
use roc_types::types::{
|
||||
gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, PatternCategory,
|
||||
TypeExtension,
|
||||
Reason, TypeExtension,
|
||||
};
|
||||
use roc_unify::unify::{unify, Mode, Unified::*};
|
||||
|
||||
@ -68,6 +70,15 @@ use roc_unify::unify::{unify, Mode, Unified::*};
|
||||
// Ranks are used to limit the number of type variables considered for generalization. Only those inside
|
||||
// of the let (so those used in inferring the type of `\x -> x`) are considered.
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct IncompleteAbilityImplementation {
|
||||
// TODO(abilities): have general types here, not just opaques
|
||||
pub typ: Symbol,
|
||||
pub ability: Symbol,
|
||||
pub specialized_members: Vec<Loc<Symbol>>,
|
||||
pub missing_members: Vec<Loc<Symbol>>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum TypeError {
|
||||
BadExpr(Region, Category, ErrorType, Expected<ErrorType>),
|
||||
@ -75,6 +86,19 @@ pub enum TypeError {
|
||||
CircularType(Region, Symbol, ErrorType),
|
||||
BadType(roc_types::types::Problem),
|
||||
UnexposedLookup(Symbol),
|
||||
IncompleteAbilityImplementation(IncompleteAbilityImplementation),
|
||||
BadExprMissingAbility(
|
||||
Region,
|
||||
Category,
|
||||
ErrorType,
|
||||
Vec<IncompleteAbilityImplementation>,
|
||||
),
|
||||
BadPatternMissingAbility(
|
||||
Region,
|
||||
PatternCategory,
|
||||
ErrorType,
|
||||
Vec<IncompleteAbilityImplementation>,
|
||||
),
|
||||
}
|
||||
|
||||
use roc_types::types::Alias;
|
||||
@ -515,8 +539,17 @@ pub fn run(
|
||||
mut subs: Subs,
|
||||
aliases: &mut Aliases,
|
||||
constraint: &Constraint,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
) -> (Solved<Subs>, Env) {
|
||||
let env = run_in_place(constraints, env, problems, &mut subs, aliases, constraint);
|
||||
let env = run_in_place(
|
||||
constraints,
|
||||
env,
|
||||
problems,
|
||||
&mut subs,
|
||||
aliases,
|
||||
constraint,
|
||||
abilities_store,
|
||||
);
|
||||
|
||||
(Solved(subs), env)
|
||||
}
|
||||
@ -529,6 +562,7 @@ pub fn run_in_place(
|
||||
subs: &mut Subs,
|
||||
aliases: &mut Aliases,
|
||||
constraint: &Constraint,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
) -> Env {
|
||||
let mut pools = Pools::default();
|
||||
|
||||
@ -540,6 +574,8 @@ pub fn run_in_place(
|
||||
|
||||
let arena = Bump::new();
|
||||
|
||||
let mut deferred_must_implement_abilities = DeferredMustImplementAbility::default();
|
||||
|
||||
let state = solve(
|
||||
&arena,
|
||||
constraints,
|
||||
@ -551,8 +587,14 @@ pub fn run_in_place(
|
||||
aliases,
|
||||
subs,
|
||||
constraint,
|
||||
abilities_store,
|
||||
&mut deferred_must_implement_abilities,
|
||||
);
|
||||
|
||||
// Now that the module has been solved, we can run through and check all
|
||||
// types claimed to implement abilities.
|
||||
problems.extend(deferred_must_implement_abilities.check(subs, abilities_store));
|
||||
|
||||
state.env
|
||||
}
|
||||
|
||||
@ -604,6 +646,8 @@ fn solve(
|
||||
aliases: &mut Aliases,
|
||||
subs: &mut Subs,
|
||||
constraint: &Constraint,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
deferred_must_implement_abilities: &mut DeferredMustImplementAbility,
|
||||
) -> State {
|
||||
let initial = Work::Constraint {
|
||||
env,
|
||||
@ -656,6 +700,19 @@ fn solve(
|
||||
|
||||
let mut new_env = env.clone();
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
check_ability_specialization(
|
||||
arena,
|
||||
subs,
|
||||
&new_env,
|
||||
pools,
|
||||
rank,
|
||||
abilities_store,
|
||||
problems,
|
||||
deferred_must_implement_abilities,
|
||||
*symbol,
|
||||
*loc_var,
|
||||
);
|
||||
|
||||
new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
|
||||
}
|
||||
|
||||
@ -752,6 +809,19 @@ fn solve(
|
||||
|
||||
let mut new_env = env.clone();
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
check_ability_specialization(
|
||||
arena,
|
||||
subs,
|
||||
&new_env,
|
||||
pools,
|
||||
rank,
|
||||
abilities_store,
|
||||
problems,
|
||||
deferred_must_implement_abilities,
|
||||
*symbol,
|
||||
*loc_var,
|
||||
);
|
||||
|
||||
new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
|
||||
}
|
||||
|
||||
@ -796,12 +866,21 @@ fn solve(
|
||||
let expected = type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
|
||||
|
||||
match unify(subs, actual, expected, Mode::EQ) {
|
||||
Success(vars) => {
|
||||
Success {
|
||||
vars,
|
||||
must_implement_ability,
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_must_implement_abilities.add(
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadExpr(*region, category.clone(), actual),
|
||||
);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
Failure(vars, actual_type, expected_type) => {
|
||||
Failure(vars, actual_type, expected_type, _bad_impls) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
let problem = TypeError::BadExpr(
|
||||
@ -838,12 +917,16 @@ fn solve(
|
||||
let target = *target;
|
||||
|
||||
match unify(subs, actual, target, Mode::EQ) {
|
||||
Success(vars) => {
|
||||
Success {
|
||||
vars,
|
||||
// ERROR NOT REPORTED
|
||||
must_implement_ability: _,
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
state
|
||||
}
|
||||
Failure(vars, _actual_type, _expected_type) => {
|
||||
Failure(vars, _actual_type, _expected_type, _bad_impls) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
// ERROR NOT REPORTED
|
||||
@ -890,13 +973,26 @@ fn solve(
|
||||
type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
|
||||
|
||||
match unify(subs, actual, expected, Mode::EQ) {
|
||||
Success(vars) => {
|
||||
Success {
|
||||
vars,
|
||||
must_implement_ability,
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_must_implement_abilities.add(
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadExpr(
|
||||
*region,
|
||||
Category::Lookup(*symbol),
|
||||
actual,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
Failure(vars, actual_type, expected_type) => {
|
||||
Failure(vars, actual_type, expected_type, _bad_impls) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
let problem = TypeError::BadExpr(
|
||||
@ -957,12 +1053,21 @@ fn solve(
|
||||
};
|
||||
|
||||
match unify(subs, actual, expected, mode) {
|
||||
Success(vars) => {
|
||||
Success {
|
||||
vars,
|
||||
must_implement_ability,
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_must_implement_abilities.add(
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadPattern(*region, category.clone(), actual),
|
||||
);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
Failure(vars, actual_type, expected_type) => {
|
||||
Failure(vars, actual_type, expected_type, _bad_impls) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
let problem = TypeError::BadPattern(
|
||||
@ -1126,12 +1231,25 @@ fn solve(
|
||||
let includes = type_to_var(subs, rank, pools, aliases, &tag_ty);
|
||||
|
||||
match unify(subs, actual, includes, Mode::PRESENT) {
|
||||
Success(vars) => {
|
||||
Success {
|
||||
vars,
|
||||
must_implement_ability,
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_must_implement_abilities.add(
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadPattern(
|
||||
*region,
|
||||
pattern_category.clone(),
|
||||
actual,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
Failure(vars, actual_type, expected_to_include_type) => {
|
||||
Failure(vars, actual_type, expected_to_include_type, _bad_impls) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
let problem = TypeError::BadPattern(
|
||||
@ -1159,6 +1277,137 @@ fn solve(
|
||||
state
|
||||
}
|
||||
|
||||
/// If a symbol claims to specialize an ability member, check that its solved type in fact
|
||||
/// does specialize the ability, and record the specialization.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
// Aggressive but necessary - there aren't many usages.
|
||||
#[inline(always)]
|
||||
fn check_ability_specialization(
|
||||
arena: &Bump,
|
||||
subs: &mut Subs,
|
||||
env: &Env,
|
||||
pools: &mut Pools,
|
||||
rank: Rank,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
problems: &mut Vec<TypeError>,
|
||||
deferred_must_implement_abilities: &mut DeferredMustImplementAbility,
|
||||
symbol: Symbol,
|
||||
symbol_loc_var: Loc<Variable>,
|
||||
) {
|
||||
// If the symbol specializes an ability member, we need to make sure that the
|
||||
// inferred type for the specialization actually aligns with the expected
|
||||
// implementation.
|
||||
if let Some((root_symbol, root_data)) = abilities_store.root_name_and_def(symbol) {
|
||||
let root_signature_var = env
|
||||
.get_var_by_symbol(&root_symbol)
|
||||
.expect("Ability should be registered in env by now!");
|
||||
|
||||
// Check if they unify - if they don't, then the claimed specialization isn't really one,
|
||||
// and that's a type error!
|
||||
// This also fixes any latent type variables that need to be specialized to exactly what
|
||||
// the ability signature expects.
|
||||
|
||||
// We need to freshly instantiate the root signature so that all unifications are reflected
|
||||
// in the specialization type, but not the original signature type.
|
||||
let root_signature_var =
|
||||
deep_copy_var_in(subs, Rank::toplevel(), pools, root_signature_var, arena);
|
||||
let snapshot = subs.snapshot();
|
||||
let unified = unify(subs, symbol_loc_var.value, root_signature_var, Mode::EQ);
|
||||
|
||||
match unified {
|
||||
Success {
|
||||
vars: _,
|
||||
must_implement_ability,
|
||||
} if must_implement_ability.is_empty() => {
|
||||
// This can happen when every ability constriant on a type variable went
|
||||
// through only another type variable. That means this def is not specialized
|
||||
// for one type - for now, we won't admit this.
|
||||
|
||||
// Rollback the snapshot so we unlink the root signature with the specialization,
|
||||
// so we can have two separate error types.
|
||||
subs.rollback_to(snapshot);
|
||||
|
||||
let (expected_type, _problems) = subs.var_to_error_type(root_signature_var);
|
||||
let (actual_type, _problems) = subs.var_to_error_type(symbol_loc_var.value);
|
||||
|
||||
let reason = Reason::GeneralizedAbilityMemberSpecialization {
|
||||
member_name: root_symbol,
|
||||
def_region: root_data.region,
|
||||
};
|
||||
|
||||
let problem = TypeError::BadExpr(
|
||||
symbol_loc_var.region,
|
||||
Category::AbilityMemberSpecialization(root_symbol),
|
||||
actual_type,
|
||||
Expected::ForReason(reason, expected_type, symbol_loc_var.region),
|
||||
);
|
||||
|
||||
problems.push(problem);
|
||||
}
|
||||
|
||||
Success {
|
||||
vars,
|
||||
must_implement_ability,
|
||||
} => {
|
||||
subs.commit_snapshot(snapshot);
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
// First, figure out and register for what type does this symbol specialize
|
||||
// the ability member.
|
||||
let mut ability_implementations_for_specialization = must_implement_ability
|
||||
.iter()
|
||||
.filter(|mia| mia.ability == root_data.parent_ability)
|
||||
.collect::<Vec<_>>();
|
||||
ability_implementations_for_specialization.dedup();
|
||||
|
||||
debug_assert!(ability_implementations_for_specialization.len() == 1, "Multiple variables bound to an ability - this is ambiguous and should have been caught in canonicalization");
|
||||
|
||||
// This is a valid specialization! Record it.
|
||||
let specialization_type = ability_implementations_for_specialization[0].typ;
|
||||
let specialization = MemberSpecialization {
|
||||
symbol,
|
||||
region: symbol_loc_var.region,
|
||||
};
|
||||
abilities_store.register_specialization_for_type(
|
||||
root_symbol,
|
||||
specialization_type,
|
||||
specialization,
|
||||
);
|
||||
|
||||
// Store the checks for what abilities must be implemented to be checked after the
|
||||
// whole module is complete.
|
||||
deferred_must_implement_abilities
|
||||
.add(must_implement_ability, AbilityImplError::IncompleteAbility);
|
||||
}
|
||||
Failure(vars, actual_type, expected_type, unimplemented_abilities) => {
|
||||
subs.commit_snapshot(snapshot);
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
let reason = Reason::InvalidAbilityMemberSpecialization {
|
||||
member_name: root_symbol,
|
||||
def_region: root_data.region,
|
||||
unimplemented_abilities,
|
||||
};
|
||||
|
||||
let problem = TypeError::BadExpr(
|
||||
symbol_loc_var.region,
|
||||
Category::AbilityMemberSpecialization(root_symbol),
|
||||
actual_type,
|
||||
Expected::ForReason(reason, expected_type, symbol_loc_var.region),
|
||||
);
|
||||
|
||||
problems.push(problem);
|
||||
}
|
||||
BadType(vars, problem) => {
|
||||
subs.commit_snapshot(snapshot);
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
problems.push(TypeError::BadType(problem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LocalDefVarsVec<T> {
|
||||
Stack(arrayvec::ArrayVec<T, 32>),
|
||||
@ -1291,7 +1540,7 @@ impl RegisterVariable {
|
||||
use RegisterVariable::*;
|
||||
|
||||
match typ {
|
||||
Variable(var) => Direct(*var),
|
||||
Type::Variable(var) => Direct(*var),
|
||||
EmptyRec => Direct(Variable::EMPTY_RECORD),
|
||||
EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION),
|
||||
Type::DelayedAlias(AliasCommon { symbol, .. }) => {
|
||||
@ -2183,7 +2432,7 @@ fn adjust_rank_content(
|
||||
use roc_types::subs::FlatType::*;
|
||||
|
||||
match content {
|
||||
FlexVar(_) | RigidVar(_) | Error => group_rank,
|
||||
FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) | Error => group_rank,
|
||||
|
||||
RecursionVar { .. } => group_rank,
|
||||
|
||||
@ -2399,7 +2648,14 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
|
||||
desc.mark = Mark::NONE;
|
||||
desc.copy = OptVariable::NONE;
|
||||
}
|
||||
FlexVar(_) | Error => (),
|
||||
&RigidAbleVar(name, ability) => {
|
||||
// Same as `RigidVar` above
|
||||
desc.content = FlexAbleVar(Some(name), ability);
|
||||
desc.rank = max_rank;
|
||||
desc.mark = Mark::NONE;
|
||||
desc.copy = OptVariable::NONE;
|
||||
}
|
||||
FlexVar(_) | FlexAbleVar(_, _) | Error => (),
|
||||
|
||||
RecursionVar { structure, .. } => {
|
||||
stack.push(*structure);
|
||||
@ -2687,7 +2943,7 @@ fn deep_copy_var_help(
|
||||
copy
|
||||
}
|
||||
|
||||
FlexVar(_) | Error => copy,
|
||||
FlexVar(_) | FlexAbleVar(_, _) | Error => copy,
|
||||
|
||||
RecursionVar {
|
||||
opt_name,
|
||||
@ -2712,6 +2968,12 @@ fn deep_copy_var_help(
|
||||
copy
|
||||
}
|
||||
|
||||
RigidAbleVar(name, ability) => {
|
||||
subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability)));
|
||||
|
||||
copy
|
||||
}
|
||||
|
||||
Alias(symbol, arguments, real_type_var, kind) => {
|
||||
let new_variables =
|
||||
SubsSlice::reserve_into_subs(subs, arguments.all_variables_len as _);
|
||||
|
@ -10,23 +10,13 @@ mod helpers;
|
||||
#[cfg(test)]
|
||||
mod solve_expr {
|
||||
use crate::helpers::with_larger_debug_stack;
|
||||
use roc_load::LoadedModule;
|
||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||
|
||||
// HELPERS
|
||||
|
||||
fn infer_eq_help(
|
||||
src: &str,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<roc_solve::solve::TypeError>,
|
||||
Vec<roc_problem::can::Problem>,
|
||||
String,
|
||||
),
|
||||
std::io::Error,
|
||||
> {
|
||||
fn run_load_and_infer(src: &str) -> Result<LoadedModule, std::io::Error> {
|
||||
use bumpalo::Bump;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::tempdir;
|
||||
|
||||
@ -55,6 +45,7 @@ mod solve_expr {
|
||||
dir.path(),
|
||||
exposed_types,
|
||||
roc_target::TargetInfo::default_x86_64(),
|
||||
roc_reporting::report::RenderTarget::Generic,
|
||||
);
|
||||
|
||||
dir.close()?;
|
||||
@ -63,8 +54,19 @@ mod solve_expr {
|
||||
};
|
||||
|
||||
let loaded = loaded.expect("failed to load module");
|
||||
Ok(loaded)
|
||||
}
|
||||
|
||||
use roc_load::LoadedModule;
|
||||
fn infer_eq_help(
|
||||
src: &str,
|
||||
) -> Result<
|
||||
(
|
||||
Vec<roc_solve::solve::TypeError>,
|
||||
Vec<roc_problem::can::Problem>,
|
||||
String,
|
||||
),
|
||||
std::io::Error,
|
||||
> {
|
||||
let LoadedModule {
|
||||
module_id: home,
|
||||
mut can_problems,
|
||||
@ -73,7 +75,7 @@ mod solve_expr {
|
||||
mut solved,
|
||||
exposed_to_host,
|
||||
..
|
||||
} = loaded;
|
||||
} = run_load_and_infer(src)?;
|
||||
|
||||
let mut can_problems = can_problems.remove(&home).unwrap_or_default();
|
||||
let type_problems = type_problems.remove(&home).unwrap_or_default();
|
||||
@ -159,6 +161,59 @@ mod solve_expr {
|
||||
assert_eq!(actual, expected.to_string());
|
||||
}
|
||||
|
||||
fn check_inferred_abilities<'a, I>(src: &'a str, expected_specializations: I)
|
||||
where
|
||||
I: IntoIterator<Item = (&'a str, &'a str)>,
|
||||
{
|
||||
let LoadedModule {
|
||||
module_id: home,
|
||||
mut can_problems,
|
||||
mut type_problems,
|
||||
interns,
|
||||
abilities_store,
|
||||
..
|
||||
} = run_load_and_infer(src).unwrap();
|
||||
|
||||
let can_problems = can_problems.remove(&home).unwrap_or_default();
|
||||
let type_problems = type_problems.remove(&home).unwrap_or_default();
|
||||
|
||||
assert_eq!(can_problems, Vec::new(), "Canonicalization problems: ");
|
||||
|
||||
if !type_problems.is_empty() {
|
||||
eprintln!("{:?}", type_problems);
|
||||
panic!();
|
||||
}
|
||||
|
||||
let known_specializations = abilities_store.get_known_specializations();
|
||||
use std::collections::HashSet;
|
||||
let pretty_specializations = known_specializations
|
||||
.into_iter()
|
||||
.map(|(member, typ)| {
|
||||
let member_data = abilities_store.member_def(member).unwrap();
|
||||
let member_str = member.ident_str(&interns).as_str();
|
||||
let ability_str = member_data.parent_ability.ident_str(&interns).as_str();
|
||||
(
|
||||
format!("{}:{}", ability_str, member_str),
|
||||
typ.ident_str(&interns).as_str(),
|
||||
)
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
for (parent, specialization) in expected_specializations.into_iter() {
|
||||
let has_the_one = pretty_specializations
|
||||
.iter()
|
||||
// references are annoying so we do this
|
||||
.find(|(p, s)| p == parent && s == &specialization)
|
||||
.is_some();
|
||||
assert!(
|
||||
has_the_one,
|
||||
"{:#?} not in {:#?}",
|
||||
(parent, specialization),
|
||||
pretty_specializations,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_literal() {
|
||||
infer_eq("5", "Num *");
|
||||
@ -2344,7 +2399,7 @@ mod solve_expr {
|
||||
{ numIdentity, x : numIdentity 42, y }
|
||||
"#
|
||||
),
|
||||
"{ numIdentity : Num a -> Num a, x : Num a, y : F64 }",
|
||||
"{ numIdentity : Num a -> Num a, x : Num a, y : Float * }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -3767,7 +3822,7 @@ mod solve_expr {
|
||||
negatePoint { x: 1, y: 2.1, z: 0x3 }
|
||||
"#
|
||||
),
|
||||
"{ x : Num a, y : F64, z : Int * }",
|
||||
"{ x : Num a, y : Float *, z : Int * }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -3784,7 +3839,7 @@ mod solve_expr {
|
||||
{ a, b }
|
||||
"#
|
||||
),
|
||||
"{ a : { x : Num a, y : F64, z : c }, b : { blah : Str, x : Num a, y : F64, z : c } }",
|
||||
"{ a : { x : Num a, y : Float *, z : c }, b : { blah : Str, x : Num a, y : Float *, z : c } }",
|
||||
);
|
||||
}
|
||||
|
||||
@ -5714,4 +5769,150 @@ mod solve_expr {
|
||||
"a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb -> { a : a, aa : aa, b : b, bb : bb, c : c, d : d, e : e, f : f, g : g, h : h, i : i, j : j, k : k, l : l, m : m, n : n, o : o, p : p, q : q, r : r, s : s, t : t, u : u, v : v, w : w, x : x, y : y, z : z }",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exposed_ability_name() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ hash ] to "./platform"
|
||||
|
||||
Hash has hash : a -> U64 | a has Hash
|
||||
"#
|
||||
),
|
||||
"a -> U64 | a has Hash",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_ability_single_member_specializations() {
|
||||
check_inferred_abilities(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ hash ] to "./platform"
|
||||
|
||||
Hash has hash : a -> U64 | a has Hash
|
||||
|
||||
Id := U64
|
||||
|
||||
hash = \$Id n -> n
|
||||
"#
|
||||
),
|
||||
[("Hash:hash", "Id")],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_ability_multiple_members_specializations() {
|
||||
check_inferred_abilities(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ hash, hash32 ] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
hash32 : a -> U32 | a has Hash
|
||||
|
||||
Id := U64
|
||||
|
||||
hash = \$Id n -> n
|
||||
hash32 = \$Id n -> Num.toU32 n
|
||||
"#
|
||||
),
|
||||
[("Hash:hash", "Id"), ("Hash:hash32", "Id")],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_abilities_multiple_members_specializations() {
|
||||
check_inferred_abilities(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ hash, hash32, eq, le ] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
hash32 : a -> U32 | a has Hash
|
||||
|
||||
Ord has
|
||||
eq : a, a -> Bool | a has Ord
|
||||
le : a, a -> Bool | a has Ord
|
||||
|
||||
Id := U64
|
||||
|
||||
hash = \$Id n -> n
|
||||
hash32 = \$Id n -> Num.toU32 n
|
||||
|
||||
eq = \$Id m, $Id n -> m == n
|
||||
le = \$Id m, $Id n -> m < n
|
||||
"#
|
||||
),
|
||||
[
|
||||
("Hash:hash", "Id"),
|
||||
("Hash:hash32", "Id"),
|
||||
("Ord:eq", "Id"),
|
||||
("Ord:le", "Id"),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_checked_specialization_with_typed_body() {
|
||||
check_inferred_abilities(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ hash ] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
|
||||
Id := U64
|
||||
|
||||
hash : Id -> U64
|
||||
hash = \$Id n -> n
|
||||
"#
|
||||
),
|
||||
[("Hash:hash", "Id")],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_checked_specialization_with_annotation_only() {
|
||||
check_inferred_abilities(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ hash ] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
|
||||
Id := U64
|
||||
|
||||
hash : Id -> U64
|
||||
"#
|
||||
),
|
||||
[("Hash:hash", "Id")],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_called() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ zero ] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
|
||||
Id := U64
|
||||
|
||||
hash = \$Id n -> n
|
||||
|
||||
zero = hash ($Id 0)
|
||||
"#
|
||||
),
|
||||
"U64",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2745,6 +2745,166 @@ fn num_to_str() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn num_to_str_u8() {
|
||||
use roc_std::RocStr;
|
||||
|
||||
assert_evals_to!(r#"Num.toStr 0u8"#, RocStr::from("0"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 1u8"#, RocStr::from("1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 10u8"#, RocStr::from("10"), RocStr);
|
||||
|
||||
let max = format!("{}", u8::MAX);
|
||||
assert_evals_to!(r#"Num.toStr Num.maxU8"#, RocStr::from(max.as_str()), RocStr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn num_to_str_u16() {
|
||||
use roc_std::RocStr;
|
||||
|
||||
assert_evals_to!(r#"Num.toStr 0u16"#, RocStr::from("0"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 1u16"#, RocStr::from("1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 10u16"#, RocStr::from("10"), RocStr);
|
||||
|
||||
let max = format!("{}", u16::MAX);
|
||||
assert_evals_to!(
|
||||
r#"Num.toStr Num.maxU16"#,
|
||||
RocStr::from(max.as_str()),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn num_to_str_u32() {
|
||||
use roc_std::RocStr;
|
||||
|
||||
assert_evals_to!(r#"Num.toStr 0u32"#, RocStr::from("0"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 1u32"#, RocStr::from("1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 10u32"#, RocStr::from("10"), RocStr);
|
||||
|
||||
let max = format!("{}", u32::MAX);
|
||||
assert_evals_to!(
|
||||
r#"Num.toStr Num.maxU32"#,
|
||||
RocStr::from(max.as_str()),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn num_to_str_u64() {
|
||||
use roc_std::RocStr;
|
||||
|
||||
assert_evals_to!(r#"Num.toStr 0u64"#, RocStr::from("0"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 1u64"#, RocStr::from("1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 10u64"#, RocStr::from("10"), RocStr);
|
||||
|
||||
let max = format!("{}", u64::MAX);
|
||||
assert_evals_to!(
|
||||
r#"Num.toStr Num.maxU64"#,
|
||||
RocStr::from(max.as_str()),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn num_to_str_i8() {
|
||||
use roc_std::RocStr;
|
||||
|
||||
assert_evals_to!(r#"Num.toStr -10i8"#, RocStr::from("-10"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr -1i8"#, RocStr::from("-1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 0i8"#, RocStr::from("0"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 1i8"#, RocStr::from("1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 10i8"#, RocStr::from("10"), RocStr);
|
||||
|
||||
let max = format!("{}", i8::MAX);
|
||||
assert_evals_to!(r#"Num.toStr Num.maxI8"#, RocStr::from(max.as_str()), RocStr);
|
||||
|
||||
let max = format!("{}", i8::MIN);
|
||||
assert_evals_to!(r#"Num.toStr Num.minI8"#, RocStr::from(max.as_str()), RocStr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn num_to_str_i16() {
|
||||
use roc_std::RocStr;
|
||||
|
||||
assert_evals_to!(r#"Num.toStr -10i16"#, RocStr::from("-10"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr -1i16"#, RocStr::from("-1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 0i16"#, RocStr::from("0"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 1i16"#, RocStr::from("1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 10i16"#, RocStr::from("10"), RocStr);
|
||||
|
||||
let max = format!("{}", i16::MAX);
|
||||
assert_evals_to!(
|
||||
r#"Num.toStr Num.maxI16"#,
|
||||
RocStr::from(max.as_str()),
|
||||
RocStr
|
||||
);
|
||||
|
||||
let max = format!("{}", i16::MIN);
|
||||
assert_evals_to!(
|
||||
r#"Num.toStr Num.minI16"#,
|
||||
RocStr::from(max.as_str()),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn num_to_str_i32() {
|
||||
use roc_std::RocStr;
|
||||
|
||||
assert_evals_to!(r#"Num.toStr -10i32"#, RocStr::from("-10"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr -1i32"#, RocStr::from("-1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 0i32"#, RocStr::from("0"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 1i32"#, RocStr::from("1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 10i32"#, RocStr::from("10"), RocStr);
|
||||
|
||||
let max = format!("{}", i32::MAX);
|
||||
assert_evals_to!(
|
||||
r#"Num.toStr Num.maxI32"#,
|
||||
RocStr::from(max.as_str()),
|
||||
RocStr
|
||||
);
|
||||
|
||||
let max = format!("{}", i32::MIN);
|
||||
assert_evals_to!(
|
||||
r#"Num.toStr Num.minI32"#,
|
||||
RocStr::from(max.as_str()),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn num_to_str_i64() {
|
||||
use roc_std::RocStr;
|
||||
|
||||
assert_evals_to!(r#"Num.toStr -10i64"#, RocStr::from("-10"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr -1i64"#, RocStr::from("-1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 0i64"#, RocStr::from("0"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 1i64"#, RocStr::from("1"), RocStr);
|
||||
assert_evals_to!(r#"Num.toStr 10i64"#, RocStr::from("10"), RocStr);
|
||||
|
||||
let max = format!("{}", i64::MAX);
|
||||
assert_evals_to!(
|
||||
r#"Num.toStr Num.maxI64"#,
|
||||
RocStr::from(max.as_str()),
|
||||
RocStr
|
||||
);
|
||||
|
||||
let max = format!("{}", i64::MIN);
|
||||
assert_evals_to!(
|
||||
r#"Num.toStr Num.minI64"#,
|
||||
RocStr::from(max.as_str()),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn u8_addition_greater_than_i8() {
|
||||
|
@ -54,6 +54,7 @@ pub fn helper(
|
||||
src_dir,
|
||||
Default::default(),
|
||||
roc_target::TargetInfo::default_x86_64(),
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
);
|
||||
|
||||
let mut loaded = loaded.expect("failed to load module");
|
||||
|
@ -7,6 +7,7 @@ use roc_collections::all::MutSet;
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_reporting::report::RenderTarget;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
@ -57,6 +58,7 @@ fn create_llvm_module<'a>(
|
||||
src_dir,
|
||||
Default::default(),
|
||||
target_info,
|
||||
RenderTarget::ColorTerminal,
|
||||
);
|
||||
|
||||
let mut loaded = match loaded {
|
||||
|
@ -91,6 +91,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>(
|
||||
src_dir,
|
||||
Default::default(),
|
||||
roc_target::TargetInfo::default_wasm32(),
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
);
|
||||
|
||||
let loaded = loaded.expect("failed to load module");
|
||||
|
@ -17,6 +17,7 @@ roc_load = { path = "../load" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
test_mono_macros = { path = "../test_mono_macros" }
|
||||
pretty_assertions = "1.0.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
@ -101,6 +101,7 @@ fn compiles_to_ir(test_name: &str, src: &str) {
|
||||
src_dir,
|
||||
Default::default(),
|
||||
TARGET_INFO,
|
||||
roc_reporting::report::RenderTarget::Generic,
|
||||
);
|
||||
|
||||
let mut loaded = match loaded {
|
||||
|
@ -116,7 +116,7 @@ fn find_names_needed(
|
||||
}
|
||||
|
||||
match &subs.get_content_without_compacting(variable).clone() {
|
||||
RecursionVar { opt_name: None, .. } | FlexVar(None) => {
|
||||
RecursionVar { opt_name: None, .. } | FlexVar(None) | FlexAbleVar(None, _) => {
|
||||
let root = subs.get_root_key_without_compacting(variable);
|
||||
|
||||
// If this var is *not* its own root, then the
|
||||
@ -139,7 +139,8 @@ fn find_names_needed(
|
||||
opt_name: Some(name_index),
|
||||
..
|
||||
}
|
||||
| FlexVar(Some(name_index)) => {
|
||||
| FlexVar(Some(name_index))
|
||||
| FlexAbleVar(Some(name_index), _) => {
|
||||
// This root already has a name. Nothing more to do here!
|
||||
|
||||
// User-defined names are already taken.
|
||||
@ -147,7 +148,7 @@ fn find_names_needed(
|
||||
let name = subs.field_names[name_index.index as usize].clone();
|
||||
names_taken.insert(name);
|
||||
}
|
||||
RigidVar(name_index) => {
|
||||
RigidVar(name_index) | RigidAbleVar(name_index, _) => {
|
||||
// User-defined names are already taken.
|
||||
// We must not accidentally generate names that collide with them!
|
||||
let name = subs.field_names[name_index.index as usize].clone();
|
||||
@ -289,6 +290,11 @@ fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Context<'a> {
|
||||
able_variables: Vec<(&'a str, Symbol)>,
|
||||
}
|
||||
|
||||
pub fn content_to_string(
|
||||
content: &Content,
|
||||
subs: &Subs,
|
||||
@ -297,8 +303,16 @@ pub fn content_to_string(
|
||||
) -> String {
|
||||
let mut buf = String::new();
|
||||
let env = Env { home, interns };
|
||||
let mut ctx = Context::default();
|
||||
|
||||
write_content(&env, content, subs, &mut buf, Parens::Unnecessary);
|
||||
write_content(&env, &mut ctx, content, subs, &mut buf, Parens::Unnecessary);
|
||||
|
||||
for (i, (var, ability)) in ctx.able_variables.into_iter().enumerate() {
|
||||
buf.push_str(if i == 0 { " | " } else { ", " });
|
||||
buf.push_str(var);
|
||||
buf.push_str(" has ");
|
||||
write_symbol(&env, ability, &mut buf);
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
@ -314,7 +328,14 @@ pub fn get_single_arg<'a>(subs: &'a Subs, args: &'a AliasVariables) -> &'a Conte
|
||||
subs.get_content_without_compacting(arg_var)
|
||||
}
|
||||
|
||||
fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, parens: Parens) {
|
||||
fn write_content<'a>(
|
||||
env: &Env,
|
||||
ctx: &mut Context<'a>,
|
||||
content: &Content,
|
||||
subs: &'a Subs,
|
||||
buf: &mut String,
|
||||
parens: Parens,
|
||||
) {
|
||||
use crate::subs::Content::*;
|
||||
|
||||
match content {
|
||||
@ -327,6 +348,18 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
let name = &subs.field_names[name_index.index as usize];
|
||||
buf.push_str(name.as_str())
|
||||
}
|
||||
FlexAbleVar(opt_name_index, ability) => {
|
||||
let name = opt_name_index
|
||||
.map(|name_index| subs.field_names[name_index.index as usize].as_str())
|
||||
.unwrap_or(WILDCARD);
|
||||
ctx.able_variables.push((name, *ability));
|
||||
buf.push_str(name);
|
||||
}
|
||||
RigidAbleVar(name_index, ability) => {
|
||||
let name = subs.field_names[name_index.index as usize].as_str();
|
||||
ctx.able_variables.push((name, *ability));
|
||||
buf.push_str(name);
|
||||
}
|
||||
RecursionVar { opt_name, .. } => match opt_name {
|
||||
Some(name_index) => {
|
||||
let name = &subs.field_names[name_index.index as usize];
|
||||
@ -334,7 +367,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
}
|
||||
None => buf.push_str(WILDCARD),
|
||||
},
|
||||
Structure(flat_type) => write_flat_type(env, flat_type, subs, buf, parens),
|
||||
Structure(flat_type) => write_flat_type(env, ctx, flat_type, subs, buf, parens),
|
||||
Alias(symbol, args, _actual, _kind) => {
|
||||
let write_parens = parens == Parens::InTypeParam && !args.is_empty();
|
||||
|
||||
@ -346,6 +379,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
Symbol::NUM_INTEGER => {
|
||||
write_integer(
|
||||
env,
|
||||
ctx,
|
||||
get_single_arg(subs, &args),
|
||||
subs,
|
||||
buf,
|
||||
@ -353,17 +387,25 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
false,
|
||||
);
|
||||
}
|
||||
Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"),
|
||||
Symbol::NUM_FLOATINGPOINT => write_float(
|
||||
env,
|
||||
ctx,
|
||||
get_single_arg(subs, &args),
|
||||
subs,
|
||||
buf,
|
||||
parens,
|
||||
write_parens,
|
||||
),
|
||||
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
buf.push_str("Num ");
|
||||
write_content(env, content, subs, buf, parens);
|
||||
write_content(env, ctx, content, subs, buf, parens);
|
||||
}),
|
||||
},
|
||||
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
buf.push_str("Num ");
|
||||
write_content(env, content, subs, buf, parens);
|
||||
write_content(env, ctx, content, subs, buf, parens);
|
||||
}),
|
||||
}
|
||||
}
|
||||
@ -371,29 +413,18 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
Symbol::NUM_INT => {
|
||||
let content = get_single_arg(subs, args);
|
||||
|
||||
write_integer(env, content, subs, buf, parens, write_parens)
|
||||
write_integer(env, ctx, content, subs, buf, parens, write_parens)
|
||||
}
|
||||
|
||||
Symbol::NUM_FLOAT => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let arg_var_index = args
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Num was not applied to a type argument!");
|
||||
let arg_var = subs[arg_var_index];
|
||||
let content = subs.get_content_without_compacting(arg_var);
|
||||
|
||||
match content {
|
||||
Alias(Symbol::NUM_BINARY32, _, _, _) => buf.push_str("F32"),
|
||||
Alias(Symbol::NUM_BINARY64, _, _, _) => buf.push_str("F64"),
|
||||
Alias(Symbol::NUM_DECIMAL, _, _, _) => buf.push_str("Dec"),
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
buf.push_str("Float ");
|
||||
write_content(env, content, subs, buf, parens);
|
||||
}),
|
||||
}
|
||||
}
|
||||
Symbol::NUM_FLOAT => write_float(
|
||||
env,
|
||||
ctx,
|
||||
get_single_arg(subs, args),
|
||||
subs,
|
||||
buf,
|
||||
parens,
|
||||
write_parens,
|
||||
),
|
||||
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
write_symbol(env, *symbol, buf);
|
||||
@ -403,6 +434,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
buf.push(' ');
|
||||
write_content(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_content_without_compacting(var),
|
||||
subs,
|
||||
buf,
|
||||
@ -414,7 +446,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
if false {
|
||||
buf.push_str("[[ but really ");
|
||||
let content = subs.get_content_without_compacting(*_actual);
|
||||
write_content(env, content, subs, buf, parens);
|
||||
write_content(env, ctx, content, subs, buf, parens);
|
||||
buf.push_str("]]");
|
||||
}
|
||||
}),
|
||||
@ -422,6 +454,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
}
|
||||
RangedNumber(typ, _range_vars) => write_content(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_content_without_compacting(*typ),
|
||||
subs,
|
||||
buf,
|
||||
@ -431,10 +464,32 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
}
|
||||
}
|
||||
|
||||
fn write_integer(
|
||||
fn write_float<'a>(
|
||||
env: &Env,
|
||||
ctx: &mut Context<'a>,
|
||||
content: &Content,
|
||||
subs: &Subs,
|
||||
subs: &'a Subs,
|
||||
buf: &mut String,
|
||||
parens: Parens,
|
||||
write_parens: bool,
|
||||
) {
|
||||
use crate::subs::Content::*;
|
||||
match content {
|
||||
Alias(Symbol::NUM_BINARY32, _, _, _) => buf.push_str("F32"),
|
||||
Alias(Symbol::NUM_BINARY64, _, _, _) => buf.push_str("F64"),
|
||||
Alias(Symbol::NUM_DECIMAL, _, _, _) => buf.push_str("Dec"),
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
buf.push_str("Float ");
|
||||
write_content(env, ctx, content, subs, buf, parens);
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_integer<'a>(
|
||||
env: &Env,
|
||||
ctx: &mut Context<'a>,
|
||||
content: &Content,
|
||||
subs: &'a Subs,
|
||||
buf: &mut String,
|
||||
parens: Parens,
|
||||
write_parens: bool,
|
||||
@ -454,7 +509,7 @@ fn write_integer(
|
||||
)*
|
||||
actual => {
|
||||
buf.push_str("Int ");
|
||||
write_content(env, actual, subs, buf, parens);
|
||||
write_content(env, ctx, actual, subs, buf, parens);
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -497,6 +552,7 @@ impl<'a> ExtContent<'a> {
|
||||
|
||||
fn write_ext_content<'a>(
|
||||
env: &Env,
|
||||
ctx: &mut Context<'a>,
|
||||
subs: &'a Subs,
|
||||
buf: &mut String,
|
||||
ext_content: ExtContent<'a>,
|
||||
@ -508,12 +564,13 @@ fn write_ext_content<'a>(
|
||||
//
|
||||
// e.g. the "*" at the end of `{ x: I64 }*`
|
||||
// or the "r" at the end of `{ x: I64 }r`
|
||||
write_content(env, content, subs, buf, parens)
|
||||
write_content(env, ctx, content, subs, buf, parens)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_sorted_tags2<'a>(
|
||||
env: &Env,
|
||||
ctx: &mut Context<'a>,
|
||||
subs: &'a Subs,
|
||||
buf: &mut String,
|
||||
tags: &UnionTags,
|
||||
@ -546,6 +603,7 @@ fn write_sorted_tags2<'a>(
|
||||
buf.push(' ');
|
||||
write_content(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_content_without_compacting(*var),
|
||||
subs,
|
||||
buf,
|
||||
@ -559,6 +617,7 @@ fn write_sorted_tags2<'a>(
|
||||
|
||||
fn write_sorted_tags<'a>(
|
||||
env: &Env,
|
||||
ctx: &mut Context<'a>,
|
||||
subs: &'a Subs,
|
||||
buf: &mut String,
|
||||
tags: &MutMap<TagName, Vec<Variable>>,
|
||||
@ -603,6 +662,7 @@ fn write_sorted_tags<'a>(
|
||||
buf.push(' ');
|
||||
write_content(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_content_without_compacting(*var),
|
||||
subs,
|
||||
buf,
|
||||
@ -614,18 +674,37 @@ fn write_sorted_tags<'a>(
|
||||
ExtContent::from_var(subs, ext_var)
|
||||
}
|
||||
|
||||
fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut String, parens: Parens) {
|
||||
fn write_flat_type<'a>(
|
||||
env: &Env,
|
||||
ctx: &mut Context<'a>,
|
||||
flat_type: &FlatType,
|
||||
subs: &'a Subs,
|
||||
buf: &mut String,
|
||||
parens: Parens,
|
||||
) {
|
||||
use crate::subs::FlatType::*;
|
||||
|
||||
match flat_type {
|
||||
Apply(symbol, args) => {
|
||||
write_apply(env, *symbol, subs.get_subs_slice(*args), subs, buf, parens)
|
||||
}
|
||||
Apply(symbol, args) => write_apply(
|
||||
env,
|
||||
ctx,
|
||||
*symbol,
|
||||
subs.get_subs_slice(*args),
|
||||
subs,
|
||||
buf,
|
||||
parens,
|
||||
),
|
||||
EmptyRecord => buf.push_str(EMPTY_RECORD),
|
||||
EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION),
|
||||
Func(args, _closure, ret) => {
|
||||
write_fn(env, subs.get_subs_slice(*args), *ret, subs, buf, parens)
|
||||
}
|
||||
Func(args, _closure, ret) => write_fn(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_subs_slice(*args),
|
||||
*ret,
|
||||
subs,
|
||||
buf,
|
||||
parens,
|
||||
),
|
||||
Record(fields, ext_var) => {
|
||||
use crate::types::{gather_fields, RecordStructure};
|
||||
|
||||
@ -664,6 +743,7 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
|
||||
|
||||
write_content(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_content_without_compacting(var),
|
||||
subs,
|
||||
buf,
|
||||
@ -684,18 +764,18 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
|
||||
//
|
||||
// e.g. the "*" at the end of `{ x: I64 }*`
|
||||
// or the "r" at the end of `{ x: I64 }r`
|
||||
write_content(env, content, subs, buf, parens)
|
||||
write_content(env, ctx, content, subs, buf, parens)
|
||||
}
|
||||
}
|
||||
}
|
||||
TagUnion(tags, ext_var) => {
|
||||
buf.push_str("[ ");
|
||||
|
||||
let ext_content = write_sorted_tags2(env, subs, buf, tags, *ext_var);
|
||||
let ext_content = write_sorted_tags2(env, ctx, subs, buf, tags, *ext_var);
|
||||
|
||||
buf.push_str(" ]");
|
||||
|
||||
write_ext_content(env, subs, buf, ext_content, parens)
|
||||
write_ext_content(env, ctx, subs, buf, ext_content, parens)
|
||||
}
|
||||
|
||||
FunctionOrTagUnion(tag_name, _, ext_var) => {
|
||||
@ -703,25 +783,26 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
|
||||
|
||||
let mut tags: MutMap<TagName, _> = MutMap::default();
|
||||
tags.insert(subs[*tag_name].clone(), vec![]);
|
||||
let ext_content = write_sorted_tags(env, subs, buf, &tags, *ext_var);
|
||||
let ext_content = write_sorted_tags(env, ctx, subs, buf, &tags, *ext_var);
|
||||
|
||||
buf.push_str(" ]");
|
||||
|
||||
write_ext_content(env, subs, buf, ext_content, parens)
|
||||
write_ext_content(env, ctx, subs, buf, ext_content, parens)
|
||||
}
|
||||
|
||||
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||
buf.push_str("[ ");
|
||||
|
||||
let ext_content = write_sorted_tags2(env, subs, buf, tags, *ext_var);
|
||||
let ext_content = write_sorted_tags2(env, ctx, subs, buf, tags, *ext_var);
|
||||
|
||||
buf.push_str(" ]");
|
||||
|
||||
write_ext_content(env, subs, buf, ext_content, parens);
|
||||
write_ext_content(env, ctx, subs, buf, ext_content, parens);
|
||||
|
||||
buf.push_str(" as ");
|
||||
write_content(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_content_without_compacting(*rec_var),
|
||||
subs,
|
||||
buf,
|
||||
@ -777,11 +858,12 @@ pub fn chase_ext_tag_union<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
fn write_apply(
|
||||
fn write_apply<'a>(
|
||||
env: &Env,
|
||||
ctx: &mut Context<'a>,
|
||||
symbol: Symbol,
|
||||
args: &[Variable],
|
||||
subs: &Subs,
|
||||
subs: &'a Subs,
|
||||
buf: &mut String,
|
||||
parens: Parens,
|
||||
) {
|
||||
@ -805,7 +887,7 @@ fn write_apply(
|
||||
buf.push('(');
|
||||
}
|
||||
|
||||
write_content(env, content, subs, &mut arg_param, Parens::InTypeParam);
|
||||
write_content(env, ctx, content, subs, &mut arg_param, Parens::InTypeParam);
|
||||
buf.push_str("Num ");
|
||||
buf.push_str(&arg_param);
|
||||
|
||||
@ -838,6 +920,7 @@ fn write_apply(
|
||||
buf.push(' ');
|
||||
write_content(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_content_without_compacting(*arg),
|
||||
subs,
|
||||
buf,
|
||||
@ -852,11 +935,12 @@ fn write_apply(
|
||||
}
|
||||
}
|
||||
|
||||
fn write_fn(
|
||||
fn write_fn<'a>(
|
||||
env: &Env,
|
||||
ctx: &mut Context<'a>,
|
||||
args: &[Variable],
|
||||
ret: Variable,
|
||||
subs: &Subs,
|
||||
subs: &'a Subs,
|
||||
buf: &mut String,
|
||||
parens: Parens,
|
||||
) {
|
||||
@ -876,6 +960,7 @@ fn write_fn(
|
||||
|
||||
write_content(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_content_without_compacting(*arg),
|
||||
subs,
|
||||
buf,
|
||||
@ -886,6 +971,7 @@ fn write_fn(
|
||||
buf.push_str(" -> ");
|
||||
write_content(
|
||||
env,
|
||||
ctx,
|
||||
subs.get_content_without_compacting(ret),
|
||||
subs,
|
||||
buf,
|
||||
|
@ -1,6 +1,9 @@
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
use crate::types::{name_type_var, AliasKind, ErrorType, Problem, RecordField, TypeExt};
|
||||
use crate::types::{
|
||||
name_type_var, AliasKind, ErrorType, Problem, RecordField, RecordFieldsError, TypeExt,
|
||||
};
|
||||
use roc_collections::all::{ImMap, ImSet, MutSet, SendMap};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::ident::{Lowercase, TagName, Uppercase};
|
||||
use roc_module::symbol::Symbol;
|
||||
use std::fmt;
|
||||
@ -754,7 +757,9 @@ impl<'a> fmt::Debug for SubsFmtContent<'a> {
|
||||
fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match this {
|
||||
Content::FlexVar(name) => write!(f, "Flex({:?})", name),
|
||||
Content::FlexAbleVar(name, symbol) => write!(f, "FlexAble({:?}, {:?})", name, symbol),
|
||||
Content::RigidVar(name) => write!(f, "Rigid({:?})", name),
|
||||
Content::RigidAbleVar(name, symbol) => write!(f, "RigidAble({:?}, {:?})", name, symbol),
|
||||
Content::RecursionVar {
|
||||
structure,
|
||||
opt_name,
|
||||
@ -794,7 +799,19 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f
|
||||
}
|
||||
FlatType::Func(arguments, lambda_set, result) => {
|
||||
let slice = subs.get_subs_slice(*arguments);
|
||||
write!(f, "Func({:?}, {:?}, {:?})", slice, lambda_set, result)
|
||||
write!(f, "Func([")?;
|
||||
for var in slice {
|
||||
let content = subs.get_content_without_compacting(*var);
|
||||
write!(f, "<{:?}>{:?},", *var, SubsFmtContent(content, subs))?;
|
||||
}
|
||||
let result_content = subs.get_content_without_compacting(*result);
|
||||
write!(
|
||||
f,
|
||||
"], {:?}, <{:?}>{:?})",
|
||||
lambda_set,
|
||||
*result,
|
||||
SubsFmtContent(result_content, subs)
|
||||
)
|
||||
}
|
||||
FlatType::Record(fields, ext) => {
|
||||
write!(f, "{{ ")?;
|
||||
@ -1737,6 +1754,14 @@ impl Subs {
|
||||
self.set(var, desc);
|
||||
}
|
||||
|
||||
pub fn rigid_able_var(&mut self, var: Variable, name: Lowercase, ability: Symbol) {
|
||||
let name_index = SubsIndex::push_new(&mut self.field_names, name);
|
||||
let content = Content::RigidAbleVar(name_index, ability);
|
||||
let desc = Descriptor::from(content);
|
||||
|
||||
self.set(var, desc);
|
||||
}
|
||||
|
||||
/// 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);
|
||||
@ -2118,6 +2143,12 @@ pub enum Content {
|
||||
FlexVar(Option<SubsIndex<Lowercase>>),
|
||||
/// name given in a user-written annotation
|
||||
RigidVar(SubsIndex<Lowercase>),
|
||||
/// Like a [Self::FlexVar], but is also bound to an ability.
|
||||
/// This can only happen when unified with a [Self::RigidAbleVar].
|
||||
FlexAbleVar(Option<SubsIndex<Lowercase>>, Symbol),
|
||||
/// Like a [Self::RigidVar], but is also bound to an ability.
|
||||
/// For example, "a has Hash".
|
||||
RigidAbleVar(SubsIndex<Lowercase>, Symbol),
|
||||
/// name given to a recursion variable
|
||||
RecursionVar {
|
||||
structure: Variable,
|
||||
@ -2713,11 +2744,11 @@ impl RecordFields {
|
||||
&'a self,
|
||||
subs: &'a Subs,
|
||||
ext: Variable,
|
||||
) -> impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + 'a {
|
||||
let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext)
|
||||
.expect("Something weird ended up in a record type");
|
||||
) -> Result<impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + 'a, RecordFieldsError>
|
||||
{
|
||||
let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext)?;
|
||||
|
||||
it
|
||||
Ok(it)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -2838,7 +2869,12 @@ fn occurs(
|
||||
Err((root_var, vec![]))
|
||||
} else {
|
||||
match subs.get_content_without_compacting(root_var) {
|
||||
FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => Ok(()),
|
||||
FlexVar(_)
|
||||
| RigidVar(_)
|
||||
| FlexAbleVar(_, _)
|
||||
| RigidAbleVar(_, _)
|
||||
| RecursionVar { .. }
|
||||
| Error => Ok(()),
|
||||
|
||||
Structure(flat_type) => {
|
||||
let mut new_seen = seen.to_owned();
|
||||
@ -2966,7 +3002,12 @@ fn explicit_substitute(
|
||||
to
|
||||
} else {
|
||||
match subs.get(in_var).content {
|
||||
FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => in_var,
|
||||
FlexVar(_)
|
||||
| RigidVar(_)
|
||||
| FlexAbleVar(_, _)
|
||||
| RigidAbleVar(_, _)
|
||||
| RecursionVar { .. }
|
||||
| Error => in_var,
|
||||
|
||||
Structure(flat_type) => {
|
||||
match flat_type {
|
||||
@ -3134,9 +3175,9 @@ fn get_var_names(
|
||||
subs.set_mark(var, Mark::GET_VAR_NAMES);
|
||||
|
||||
match desc.content {
|
||||
Error | FlexVar(None) => taken_names,
|
||||
Error | FlexVar(None) | FlexAbleVar(None, _) => taken_names,
|
||||
|
||||
FlexVar(Some(name_index)) => add_name(
|
||||
FlexVar(Some(name_index)) | FlexAbleVar(Some(name_index), _) => add_name(
|
||||
subs,
|
||||
0,
|
||||
name_index,
|
||||
@ -3163,7 +3204,9 @@ fn get_var_names(
|
||||
None => taken_names,
|
||||
},
|
||||
|
||||
RigidVar(name_index) => add_name(subs, 0, name_index, var, RigidVar, taken_names),
|
||||
RigidVar(name_index) | RigidAbleVar(name_index, _) => {
|
||||
add_name(subs, 0, name_index, var, RigidVar, taken_names)
|
||||
}
|
||||
|
||||
Alias(_, args, _, _) => args.into_iter().fold(taken_names, |answer, arg_var| {
|
||||
get_var_names(subs, subs[arg_var], answer)
|
||||
@ -3329,11 +3372,6 @@ fn content_to_err_type(
|
||||
match content {
|
||||
Structure(flat_type) => flat_type_to_err_type(subs, state, flat_type),
|
||||
|
||||
FlexVar(Some(name_index)) => {
|
||||
let name = subs.field_names[name_index.index as usize].clone();
|
||||
ErrorType::FlexVar(name)
|
||||
}
|
||||
|
||||
FlexVar(opt_name) => {
|
||||
let name = match opt_name {
|
||||
Some(name_index) => subs.field_names[name_index.index as usize].clone(),
|
||||
@ -3356,6 +3394,28 @@ fn content_to_err_type(
|
||||
ErrorType::RigidVar(name)
|
||||
}
|
||||
|
||||
FlexAbleVar(opt_name, ability) => {
|
||||
let name = match opt_name {
|
||||
Some(name_index) => subs.field_names[name_index.index as usize].clone(),
|
||||
None => {
|
||||
// set the name so when this variable occurs elsewhere in the type it gets the same name
|
||||
let name = get_fresh_var_name(state);
|
||||
let name_index = SubsIndex::push_new(&mut subs.field_names, name.clone());
|
||||
|
||||
subs.set_content(var, FlexVar(Some(name_index)));
|
||||
|
||||
name
|
||||
}
|
||||
};
|
||||
|
||||
ErrorType::FlexAbleVar(name, ability)
|
||||
}
|
||||
|
||||
RigidAbleVar(name_index, ability) => {
|
||||
let name = subs.field_names[name_index.index as usize].clone();
|
||||
ErrorType::RigidAbleVar(name, ability)
|
||||
}
|
||||
|
||||
RecursionVar { opt_name, .. } => {
|
||||
let name = match opt_name {
|
||||
Some(name_index) => subs.field_names[name_index.index as usize].clone(),
|
||||
@ -3628,7 +3688,7 @@ fn restore_help(subs: &mut Subs, initial: Variable) {
|
||||
use FlatType::*;
|
||||
|
||||
match &desc.content {
|
||||
FlexVar(_) | RigidVar(_) | Error => (),
|
||||
FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) | Error => (),
|
||||
|
||||
RecursionVar { structure, .. } => {
|
||||
stack.push(*structure);
|
||||
@ -3857,6 +3917,8 @@ impl StorageSubs {
|
||||
match content {
|
||||
FlexVar(opt_name) => FlexVar(*opt_name),
|
||||
RigidVar(name) => RigidVar(*name),
|
||||
FlexAbleVar(opt_name, ability) => FlexAbleVar(*opt_name, *ability),
|
||||
RigidAbleVar(name, ability) => RigidAbleVar(*name, *ability),
|
||||
RecursionVar {
|
||||
structure,
|
||||
opt_name,
|
||||
@ -4253,6 +4315,29 @@ fn deep_copy_var_to_help(env: &mut DeepCopyVarToEnv<'_>, var: Variable) -> Varia
|
||||
copy
|
||||
}
|
||||
|
||||
FlexAbleVar(opt_name_index, ability) => {
|
||||
let new_name_index = opt_name_index.map(|name_index| {
|
||||
let name = env.source.field_names[name_index.index as usize].clone();
|
||||
SubsIndex::push_new(&mut env.target.field_names, name)
|
||||
});
|
||||
|
||||
let content = FlexAbleVar(new_name_index, ability);
|
||||
env.target.set_content(copy, content);
|
||||
|
||||
copy
|
||||
}
|
||||
|
||||
RigidAbleVar(name_index, ability) => {
|
||||
let name = env.source.field_names[name_index.index as usize].clone();
|
||||
let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name);
|
||||
env.target.set(
|
||||
copy,
|
||||
make_descriptor(FlexAbleVar(Some(new_name_index), ability)),
|
||||
);
|
||||
|
||||
copy
|
||||
}
|
||||
|
||||
Alias(symbol, arguments, real_type_var, kind) => {
|
||||
let new_variables =
|
||||
SubsSlice::reserve_into_subs(env.target, arguments.all_variables_len as _);
|
||||
@ -4312,6 +4397,8 @@ pub struct CopiedImport {
|
||||
pub variable: Variable,
|
||||
pub flex: Vec<Variable>,
|
||||
pub rigid: Vec<Variable>,
|
||||
pub flex_able: Vec<Variable>,
|
||||
pub rigid_able: Vec<Variable>,
|
||||
pub translations: Vec<(Variable, Variable)>,
|
||||
pub registered: Vec<Variable>,
|
||||
}
|
||||
@ -4322,6 +4409,8 @@ struct CopyImportEnv<'a> {
|
||||
target: &'a mut Subs,
|
||||
flex: Vec<Variable>,
|
||||
rigid: Vec<Variable>,
|
||||
flex_able: Vec<Variable>,
|
||||
rigid_able: Vec<Variable>,
|
||||
translations: Vec<(Variable, Variable)>,
|
||||
registered: Vec<Variable>,
|
||||
}
|
||||
@ -4343,6 +4432,8 @@ pub fn copy_import_to(
|
||||
target,
|
||||
flex: Vec::new(),
|
||||
rigid: Vec::new(),
|
||||
flex_able: Vec::new(),
|
||||
rigid_able: Vec::new(),
|
||||
translations: Vec::new(),
|
||||
registered: Vec::new(),
|
||||
};
|
||||
@ -4354,6 +4445,8 @@ pub fn copy_import_to(
|
||||
source,
|
||||
flex,
|
||||
rigid,
|
||||
flex_able,
|
||||
rigid_able,
|
||||
translations,
|
||||
registered,
|
||||
target: _,
|
||||
@ -4376,6 +4469,8 @@ pub fn copy_import_to(
|
||||
variable: copy,
|
||||
flex,
|
||||
rigid,
|
||||
flex_able,
|
||||
rigid_able,
|
||||
translations,
|
||||
registered,
|
||||
}
|
||||
@ -4393,7 +4488,10 @@ pub fn copy_import_to(
|
||||
/// standard variables
|
||||
fn is_registered(content: &Content) -> bool {
|
||||
match content {
|
||||
Content::FlexVar(_) | Content::RigidVar(_) => false,
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(..)
|
||||
| Content::RigidAbleVar(..) => false,
|
||||
Content::Structure(FlatType::EmptyRecord | FlatType::EmptyTagUnion) => false,
|
||||
|
||||
Content::Structure(_)
|
||||
@ -4454,6 +4552,13 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
|
||||
// We have already marked the variable as copied, so we
|
||||
// will not repeat this work or crawl this variable again.
|
||||
match desc.content {
|
||||
Structure(Erroneous(_)) => {
|
||||
// Make this into a flex var so that we don't have to copy problems across module
|
||||
// boundaries - the error will be reported locally.
|
||||
env.target.set(copy, make_descriptor(FlexVar(None)));
|
||||
|
||||
copy
|
||||
}
|
||||
Structure(flat_type) => {
|
||||
let new_flat_type = match flat_type {
|
||||
Apply(symbol, arguments) => {
|
||||
@ -4484,7 +4589,9 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
|
||||
Func(new_arguments, new_closure_var, new_ret_var)
|
||||
}
|
||||
|
||||
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
|
||||
Erroneous(_) => internal_error!("I thought this was handled above"),
|
||||
|
||||
same @ EmptyRecord | same @ EmptyTagUnion => same,
|
||||
|
||||
Record(fields, ext_var) => {
|
||||
let record_fields = {
|
||||
@ -4631,6 +4738,20 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
|
||||
copy
|
||||
}
|
||||
|
||||
FlexAbleVar(opt_name_index, ability) => {
|
||||
if let Some(name_index) = opt_name_index {
|
||||
let name = env.source.field_names[name_index.index as usize].clone();
|
||||
let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name);
|
||||
|
||||
let content = FlexAbleVar(Some(new_name_index), ability);
|
||||
env.target.set_content(copy, content);
|
||||
}
|
||||
|
||||
env.flex_able.push(copy);
|
||||
|
||||
copy
|
||||
}
|
||||
|
||||
Error => {
|
||||
// Open question: should this return Error, or a Flex var?
|
||||
|
||||
@ -4653,6 +4774,20 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
|
||||
copy
|
||||
}
|
||||
|
||||
RigidAbleVar(name_index, ability) => {
|
||||
let name = env.source.field_names[name_index.index as usize].clone();
|
||||
let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name);
|
||||
|
||||
env.target
|
||||
.set(copy, make_descriptor(RigidAbleVar(new_name_index, ability)));
|
||||
|
||||
env.rigid_able.push(copy);
|
||||
|
||||
env.translations.push((var, copy));
|
||||
|
||||
copy
|
||||
}
|
||||
|
||||
RecursionVar {
|
||||
opt_name,
|
||||
structure,
|
||||
@ -4746,7 +4881,7 @@ where
|
||||
use Content::*;
|
||||
use FlatType::*;
|
||||
match content {
|
||||
FlexVar(_) | RigidVar(_) => {}
|
||||
FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) => {}
|
||||
RecursionVar {
|
||||
structure,
|
||||
opt_name: _,
|
||||
|
@ -1744,6 +1744,15 @@ pub enum Reason {
|
||||
RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>),
|
||||
RecordDefaultField(Lowercase),
|
||||
NumericLiteralSuffix,
|
||||
InvalidAbilityMemberSpecialization {
|
||||
member_name: Symbol,
|
||||
def_region: Region,
|
||||
unimplemented_abilities: DoesNotImplementAbility,
|
||||
},
|
||||
GeneralizedAbilityMemberSpecialization {
|
||||
member_name: Symbol,
|
||||
def_region: Region,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
@ -1783,6 +1792,8 @@ pub enum Category {
|
||||
Accessor(Lowercase),
|
||||
Access(Lowercase),
|
||||
DefaultValue(Lowercase), // for setting optional fields
|
||||
|
||||
AbilityMemberSpecialization(Symbol),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@ -1867,14 +1878,19 @@ pub enum Mismatch {
|
||||
InconsistentWhenBranches,
|
||||
CanonicalizationProblem,
|
||||
TypeNotInRange,
|
||||
DoesNotImplementAbiity(Variable, Symbol),
|
||||
}
|
||||
|
||||
pub type DoesNotImplementAbility = Vec<(ErrorType, Symbol)>;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Hash)]
|
||||
pub enum ErrorType {
|
||||
Infinite,
|
||||
Type(Symbol, Vec<ErrorType>),
|
||||
FlexVar(Lowercase),
|
||||
RigidVar(Lowercase),
|
||||
FlexAbleVar(Lowercase, Symbol),
|
||||
RigidAbleVar(Lowercase, Symbol),
|
||||
Record(SendMap<Lowercase, RecordField<ErrorType>>, TypeExt),
|
||||
TagUnion(SendMap<TagName, Vec<ErrorType>>, TypeExt),
|
||||
RecursiveTagUnion(Box<ErrorType>, SendMap<TagName, Vec<ErrorType>>, TypeExt),
|
||||
@ -1905,10 +1921,7 @@ impl ErrorType {
|
||||
match self {
|
||||
Infinite => {}
|
||||
Type(_, ts) => ts.iter().for_each(|t| t.add_names(taken)),
|
||||
FlexVar(v) => {
|
||||
taken.insert(v.clone());
|
||||
}
|
||||
RigidVar(v) => {
|
||||
FlexVar(v) | RigidVar(v) | FlexAbleVar(v, _) | RigidAbleVar(v, _) => {
|
||||
taken.insert(v.clone());
|
||||
}
|
||||
Record(fields, ext) => {
|
||||
@ -2087,8 +2100,18 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
||||
match error_type {
|
||||
Infinite => buf.push('∞'),
|
||||
Error => buf.push('?'),
|
||||
FlexVar(name) => buf.push_str(name.as_str()),
|
||||
RigidVar(name) => buf.push_str(name.as_str()),
|
||||
FlexVar(name) | RigidVar(name) => buf.push_str(name.as_str()),
|
||||
FlexAbleVar(name, symbol) | RigidAbleVar(name, symbol) => {
|
||||
let write_parens = parens == Parens::InTypeParam;
|
||||
if write_parens {
|
||||
buf.push('(');
|
||||
}
|
||||
buf.push_str(name.as_str());
|
||||
buf.push_str(&format!(" has {:?}", symbol));
|
||||
if write_parens {
|
||||
buf.push(')');
|
||||
}
|
||||
}
|
||||
Type(symbol, arguments) => {
|
||||
let write_parens = parens == Parens::InTypeParam && !arguments.is_empty();
|
||||
|
||||
|
@ -11,6 +11,9 @@ bitflags = "1.3.2"
|
||||
[dependencies.roc_collections]
|
||||
path = "../collections"
|
||||
|
||||
[dependencies.roc_error_macros]
|
||||
path = "../../error_macros"
|
||||
|
||||
[dependencies.roc_module]
|
||||
path = "../module"
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use bitflags::bitflags;
|
||||
use roc_error_macros::todo_abilities;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::subs::Content::{self, *};
|
||||
@ -6,7 +7,7 @@ use roc_types::subs::{
|
||||
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable,
|
||||
RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice,
|
||||
};
|
||||
use roc_types::types::{AliasKind, ErrorType, Mismatch, RecordField};
|
||||
use roc_types::types::{AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, RecordField};
|
||||
|
||||
macro_rules! mismatch {
|
||||
() => {{
|
||||
@ -19,7 +20,10 @@ macro_rules! mismatch {
|
||||
);
|
||||
}
|
||||
|
||||
vec![Mismatch::TypeMismatch]
|
||||
Outcome {
|
||||
mismatches: vec![Mismatch::TypeMismatch],
|
||||
..Outcome::default()
|
||||
}
|
||||
}};
|
||||
($msg:expr) => {{
|
||||
if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() {
|
||||
@ -34,7 +38,10 @@ macro_rules! mismatch {
|
||||
}
|
||||
|
||||
|
||||
vec![Mismatch::TypeMismatch]
|
||||
Outcome {
|
||||
mismatches: vec![Mismatch::TypeMismatch],
|
||||
..Outcome::default()
|
||||
}
|
||||
}};
|
||||
($msg:expr,) => {{
|
||||
mismatch!($msg)
|
||||
@ -51,8 +58,28 @@ macro_rules! mismatch {
|
||||
println!("");
|
||||
}
|
||||
|
||||
vec![Mismatch::TypeMismatch]
|
||||
Outcome {
|
||||
mismatches: vec![Mismatch::TypeMismatch],
|
||||
..Outcome::default()
|
||||
}
|
||||
}};
|
||||
(%not_able, $var:expr, $ability:expr, $msg:expr, $($arg:tt)*) => {{
|
||||
if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() {
|
||||
println!(
|
||||
"Mismatch in {} Line {} Column {}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
println!($msg, $($arg)*);
|
||||
println!("");
|
||||
}
|
||||
|
||||
Outcome {
|
||||
mismatches: vec![Mismatch::TypeMismatch, Mismatch::DoesNotImplementAbiity($var, $ability)],
|
||||
..Outcome::default()
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
type Pool = Vec<Variable>;
|
||||
@ -105,20 +132,52 @@ pub struct Context {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Unified {
|
||||
Success(Pool),
|
||||
Failure(Pool, ErrorType, ErrorType),
|
||||
Success {
|
||||
vars: Pool,
|
||||
must_implement_ability: Vec<MustImplementAbility>,
|
||||
},
|
||||
Failure(Pool, ErrorType, ErrorType, DoesNotImplementAbility),
|
||||
BadType(Pool, roc_types::types::Problem),
|
||||
}
|
||||
|
||||
type Outcome = Vec<Mismatch>;
|
||||
/// Specifies that `type` must implement the ability `ability`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MustImplementAbility {
|
||||
// This only points to opaque type names currently.
|
||||
// TODO(abilities) support structural types in general
|
||||
pub typ: Symbol,
|
||||
pub ability: Symbol,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Outcome {
|
||||
mismatches: Vec<Mismatch>,
|
||||
/// We defer these checks until the end of a solving phase.
|
||||
/// NOTE: this vector is almost always empty!
|
||||
must_implement_ability: Vec<MustImplementAbility>,
|
||||
}
|
||||
|
||||
impl Outcome {
|
||||
fn union(&mut self, other: Self) {
|
||||
self.mismatches.extend(other.mismatches);
|
||||
self.must_implement_ability
|
||||
.extend(other.must_implement_ability);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Unified {
|
||||
let mut vars = Vec::new();
|
||||
let mismatches = unify_pool(subs, &mut vars, var1, var2, mode);
|
||||
let Outcome {
|
||||
mismatches,
|
||||
must_implement_ability,
|
||||
} = unify_pool(subs, &mut vars, var1, var2, mode);
|
||||
|
||||
if mismatches.is_empty() {
|
||||
Unified::Success(vars)
|
||||
Unified::Success {
|
||||
vars,
|
||||
must_implement_ability,
|
||||
}
|
||||
} else {
|
||||
let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) {
|
||||
ErrorTypeContext::ExpandRanges
|
||||
@ -136,7 +195,19 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Uni
|
||||
if !problems.is_empty() {
|
||||
Unified::BadType(vars, problems.remove(0))
|
||||
} else {
|
||||
Unified::Failure(vars, type1, type2)
|
||||
let do_not_implement_ability = mismatches
|
||||
.into_iter()
|
||||
.filter_map(|mismatch| match mismatch {
|
||||
Mismatch::DoesNotImplementAbiity(var, ab) => {
|
||||
let (err_type, _new_problems) =
|
||||
subs.var_to_error_type_contextual(var, error_context);
|
||||
Some((err_type, ab))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Unified::Failure(vars, type1, type2, do_not_implement_ability)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,7 +221,7 @@ pub fn unify_pool(
|
||||
mode: Mode,
|
||||
) -> Outcome {
|
||||
if subs.equivalent(var1, var2) {
|
||||
Vec::new()
|
||||
Outcome::default()
|
||||
} else {
|
||||
let ctx = Context {
|
||||
first: var1,
|
||||
@ -164,8 +235,10 @@ pub fn unify_pool(
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
||||
if false {
|
||||
#[cfg(debug_assertions)]
|
||||
fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, before_unified: bool) {
|
||||
if std::env::var("ROC_PRINT_UNIFICATIONS").is_ok() {
|
||||
let time = if before_unified { "START" } else { "END" };
|
||||
// if true, print the types that are unified.
|
||||
//
|
||||
// NOTE: names are generated here (when creating an error type) and that modifies names
|
||||
@ -181,8 +254,11 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
||||
let content_1 = subs.get(ctx.first).content;
|
||||
let content_2 = subs.get(ctx.second).content;
|
||||
let mode = if ctx.mode.is_eq() { "~" } else { "+=" };
|
||||
println!(
|
||||
"{:?} {:?} {} {:?} {:?}",
|
||||
eprintln!(
|
||||
"{}({:?}-{:?}): {:?} {:?} {} {:?} {:?}",
|
||||
time,
|
||||
ctx.first,
|
||||
ctx.second,
|
||||
ctx.first,
|
||||
roc_types::subs::SubsFmtContent(&content_1, subs),
|
||||
mode,
|
||||
@ -190,8 +266,21 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
||||
roc_types::subs::SubsFmtContent(&content_2, subs),
|
||||
);
|
||||
}
|
||||
match &ctx.first_desc.content {
|
||||
FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, &ctx.second_desc.content),
|
||||
}
|
||||
|
||||
fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
||||
#[cfg(debug_assertions)]
|
||||
debug_print_unified_types(subs, &ctx, true);
|
||||
|
||||
let result = match &ctx.first_desc.content {
|
||||
FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, None, &ctx.second_desc.content),
|
||||
FlexAbleVar(opt_name, ability) => unify_flex(
|
||||
subs,
|
||||
&ctx,
|
||||
opt_name,
|
||||
Some(*ability),
|
||||
&ctx.second_desc.content,
|
||||
),
|
||||
RecursionVar {
|
||||
opt_name,
|
||||
structure,
|
||||
@ -203,7 +292,10 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
||||
*structure,
|
||||
&ctx.second_desc.content,
|
||||
),
|
||||
RigidVar(name) => unify_rigid(subs, &ctx, name, &ctx.second_desc.content),
|
||||
RigidVar(name) => unify_rigid(subs, &ctx, name, None, &ctx.second_desc.content),
|
||||
RigidAbleVar(name, ability) => {
|
||||
unify_rigid(subs, &ctx, name, Some(*ability), &ctx.second_desc.content)
|
||||
}
|
||||
Structure(flat_type) => {
|
||||
unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content)
|
||||
}
|
||||
@ -215,7 +307,12 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
||||
// Error propagates. Whatever we're comparing it to doesn't matter!
|
||||
merge(subs, &ctx, Error)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
debug_print_unified_types(subs, &ctx, true);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -238,7 +335,7 @@ fn unify_ranged_number(
|
||||
}
|
||||
&RangedNumber(other_real_var, other_range_vars) => {
|
||||
let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode);
|
||||
if outcome.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
check_valid_range(subs, pool, ctx.first, other_range_vars, ctx.mode)
|
||||
} else {
|
||||
outcome
|
||||
@ -246,9 +343,12 @@ fn unify_ranged_number(
|
||||
// TODO: We should probably check that "range_vars" and "other_range_vars" intersect
|
||||
}
|
||||
Error => merge(subs, ctx, Error),
|
||||
FlexAbleVar(..) | RigidAbleVar(..) => {
|
||||
todo_abilities!("I don't think this can be reached yet")
|
||||
}
|
||||
};
|
||||
|
||||
if !outcome.is_empty() {
|
||||
if !outcome.mismatches.is_empty() {
|
||||
return outcome;
|
||||
}
|
||||
|
||||
@ -269,11 +369,11 @@ fn check_valid_range(
|
||||
let snapshot = subs.snapshot();
|
||||
let old_pool = pool.clone();
|
||||
let outcome = unify_pool(subs, pool, var, possible_var, mode | Mode::RIGID_AS_FLEX);
|
||||
if outcome.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
// Okay, we matched some type in the range.
|
||||
subs.rollback_to(snapshot);
|
||||
*pool = old_pool;
|
||||
return vec![];
|
||||
return Outcome::default();
|
||||
} else if it.peek().is_some() {
|
||||
// We failed to match something in the range, but there are still things we can try.
|
||||
subs.rollback_to(snapshot);
|
||||
@ -283,7 +383,10 @@ fn check_valid_range(
|
||||
}
|
||||
}
|
||||
|
||||
return vec![Mismatch::TypeNotInRange];
|
||||
Outcome {
|
||||
mismatches: vec![Mismatch::TypeNotInRange],
|
||||
..Outcome::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -310,13 +413,19 @@ fn unify_alias(
|
||||
unify_pool(subs, pool, real_var, *structure, ctx.mode)
|
||||
}
|
||||
RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
|
||||
RigidAbleVar (_, ability) | FlexAbleVar(_, ability) if kind == AliasKind::Opaque && args.is_empty() => {
|
||||
// Opaque type wins
|
||||
let mut outcome = merge(subs, ctx, Alias(symbol, args, real_var, kind));
|
||||
outcome.must_implement_ability.push(MustImplementAbility { typ: symbol, ability: *ability });
|
||||
outcome
|
||||
}
|
||||
Alias(other_symbol, other_args, other_real_var, _)
|
||||
// Opaques types are only equal if the opaque symbols are equal!
|
||||
if !either_is_opaque || symbol == *other_symbol =>
|
||||
{
|
||||
if symbol == *other_symbol {
|
||||
if args.len() == other_args.len() {
|
||||
let mut problems = Vec::new();
|
||||
let mut outcome = Outcome::default();
|
||||
let it = args
|
||||
.all_variables()
|
||||
.into_iter()
|
||||
@ -327,23 +436,23 @@ fn unify_alias(
|
||||
for (l, r) in it {
|
||||
let l_var = subs[l];
|
||||
let r_var = subs[r];
|
||||
problems.extend(unify_pool(subs, pool, l_var, r_var, ctx.mode));
|
||||
outcome.union(unify_pool(subs, pool, l_var, r_var, ctx.mode));
|
||||
}
|
||||
|
||||
if problems.is_empty() {
|
||||
problems.extend(merge(subs, ctx, *other_content));
|
||||
if outcome.mismatches.is_empty() {
|
||||
outcome.union(merge(subs, ctx, *other_content));
|
||||
}
|
||||
|
||||
let args_unification_had_changes = !subs.vars_since_snapshot(&args_unification_snapshot).is_empty();
|
||||
subs.commit_snapshot(args_unification_snapshot);
|
||||
|
||||
if !args.is_empty() && args_unification_had_changes && problems.is_empty() {
|
||||
if !args.is_empty() && args_unification_had_changes && outcome.mismatches.is_empty() {
|
||||
// We need to unify the real vars because unification of type variables
|
||||
// may have made them larger, which then needs to be reflected in the `real_var`.
|
||||
problems.extend(unify_pool(subs, pool, real_var, *other_real_var, ctx.mode));
|
||||
outcome.union(unify_pool(subs, pool, real_var, *other_real_var, ctx.mode));
|
||||
}
|
||||
|
||||
problems
|
||||
outcome
|
||||
} else {
|
||||
dbg!(args.len(), other_args.len());
|
||||
mismatch!("{:?}", symbol)
|
||||
@ -355,7 +464,7 @@ fn unify_alias(
|
||||
Structure(_) if !either_is_opaque => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
|
||||
RangedNumber(other_real_var, other_range_vars) if !either_is_opaque => {
|
||||
let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode);
|
||||
if outcome.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode)
|
||||
} else {
|
||||
outcome
|
||||
@ -448,13 +557,31 @@ fn unify_structure(
|
||||
},
|
||||
RangedNumber(other_real_var, other_range_vars) => {
|
||||
let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode);
|
||||
if outcome.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode)
|
||||
} else {
|
||||
outcome
|
||||
}
|
||||
}
|
||||
Error => merge(subs, ctx, Error),
|
||||
|
||||
FlexAbleVar(_, ability) => {
|
||||
// TODO(abilities) support structural types in ability bounds
|
||||
mismatch!(
|
||||
%not_able, ctx.first, *ability,
|
||||
"trying to unify {:?} with FlexAble {:?}",
|
||||
&flat_type,
|
||||
&other
|
||||
)
|
||||
}
|
||||
RigidAbleVar(_, ability) => {
|
||||
mismatch!(
|
||||
%not_able, ctx.first, *ability,
|
||||
"trying to unify {:?} with RigidAble {:?}",
|
||||
&flat_type,
|
||||
&other
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -474,29 +601,29 @@ fn unify_record(
|
||||
if separate.only_in_1.is_empty() {
|
||||
if separate.only_in_2.is_empty() {
|
||||
// these variable will be the empty record, but we must still unify them
|
||||
let ext_problems = unify_pool(subs, pool, ext1, ext2, ctx.mode);
|
||||
let ext_outcome = unify_pool(subs, pool, ext1, ext2, ctx.mode);
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
|
||||
let mut field_problems =
|
||||
let mut field_outcome =
|
||||
unify_shared_fields(subs, pool, ctx, shared_fields, OtherFields::None, ext1);
|
||||
|
||||
field_problems.extend(ext_problems);
|
||||
field_outcome.union(ext_outcome);
|
||||
|
||||
field_problems
|
||||
field_outcome
|
||||
} else {
|
||||
let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2);
|
||||
let flat_type = FlatType::Record(only_in_2, ext2);
|
||||
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
||||
let ext_problems = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
|
||||
let ext_outcome = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
|
||||
let mut field_problems = unify_shared_fields(
|
||||
let mut field_outcome = unify_shared_fields(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
@ -505,21 +632,21 @@ fn unify_record(
|
||||
sub_record,
|
||||
);
|
||||
|
||||
field_problems.extend(ext_problems);
|
||||
field_outcome.union(ext_outcome);
|
||||
|
||||
field_problems
|
||||
field_outcome
|
||||
}
|
||||
} else if separate.only_in_2.is_empty() {
|
||||
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1);
|
||||
let flat_type = FlatType::Record(only_in_1, ext1);
|
||||
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
||||
let ext_problems = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
|
||||
let ext_outcome = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
|
||||
let mut field_problems = unify_shared_fields(
|
||||
let mut field_outcome = unify_shared_fields(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
@ -528,9 +655,9 @@ fn unify_record(
|
||||
sub_record,
|
||||
);
|
||||
|
||||
field_problems.extend(ext_problems);
|
||||
field_outcome.union(ext_outcome);
|
||||
|
||||
field_problems
|
||||
field_outcome
|
||||
} else {
|
||||
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1);
|
||||
let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2);
|
||||
@ -544,24 +671,26 @@ fn unify_record(
|
||||
let sub1 = fresh(subs, pool, ctx, Structure(flat_type1));
|
||||
let sub2 = fresh(subs, pool, ctx, Structure(flat_type2));
|
||||
|
||||
let rec1_problems = unify_pool(subs, pool, ext1, sub2, ctx.mode);
|
||||
if !rec1_problems.is_empty() {
|
||||
return rec1_problems;
|
||||
let rec1_outcome = unify_pool(subs, pool, ext1, sub2, ctx.mode);
|
||||
if !rec1_outcome.mismatches.is_empty() {
|
||||
return rec1_outcome;
|
||||
}
|
||||
|
||||
let rec2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode);
|
||||
if !rec2_problems.is_empty() {
|
||||
return rec2_problems;
|
||||
let rec2_outcome = unify_pool(subs, pool, sub1, ext2, ctx.mode);
|
||||
if !rec2_outcome.mismatches.is_empty() {
|
||||
return rec2_outcome;
|
||||
}
|
||||
|
||||
let mut field_problems =
|
||||
let mut field_outcome =
|
||||
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, ext);
|
||||
|
||||
field_problems.reserve(rec1_problems.len() + rec2_problems.len());
|
||||
field_problems.extend(rec1_problems);
|
||||
field_problems.extend(rec2_problems);
|
||||
field_outcome
|
||||
.mismatches
|
||||
.reserve(rec1_outcome.mismatches.len() + rec2_outcome.mismatches.len());
|
||||
field_outcome.union(rec1_outcome);
|
||||
field_outcome.union(rec2_outcome);
|
||||
|
||||
field_problems
|
||||
field_outcome
|
||||
}
|
||||
}
|
||||
|
||||
@ -584,7 +713,7 @@ fn unify_shared_fields(
|
||||
let num_shared_fields = shared_fields.len();
|
||||
|
||||
for (name, (actual, expected)) in shared_fields {
|
||||
let local_problems = unify_pool(
|
||||
let local_outcome = unify_pool(
|
||||
subs,
|
||||
pool,
|
||||
actual.into_inner(),
|
||||
@ -592,7 +721,7 @@ fn unify_shared_fields(
|
||||
ctx.mode,
|
||||
);
|
||||
|
||||
if local_problems.is_empty() {
|
||||
if local_outcome.mismatches.is_empty() {
|
||||
use RecordField::*;
|
||||
|
||||
// Unification of optional fields
|
||||
@ -856,18 +985,18 @@ fn unify_tag_union_new(
|
||||
|
||||
if separate.only_in_1.is_empty() {
|
||||
if separate.only_in_2.is_empty() {
|
||||
let ext_problems = if ctx.mode.is_eq() {
|
||||
let ext_outcome = if ctx.mode.is_eq() {
|
||||
unify_pool(subs, pool, ext1, ext2, ctx.mode)
|
||||
} else {
|
||||
// In a presence context, we don't care about ext2 being equal to ext1
|
||||
vec![]
|
||||
Outcome::default()
|
||||
};
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
|
||||
let mut tag_problems = unify_shared_tags_new(
|
||||
let mut shared_tags_outcome = unify_shared_tags_new(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
@ -877,20 +1006,20 @@ fn unify_tag_union_new(
|
||||
recursion_var,
|
||||
);
|
||||
|
||||
tag_problems.extend(ext_problems);
|
||||
shared_tags_outcome.union(ext_outcome);
|
||||
|
||||
tag_problems
|
||||
shared_tags_outcome
|
||||
} else {
|
||||
let unique_tags2 = UnionTags::insert_slices_into_subs(subs, separate.only_in_2);
|
||||
let flat_type = FlatType::TagUnion(unique_tags2, ext2);
|
||||
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
||||
let ext_problems = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
|
||||
let ext_outcome = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
|
||||
let mut tag_problems = unify_shared_tags_new(
|
||||
let mut shared_tags_outcome = unify_shared_tags_new(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
@ -900,9 +1029,9 @@ fn unify_tag_union_new(
|
||||
recursion_var,
|
||||
);
|
||||
|
||||
tag_problems.extend(ext_problems);
|
||||
shared_tags_outcome.union(ext_outcome);
|
||||
|
||||
tag_problems
|
||||
shared_tags_outcome
|
||||
}
|
||||
} else if separate.only_in_2.is_empty() {
|
||||
let unique_tags1 = UnionTags::insert_slices_into_subs(subs, separate.only_in_1);
|
||||
@ -911,10 +1040,10 @@ fn unify_tag_union_new(
|
||||
|
||||
// In a presence context, we don't care about ext2 being equal to tags1
|
||||
if ctx.mode.is_eq() {
|
||||
let ext_problems = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
|
||||
let ext_outcome = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
|
||||
|
||||
if !ext_problems.is_empty() {
|
||||
return ext_problems;
|
||||
if !ext_outcome.mismatches.is_empty() {
|
||||
return ext_outcome;
|
||||
}
|
||||
}
|
||||
|
||||
@ -961,17 +1090,17 @@ fn unify_tag_union_new(
|
||||
|
||||
let snapshot = subs.snapshot();
|
||||
|
||||
let ext1_problems = unify_pool(subs, pool, ext1, sub2, ctx.mode);
|
||||
if !ext1_problems.is_empty() {
|
||||
let ext1_outcome = unify_pool(subs, pool, ext1, sub2, ctx.mode);
|
||||
if !ext1_outcome.mismatches.is_empty() {
|
||||
subs.rollback_to(snapshot);
|
||||
return ext1_problems;
|
||||
return ext1_outcome;
|
||||
}
|
||||
|
||||
if ctx.mode.is_eq() {
|
||||
let ext2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode);
|
||||
if !ext2_problems.is_empty() {
|
||||
let ext2_outcome = unify_pool(subs, pool, sub1, ext2, ctx.mode);
|
||||
if !ext2_outcome.mismatches.is_empty() {
|
||||
subs.rollback_to(snapshot);
|
||||
return ext2_problems;
|
||||
return ext2_outcome;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1063,17 +1192,17 @@ fn unify_shared_tags_new(
|
||||
maybe_mark_tag_union_recursive(subs, actual);
|
||||
maybe_mark_tag_union_recursive(subs, expected);
|
||||
|
||||
let mut problems = Vec::new();
|
||||
let mut outcome = Outcome::default();
|
||||
|
||||
problems.extend(unify_pool(subs, pool, actual, expected, ctx.mode));
|
||||
outcome.union(unify_pool(subs, pool, actual, expected, ctx.mode));
|
||||
|
||||
// clearly, this is very suspicious: these variables have just been unified. And yet,
|
||||
// not doing this leads to stack overflows
|
||||
if let Rec::Right(_) = recursion_var {
|
||||
if problems.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
matching_vars.push(expected);
|
||||
}
|
||||
} else if problems.is_empty() {
|
||||
} else if outcome.mismatches.is_empty() {
|
||||
matching_vars.push(actual);
|
||||
}
|
||||
}
|
||||
@ -1215,39 +1344,43 @@ fn unify_flat_type(
|
||||
debug_assert!(is_recursion_var(subs, *rec2));
|
||||
|
||||
let rec = Rec::Both(*rec1, *rec2);
|
||||
let mut problems =
|
||||
let mut outcome =
|
||||
unify_tag_union_new(subs, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec);
|
||||
problems.extend(unify_pool(subs, pool, *rec1, *rec2, ctx.mode));
|
||||
outcome.union(unify_pool(subs, pool, *rec1, *rec2, ctx.mode));
|
||||
|
||||
problems
|
||||
outcome
|
||||
}
|
||||
|
||||
(Apply(l_symbol, l_args), Apply(r_symbol, r_args)) if l_symbol == r_symbol => {
|
||||
let problems = unify_zip_slices(subs, pool, *l_args, *r_args);
|
||||
let mut outcome = unify_zip_slices(subs, pool, *l_args, *r_args);
|
||||
|
||||
if problems.is_empty() {
|
||||
merge(subs, ctx, Structure(Apply(*r_symbol, *r_args)))
|
||||
} else {
|
||||
problems
|
||||
if outcome.mismatches.is_empty() {
|
||||
outcome.union(merge(subs, ctx, Structure(Apply(*r_symbol, *r_args))));
|
||||
}
|
||||
|
||||
outcome
|
||||
}
|
||||
(Func(l_args, l_closure, l_ret), Func(r_args, r_closure, r_ret))
|
||||
if l_args.len() == r_args.len() =>
|
||||
{
|
||||
let arg_problems = unify_zip_slices(subs, pool, *l_args, *r_args);
|
||||
let ret_problems = unify_pool(subs, pool, *l_ret, *r_ret, ctx.mode);
|
||||
let closure_problems = unify_pool(subs, pool, *l_closure, *r_closure, ctx.mode);
|
||||
let arg_outcome = unify_zip_slices(subs, pool, *l_args, *r_args);
|
||||
let ret_outcome = unify_pool(subs, pool, *l_ret, *r_ret, ctx.mode);
|
||||
let closure_outcome = unify_pool(subs, pool, *l_closure, *r_closure, ctx.mode);
|
||||
|
||||
if arg_problems.is_empty() && closure_problems.is_empty() && ret_problems.is_empty() {
|
||||
merge(subs, ctx, Structure(Func(*r_args, *r_closure, *r_ret)))
|
||||
} else {
|
||||
let mut problems = ret_problems;
|
||||
let mut outcome = ret_outcome;
|
||||
|
||||
problems.extend(closure_problems);
|
||||
problems.extend(arg_problems);
|
||||
outcome.union(closure_outcome);
|
||||
outcome.union(arg_outcome);
|
||||
|
||||
problems
|
||||
if outcome.mismatches.is_empty() {
|
||||
outcome.union(merge(
|
||||
subs,
|
||||
ctx,
|
||||
Structure(Func(*r_args, *r_closure, *r_ret)),
|
||||
));
|
||||
}
|
||||
|
||||
outcome
|
||||
}
|
||||
(FunctionOrTagUnion(tag_name, tag_symbol, ext), Func(args, closure, ret)) => {
|
||||
unify_function_or_tag_union_and_func(
|
||||
@ -1282,12 +1415,12 @@ fn unify_flat_type(
|
||||
let tag_name_2_ref = &subs[*tag_name_2];
|
||||
|
||||
if tag_name_1_ref == tag_name_2_ref {
|
||||
let problems = unify_pool(subs, pool, *ext1, *ext2, ctx.mode);
|
||||
if problems.is_empty() {
|
||||
let outcome = unify_pool(subs, pool, *ext1, *ext2, ctx.mode);
|
||||
if outcome.mismatches.is_empty() {
|
||||
let content = *subs.get_content_without_compacting(ctx.second);
|
||||
merge(subs, ctx, content)
|
||||
} else {
|
||||
problems
|
||||
outcome
|
||||
}
|
||||
} else {
|
||||
let tags1 = UnionTags::from_tag_name_index(*tag_name_1);
|
||||
@ -1343,7 +1476,7 @@ fn unify_zip_slices(
|
||||
left: SubsSlice<Variable>,
|
||||
right: SubsSlice<Variable>,
|
||||
) -> Outcome {
|
||||
let mut problems = Vec::new();
|
||||
let mut outcome = Outcome::default();
|
||||
|
||||
let it = left.into_iter().zip(right.into_iter());
|
||||
|
||||
@ -1351,10 +1484,10 @@ fn unify_zip_slices(
|
||||
let l_var = subs[l_index];
|
||||
let r_var = subs[r_index];
|
||||
|
||||
problems.extend(unify_pool(subs, pool, l_var, r_var, Mode::EQ));
|
||||
outcome.union(unify_pool(subs, pool, l_var, r_var, Mode::EQ));
|
||||
}
|
||||
|
||||
problems
|
||||
outcome
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -1362,6 +1495,7 @@ fn unify_rigid(
|
||||
subs: &mut Subs,
|
||||
ctx: &Context,
|
||||
name: &SubsIndex<Lowercase>,
|
||||
opt_able_bound: Option<Symbol>,
|
||||
other: &Content,
|
||||
) -> Outcome {
|
||||
match other {
|
||||
@ -1369,16 +1503,76 @@ fn unify_rigid(
|
||||
// If the other is flex, rigid wins!
|
||||
merge(subs, ctx, RigidVar(*name))
|
||||
}
|
||||
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) => {
|
||||
if !ctx.mode.contains(Mode::RIGID_AS_FLEX) {
|
||||
// Type mismatch! Rigid can only unify with flex, even if the
|
||||
// rigid names are the same.
|
||||
mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
|
||||
} else {
|
||||
// We are treating rigid vars as flex vars; admit this
|
||||
merge(subs, ctx, *other)
|
||||
FlexAbleVar(_, other_ability) => {
|
||||
match opt_able_bound {
|
||||
Some(ability) => {
|
||||
if ability == *other_ability {
|
||||
// The ability bounds are the same, so rigid wins!
|
||||
merge(subs, ctx, RigidAbleVar(*name, ability))
|
||||
} else {
|
||||
// Mismatch for now.
|
||||
// TODO check ability hierarchies.
|
||||
mismatch!(
|
||||
%not_able, ctx.second, ability,
|
||||
"RigidAble {:?} with ability {:?} not compatible with ability {:?}",
|
||||
ctx.first,
|
||||
ability,
|
||||
other_ability
|
||||
)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Mismatch - Rigid can unify with FlexAble only when the Rigid has an ability
|
||||
// bound as well, otherwise the user failed to correctly annotate the bound.
|
||||
mismatch!(
|
||||
%not_able, ctx.first, *other_ability,
|
||||
"Rigid {:?} with FlexAble {:?}", ctx.first, other
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..)
|
||||
if ctx.mode.contains(Mode::RIGID_AS_FLEX) =>
|
||||
{
|
||||
// Usually rigids can only unify with flex, but the mode indicates we are treating
|
||||
// rigid vars as flex, so admit this.
|
||||
match (opt_able_bound, other) {
|
||||
(None, other) => merge(subs, ctx, *other),
|
||||
(Some(ability), Alias(opaque_name, vars, _real_var, AliasKind::Opaque))
|
||||
if vars.is_empty() =>
|
||||
{
|
||||
let mut output = merge(subs, ctx, *other);
|
||||
let must_implement_ability = MustImplementAbility {
|
||||
typ: *opaque_name,
|
||||
ability,
|
||||
};
|
||||
output.must_implement_ability.push(must_implement_ability);
|
||||
output
|
||||
}
|
||||
(Some(ability), other) => {
|
||||
// For now, only allow opaque types with no type variables to implement abilities.
|
||||
mismatch!(
|
||||
%not_able, ctx.second, ability,
|
||||
"RigidAble {:?} with non-opaque or opaque with type variables {:?}",
|
||||
ctx.first,
|
||||
&other
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RigidVar(_)
|
||||
| RigidAbleVar(..)
|
||||
| RecursionVar { .. }
|
||||
| Structure(_)
|
||||
| Alias(..)
|
||||
| RangedNumber(..) => {
|
||||
// Type mismatch! Rigid can only unify with flex, even if the
|
||||
// rigid names are the same.
|
||||
mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
|
||||
}
|
||||
|
||||
Error => {
|
||||
// Error propagates.
|
||||
merge(subs, ctx, Error)
|
||||
@ -1391,16 +1585,49 @@ fn unify_flex(
|
||||
subs: &mut Subs,
|
||||
ctx: &Context,
|
||||
opt_name: &Option<SubsIndex<Lowercase>>,
|
||||
opt_able_bound: Option<Symbol>,
|
||||
other: &Content,
|
||||
) -> Outcome {
|
||||
match other {
|
||||
FlexVar(None) => {
|
||||
// If both are flex, and only left has a name, keep the name around.
|
||||
merge(subs, ctx, FlexVar(*opt_name))
|
||||
match opt_able_bound {
|
||||
Some(ability) => merge(subs, ctx, FlexAbleVar(*opt_name, ability)),
|
||||
None => merge(subs, ctx, FlexVar(*opt_name)),
|
||||
}
|
||||
}
|
||||
|
||||
FlexAbleVar(opt_other_name, other_ability) => {
|
||||
// Prefer the right's name when possible.
|
||||
let opt_name = (opt_other_name).or(*opt_name);
|
||||
|
||||
match opt_able_bound {
|
||||
Some(ability) => {
|
||||
if ability == *other_ability {
|
||||
// The ability bounds are the same! Keep the name around if it exists.
|
||||
merge(subs, ctx, FlexAbleVar(opt_name, ability))
|
||||
} else {
|
||||
// Ability names differ; mismatch for now.
|
||||
// TODO check ability hierarchies.
|
||||
mismatch!(
|
||||
%not_able, ctx.second, ability,
|
||||
"FlexAble {:?} with ability {:?} not compatible with ability {:?}",
|
||||
ctx.first,
|
||||
ability,
|
||||
other_ability
|
||||
)
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Right has an ability bound, but left might have the name. Combine them.
|
||||
merge(subs, ctx, FlexAbleVar(opt_name, *other_ability))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FlexVar(Some(_))
|
||||
| RigidVar(_)
|
||||
| RigidAbleVar(_, _)
|
||||
| RecursionVar { .. }
|
||||
| Structure(_)
|
||||
| Alias(_, _, _, _)
|
||||
@ -1446,7 +1673,13 @@ fn unify_recursion(
|
||||
// unify the structure variable with this Structure
|
||||
unify_pool(subs, pool, structure, ctx.second, ctx.mode)
|
||||
}
|
||||
RigidVar(_) => mismatch!("RecursionVar {:?} with rigid {:?}", ctx.first, &other),
|
||||
RigidVar(_) => {
|
||||
mismatch!("RecursionVar {:?} with rigid {:?}", ctx.first, &other)
|
||||
}
|
||||
|
||||
FlexAbleVar(..) | RigidAbleVar(..) => {
|
||||
mismatch!("RecursionVar {:?} with able var {:?}", ctx.first, &other)
|
||||
}
|
||||
|
||||
FlexVar(_) => merge(
|
||||
subs,
|
||||
@ -1492,7 +1725,7 @@ pub fn merge(subs: &mut Subs, ctx: &Context, content: Content) -> Outcome {
|
||||
|
||||
subs.union(ctx.first, ctx.second, desc);
|
||||
|
||||
Vec::new()
|
||||
Outcome::default()
|
||||
}
|
||||
|
||||
fn register(subs: &mut Subs, desc: Descriptor, pool: &mut Pool) -> Variable {
|
||||
@ -1543,7 +1776,7 @@ fn unify_function_or_tag_union_and_func(
|
||||
|
||||
let new_tag_union_var = fresh(subs, pool, ctx, content);
|
||||
|
||||
let mut problems = if left {
|
||||
let mut outcome = if left {
|
||||
unify_pool(subs, pool, new_tag_union_var, function_return, ctx.mode)
|
||||
} else {
|
||||
unify_pool(subs, pool, function_return, new_tag_union_var, ctx.mode)
|
||||
@ -1567,16 +1800,16 @@ fn unify_function_or_tag_union_and_func(
|
||||
pool,
|
||||
);
|
||||
|
||||
let closure_problems = if left {
|
||||
let closure_outcome = if left {
|
||||
unify_pool(subs, pool, tag_lambda_set, function_lambda_set, ctx.mode)
|
||||
} else {
|
||||
unify_pool(subs, pool, function_lambda_set, tag_lambda_set, ctx.mode)
|
||||
};
|
||||
|
||||
problems.extend(closure_problems);
|
||||
outcome.union(closure_outcome);
|
||||
}
|
||||
|
||||
if problems.is_empty() {
|
||||
if outcome.mismatches.is_empty() {
|
||||
let desc = if left {
|
||||
subs.get(ctx.second)
|
||||
} else {
|
||||
@ -1586,5 +1819,5 @@ fn unify_function_or_tag_union_and_func(
|
||||
subs.union(ctx.first, ctx.second, desc);
|
||||
}
|
||||
|
||||
problems
|
||||
outcome
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ roc_parse = { path = "../compiler/parse" }
|
||||
roc_target = { path = "../compiler/roc_target" }
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
roc_highlight = { path = "../highlight"}
|
||||
roc_reporting = { path = "../reporting"}
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
snafu = { version = "0.6.10", features = ["backtraces"] }
|
||||
peg = "0.8.0"
|
||||
|
@ -424,6 +424,7 @@ pub fn load_modules_for_files(filenames: Vec<PathBuf>) -> Vec<LoadedModule> {
|
||||
src_dir.as_path(),
|
||||
Default::default(),
|
||||
roc_target::TargetInfo::default_x86_64(), // This is just type-checking for docs, so "target" doesn't matter
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
) {
|
||||
Ok(loaded) => modules.push(loaded),
|
||||
Err(LoadingProblem::FormattedReport(report)) => {
|
||||
|
@ -236,6 +236,8 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
|
||||
print_err(&e)
|
||||
} else if let Ok(InputOutcome::Ignored) = input_outcome_res {
|
||||
println!("Input '{}' ignored!", ch);
|
||||
} else {
|
||||
window.request_redraw()
|
||||
}
|
||||
}
|
||||
//Keyboard Input
|
||||
@ -256,6 +258,8 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
|
||||
if let Err(e) = keydown_res {
|
||||
print_err(&e)
|
||||
}
|
||||
|
||||
window.request_redraw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ pub fn compile_to_mono<'a>(
|
||||
src_dir,
|
||||
exposed_types,
|
||||
target_info,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
);
|
||||
|
||||
let mut loaded = match loaded {
|
||||
|
@ -50,7 +50,7 @@ fn int_addition() {
|
||||
|
||||
#[test]
|
||||
fn float_addition() {
|
||||
expect_success("1.1 + 2", "3.1 : F64");
|
||||
expect_success("1.1 + 2", "3.1 : Float *");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
@ -309,7 +309,7 @@ fn nested_int_list() {
|
||||
fn nested_float_list() {
|
||||
expect_success(
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#,
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List F64))"#,
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List (Float *)))"#,
|
||||
);
|
||||
}
|
||||
|
||||
@ -403,7 +403,7 @@ fn list_contains() {
|
||||
fn list_sum() {
|
||||
expect_success("List.sum []", "0 : Num *");
|
||||
expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *");
|
||||
expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64");
|
||||
expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : Float *");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
@ -1123,3 +1123,20 @@ fn issue_2582_specialize_result_value() {
|
||||
r"<function> : Num *, List Str -> Result Str [ ListWasEmpty ]*",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
fn issue_2818() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
f : {} -> List Str
|
||||
f = \_ ->
|
||||
x = []
|
||||
x
|
||||
f
|
||||
"#
|
||||
),
|
||||
r"<function> : {} -> List Str",
|
||||
)
|
||||
}
|
||||
|
@ -23,9 +23,11 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
[dev-dependencies]
|
||||
roc_constrain = { path = "../compiler/constrain" }
|
||||
roc_builtins = { path = "../compiler/builtins" }
|
||||
roc_load = { path = "../compiler/load" }
|
||||
roc_problem = { path = "../compiler/problem" }
|
||||
roc_parse = { path = "../compiler/parse" }
|
||||
roc_target = { path = "../compiler/roc_target" }
|
||||
roc_test_utils = { path = "../test_utils" }
|
||||
pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
tempfile = "3.2.0"
|
||||
|
@ -44,6 +44,7 @@ const ALIAS_USES_ABILITY: &str = "ALIAS USES ABILITY";
|
||||
const ILLEGAL_HAS_CLAUSE: &str = "ILLEGAL HAS CLAUSE";
|
||||
const ABILITY_MEMBER_MISSING_HAS_CLAUSE: &str = "ABILITY MEMBER MISSING HAS CLAUSE";
|
||||
const ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE: &str = "ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE";
|
||||
const ABILITY_MEMBER_BINDS_MULTIPLE_VARIABLES: &str = "ABILITY MEMBER BINDS MULTIPLE VARIABLES";
|
||||
|
||||
pub fn can_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
@ -684,6 +685,34 @@ pub fn can_problem<'b>(
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
|
||||
Problem::AbilityMemberMultipleBoundVars {
|
||||
member,
|
||||
ability,
|
||||
span_has_clauses,
|
||||
mut bound_var_names,
|
||||
} => {
|
||||
doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The definition of the ability member "),
|
||||
alloc.symbol_unqualified(member),
|
||||
alloc.reflow(" includes multiple variables bound to the "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
alloc.keyword(" ability:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(span_has_clauses)),
|
||||
alloc.reflow("Ability members can only bind one type variable to their parent ability. Otherwise, I wouldn't know what type implements an ability by looking at specializations!"),
|
||||
alloc.concat(vec![
|
||||
alloc.hint("Did you mean to only bind "),
|
||||
alloc.type_variable(bound_var_names.swap_remove(0)),
|
||||
alloc.reflow(" to "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
alloc.reflow("?"),
|
||||
])
|
||||
]);
|
||||
title = ABILITY_MEMBER_BINDS_MULTIPLE_VARIABLES.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
|
||||
Problem::AbilityMemberBindsExternalAbility {
|
||||
member,
|
||||
ability,
|
||||
|
@ -4,7 +4,7 @@ use roc_module::called_via::{BinOp, CalledVia};
|
||||
use roc_module::ident::{Ident, IdentStr, Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{LineInfo, Loc, Region};
|
||||
use roc_solve::solve;
|
||||
use roc_solve::solve::{self, IncompleteAbilityImplementation};
|
||||
use roc_types::pretty_print::{Parens, WILDCARD};
|
||||
use roc_types::types::{
|
||||
AliasKind, Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt,
|
||||
@ -118,9 +118,136 @@ pub fn type_problem<'b>(
|
||||
other => panic!("unhandled bad type: {:?}", other),
|
||||
}
|
||||
}
|
||||
IncompleteAbilityImplementation(incomplete) => {
|
||||
let title = "INCOMPLETE ABILITY IMPLEMENTATION".to_string();
|
||||
|
||||
let doc = report_incomplete_ability(alloc, lines, incomplete);
|
||||
|
||||
report(title, doc, filename)
|
||||
}
|
||||
BadExprMissingAbility(region, category, found, incomplete) => {
|
||||
let note = alloc.stack(vec![
|
||||
alloc.reflow("The ways this expression is used requires that the following types implement the following abilities, which they do not:"),
|
||||
alloc.type_block(alloc.stack(incomplete.iter().map(|incomplete| {
|
||||
symbol_does_not_implement(alloc, incomplete.typ, incomplete.ability)
|
||||
}))),
|
||||
]);
|
||||
let snippet = alloc.region(lines.convert_region(region));
|
||||
let mut stack = vec![
|
||||
alloc.text(
|
||||
"This expression has a type that does not implement the abilities it's expected to:",
|
||||
),
|
||||
snippet,
|
||||
lone_type(
|
||||
alloc,
|
||||
found.clone(),
|
||||
found,
|
||||
ExpectationContext::Arbitrary,
|
||||
add_category(alloc, alloc.text("Right now it's"), &category),
|
||||
note,
|
||||
),
|
||||
];
|
||||
incomplete.into_iter().for_each(|incomplete| {
|
||||
stack.push(report_incomplete_ability(alloc, lines, incomplete))
|
||||
});
|
||||
|
||||
let report = Report {
|
||||
title: "TYPE MISMATCH".to_string(),
|
||||
filename,
|
||||
doc: alloc.stack(stack),
|
||||
severity: Severity::RuntimeError,
|
||||
};
|
||||
Some(report)
|
||||
}
|
||||
BadPatternMissingAbility(region, category, found, incomplete) => {
|
||||
let note = alloc.stack(vec![
|
||||
alloc.reflow("The ways this expression is used requires that the following types implement the following abilities, which they do not:"),
|
||||
alloc.type_block(alloc.stack(incomplete.iter().map(|incomplete| {
|
||||
symbol_does_not_implement(alloc, incomplete.typ, incomplete.ability)
|
||||
}))),
|
||||
]);
|
||||
let snippet = alloc.region(lines.convert_region(region));
|
||||
let mut stack = vec![
|
||||
alloc.text(
|
||||
"This expression has a type does not implement the abilities it's expected to:",
|
||||
),
|
||||
snippet,
|
||||
lone_type(
|
||||
alloc,
|
||||
found.clone(),
|
||||
found,
|
||||
ExpectationContext::Arbitrary,
|
||||
add_pattern_category(alloc, alloc.text("Right now it's"), &category),
|
||||
note,
|
||||
),
|
||||
];
|
||||
incomplete.into_iter().for_each(|incomplete| {
|
||||
stack.push(report_incomplete_ability(alloc, lines, incomplete))
|
||||
});
|
||||
|
||||
let report = Report {
|
||||
title: "TYPE MISMATCH".to_string(),
|
||||
filename,
|
||||
doc: alloc.stack(stack),
|
||||
severity: Severity::RuntimeError,
|
||||
};
|
||||
Some(report)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn report_incomplete_ability<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
lines: &LineInfo,
|
||||
incomplete: IncompleteAbilityImplementation,
|
||||
) -> RocDocBuilder<'a> {
|
||||
let IncompleteAbilityImplementation {
|
||||
typ,
|
||||
ability,
|
||||
specialized_members,
|
||||
missing_members,
|
||||
} = incomplete;
|
||||
|
||||
debug_assert!(!missing_members.is_empty());
|
||||
|
||||
let mut stack = vec![alloc.concat(vec![
|
||||
alloc.reflow("The type "),
|
||||
alloc.symbol_unqualified(typ),
|
||||
alloc.reflow(" does not fully implement the ability "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
alloc.reflow(". The following specializations are missing:"),
|
||||
])];
|
||||
|
||||
for member in missing_members.into_iter() {
|
||||
stack.push(alloc.concat(vec![
|
||||
alloc.reflow("A specialization for "),
|
||||
alloc.symbol_unqualified(member.value),
|
||||
alloc.reflow(", which is defined here:"),
|
||||
]));
|
||||
stack.push(alloc.region(lines.convert_region(member.region)));
|
||||
}
|
||||
|
||||
if !specialized_members.is_empty() {
|
||||
stack.push(alloc.concat(vec![
|
||||
alloc.note(""),
|
||||
alloc.symbol_unqualified(typ),
|
||||
alloc.reflow(" specializes the following members of "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
alloc.reflow(":"),
|
||||
]));
|
||||
|
||||
for spec in specialized_members {
|
||||
stack.push(alloc.concat(vec![
|
||||
alloc.symbol_unqualified(spec.value),
|
||||
alloc.reflow(", specialized here:"),
|
||||
]));
|
||||
stack.push(alloc.region(lines.convert_region(spec.region)));
|
||||
}
|
||||
}
|
||||
|
||||
alloc.stack(stack)
|
||||
}
|
||||
|
||||
fn report_shadowing<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
lines: &LineInfo,
|
||||
@ -950,24 +1077,105 @@ fn to_expr_report<'b>(
|
||||
None,
|
||||
),
|
||||
|
||||
Reason::LowLevelOpArg { op, arg_index } => report_mismatch(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
&category,
|
||||
found,
|
||||
expected_type,
|
||||
region,
|
||||
Some(expr_region),
|
||||
alloc.text(format!(
|
||||
"The {} argument to low level operation {:?} has the wrong type:",
|
||||
Reason::InvalidAbilityMemberSpecialization {
|
||||
member_name,
|
||||
def_region: _,
|
||||
unimplemented_abilities,
|
||||
} => {
|
||||
let problem = alloc.concat(vec![
|
||||
alloc.reflow("Something is off with this specialization of "),
|
||||
alloc.symbol_unqualified(member_name),
|
||||
alloc.reflow(":"),
|
||||
]);
|
||||
let this_is = alloc.reflow("This value is");
|
||||
let instead_of = alloc.concat(vec![
|
||||
alloc.reflow("But the type annotation on "),
|
||||
alloc.symbol_unqualified(member_name),
|
||||
alloc.reflow(" says it must match:"),
|
||||
]);
|
||||
|
||||
let hint = if unimplemented_abilities.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut stack = Vec::with_capacity(unimplemented_abilities.len());
|
||||
for (err_type, ability) in unimplemented_abilities.into_iter() {
|
||||
stack.push(does_not_implement(alloc, err_type, ability));
|
||||
}
|
||||
|
||||
let hint = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.note(""),
|
||||
alloc.reflow("Some types in this specialization don't implement the abilities they are expected to. I found the following missing implementations:"),
|
||||
]),
|
||||
alloc.type_block(alloc.stack(stack)),
|
||||
]);
|
||||
|
||||
Some(hint)
|
||||
};
|
||||
|
||||
report_mismatch(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
&category,
|
||||
found,
|
||||
expected_type,
|
||||
region,
|
||||
Some(expr_region),
|
||||
problem,
|
||||
this_is,
|
||||
instead_of,
|
||||
hint,
|
||||
)
|
||||
}
|
||||
|
||||
Reason::GeneralizedAbilityMemberSpecialization {
|
||||
member_name,
|
||||
def_region: _,
|
||||
} => {
|
||||
let problem = alloc.concat(vec![
|
||||
alloc.reflow("This specialization of "),
|
||||
alloc.symbol_unqualified(member_name),
|
||||
alloc.reflow(" is overly general:"),
|
||||
]);
|
||||
let this_is = alloc.reflow("This value is");
|
||||
let instead_of = alloc.concat(vec![
|
||||
alloc.reflow("But the type annotation on "),
|
||||
alloc.symbol_unqualified(member_name),
|
||||
alloc.reflow(" says it must match:"),
|
||||
]);
|
||||
|
||||
let note = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.note(""),
|
||||
alloc.reflow("The specialized type is too general, and does not provide a concrete type where a type variable is bound to an ability."),
|
||||
]),
|
||||
alloc.reflow("Specializations can only be made for concrete types. If you have a generic implementation for this value, perhaps you don't need an ability?"),
|
||||
]);
|
||||
|
||||
report_mismatch(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
&category,
|
||||
found,
|
||||
expected_type,
|
||||
region,
|
||||
Some(expr_region),
|
||||
problem,
|
||||
this_is,
|
||||
instead_of,
|
||||
Some(note),
|
||||
)
|
||||
}
|
||||
|
||||
Reason::LowLevelOpArg { op, arg_index } => {
|
||||
panic!(
|
||||
"Compiler bug: argument #{} to low-level operation {:?} was the wrong type!",
|
||||
arg_index.ordinal(),
|
||||
op
|
||||
)),
|
||||
alloc.text("Here the value is used as a:"),
|
||||
alloc.text("But the lowlevel expects it to be:"),
|
||||
None,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reason::ForeignCallArg {
|
||||
foreign_symbol,
|
||||
@ -995,6 +1203,30 @@ fn to_expr_report<'b>(
|
||||
}
|
||||
}
|
||||
|
||||
fn does_not_implement<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
err_type: ErrorType,
|
||||
ability: Symbol,
|
||||
) -> RocDocBuilder<'a> {
|
||||
alloc.concat(vec![
|
||||
to_doc(alloc, Parens::Unnecessary, err_type).0,
|
||||
alloc.reflow(" does not implement "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
])
|
||||
}
|
||||
|
||||
fn symbol_does_not_implement<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
symbol: Symbol,
|
||||
ability: Symbol,
|
||||
) -> RocDocBuilder<'a> {
|
||||
alloc.concat(vec![
|
||||
alloc.symbol_unqualified(symbol),
|
||||
alloc.reflow(" does not implement "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
])
|
||||
}
|
||||
|
||||
fn count_arguments(tipe: &ErrorType) -> usize {
|
||||
use ErrorType::*;
|
||||
|
||||
@ -1293,6 +1525,10 @@ fn format_category<'b>(
|
||||
alloc.concat(vec![this_is, alloc.text(" a default field")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
AbilityMemberSpecialization(_ability_member) => (
|
||||
alloc.concat(vec![this_is, alloc.text(" a declared specialization")]),
|
||||
alloc.text(" of type:"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1538,7 +1774,7 @@ fn to_circular_report<'b>(
|
||||
You will see ∞ for parts of the type that repeat \
|
||||
something already printed out infinitely.",
|
||||
),
|
||||
alloc.type_block(to_doc(alloc, Parens::Unnecessary, overall_type)),
|
||||
alloc.type_block(to_doc(alloc, Parens::Unnecessary, overall_type).0),
|
||||
]),
|
||||
])
|
||||
},
|
||||
@ -1639,10 +1875,12 @@ fn to_comparison<'b>(
|
||||
expected: ErrorType,
|
||||
) -> Comparison<'b> {
|
||||
let diff = to_diff(alloc, Parens::Unnecessary, actual, expected);
|
||||
let actual = type_with_able_vars(alloc, diff.left, diff.left_able);
|
||||
let expected = type_with_able_vars(alloc, diff.right, diff.right_able);
|
||||
|
||||
Comparison {
|
||||
actual: alloc.type_block(diff.left),
|
||||
expected: alloc.type_block(diff.right),
|
||||
actual: alloc.type_block(actual),
|
||||
expected: alloc.type_block(expected),
|
||||
problems: match diff.status {
|
||||
Status::Similar => vec![],
|
||||
Status::Different(problems) => problems,
|
||||
@ -1695,6 +1933,9 @@ pub struct Diff<T> {
|
||||
left: T,
|
||||
right: T,
|
||||
status: Status,
|
||||
// idea: lift "able" type variables so they are shown at the top of a type.
|
||||
left_able: AbleVariables,
|
||||
right_able: AbleVariables,
|
||||
}
|
||||
|
||||
fn ext_to_doc<'b>(alloc: &'b RocDocAllocator<'b>, ext: TypeExt) -> Option<RocDocBuilder<'b>> {
|
||||
@ -1706,10 +1947,30 @@ fn ext_to_doc<'b>(alloc: &'b RocDocAllocator<'b>, ext: TypeExt) -> Option<RocDoc
|
||||
}
|
||||
}
|
||||
|
||||
type AbleVariables = Vec<(Lowercase, Symbol)>;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Context {
|
||||
able_variables: AbleVariables,
|
||||
}
|
||||
|
||||
pub fn to_doc<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
parens: Parens,
|
||||
tipe: ErrorType,
|
||||
) -> (RocDocBuilder<'b>, AbleVariables) {
|
||||
let mut ctx = Context::default();
|
||||
|
||||
let doc = to_doc_help(&mut ctx, alloc, parens, tipe);
|
||||
|
||||
(doc, ctx.able_variables)
|
||||
}
|
||||
|
||||
fn to_doc_help<'b>(
|
||||
ctx: &mut Context,
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
parens: Parens,
|
||||
tipe: ErrorType,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use ErrorType::*;
|
||||
|
||||
@ -1718,22 +1979,26 @@ pub fn to_doc<'b>(
|
||||
alloc,
|
||||
parens,
|
||||
args.into_iter()
|
||||
.map(|arg| to_doc(alloc, Parens::InFn, arg))
|
||||
.map(|arg| to_doc_help(ctx, alloc, Parens::InFn, arg))
|
||||
.collect(),
|
||||
to_doc(alloc, Parens::InFn, *ret),
|
||||
to_doc_help(ctx, alloc, Parens::InFn, *ret),
|
||||
),
|
||||
Infinite => alloc.text("∞"),
|
||||
Error => alloc.text("?"),
|
||||
|
||||
FlexVar(lowercase) => alloc.type_variable(lowercase),
|
||||
RigidVar(lowercase) => alloc.type_variable(lowercase),
|
||||
FlexVar(lowercase) | RigidVar(lowercase) => alloc.type_variable(lowercase),
|
||||
FlexAbleVar(lowercase, ability) | RigidAbleVar(lowercase, ability) => {
|
||||
// TODO we should be putting able variables on the toplevel of the type, not here
|
||||
ctx.able_variables.push((lowercase.clone(), ability));
|
||||
alloc.type_variable(lowercase)
|
||||
}
|
||||
|
||||
Type(symbol, args) => report_text::apply(
|
||||
alloc,
|
||||
parens,
|
||||
alloc.symbol_foreign_qualified(symbol),
|
||||
args.into_iter()
|
||||
.map(|arg| to_doc(alloc, Parens::InTypeParam, arg))
|
||||
.map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg))
|
||||
.collect(),
|
||||
),
|
||||
|
||||
@ -1742,7 +2007,7 @@ pub fn to_doc<'b>(
|
||||
parens,
|
||||
alloc.symbol_foreign_qualified(symbol),
|
||||
args.into_iter()
|
||||
.map(|arg| to_doc(alloc, Parens::InTypeParam, arg))
|
||||
.map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg))
|
||||
.collect(),
|
||||
),
|
||||
|
||||
@ -1758,15 +2023,24 @@ pub fn to_doc<'b>(
|
||||
(
|
||||
alloc.string(k.as_str().to_string()),
|
||||
match value {
|
||||
RecordField::Optional(v) => {
|
||||
RecordField::Optional(to_doc(alloc, Parens::Unnecessary, v))
|
||||
}
|
||||
RecordField::Required(v) => {
|
||||
RecordField::Required(to_doc(alloc, Parens::Unnecessary, v))
|
||||
}
|
||||
RecordField::Demanded(v) => {
|
||||
RecordField::Demanded(to_doc(alloc, Parens::Unnecessary, v))
|
||||
}
|
||||
RecordField::Optional(v) => RecordField::Optional(to_doc_help(
|
||||
ctx,
|
||||
alloc,
|
||||
Parens::Unnecessary,
|
||||
v,
|
||||
)),
|
||||
RecordField::Required(v) => RecordField::Required(to_doc_help(
|
||||
ctx,
|
||||
alloc,
|
||||
Parens::Unnecessary,
|
||||
v,
|
||||
)),
|
||||
RecordField::Demanded(v) => RecordField::Demanded(to_doc_help(
|
||||
ctx,
|
||||
alloc,
|
||||
Parens::Unnecessary,
|
||||
v,
|
||||
)),
|
||||
},
|
||||
)
|
||||
})
|
||||
@ -1782,7 +2056,7 @@ pub fn to_doc<'b>(
|
||||
(
|
||||
name,
|
||||
args.into_iter()
|
||||
.map(|arg| to_doc(alloc, Parens::InTypeParam, arg))
|
||||
.map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
})
|
||||
@ -1805,7 +2079,7 @@ pub fn to_doc<'b>(
|
||||
(
|
||||
name,
|
||||
args.into_iter()
|
||||
.map(|arg| to_doc(alloc, Parens::InTypeParam, arg))
|
||||
.map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
})
|
||||
@ -1814,7 +2088,7 @@ pub fn to_doc<'b>(
|
||||
|
||||
report_text::recursive_tag_union(
|
||||
alloc,
|
||||
to_doc(alloc, Parens::Unnecessary, *rec_var),
|
||||
to_doc_help(ctx, alloc, Parens::Unnecessary, *rec_var),
|
||||
tags.into_iter()
|
||||
.map(|(k, v)| (alloc.tag_name(k), v))
|
||||
.collect(),
|
||||
@ -1823,10 +2097,10 @@ pub fn to_doc<'b>(
|
||||
}
|
||||
|
||||
Range(typ, range_types) => {
|
||||
let typ = to_doc(alloc, parens, *typ);
|
||||
let typ = to_doc_help(ctx, alloc, parens, *typ);
|
||||
let range_types = range_types
|
||||
.into_iter()
|
||||
.map(|arg| to_doc(alloc, Parens::Unnecessary, arg))
|
||||
.map(|arg| to_doc_help(ctx, alloc, Parens::Unnecessary, arg))
|
||||
.collect();
|
||||
report_text::range(alloc, typ, range_types)
|
||||
}
|
||||
@ -1838,15 +2112,42 @@ fn same<'b>(
|
||||
parens: Parens,
|
||||
tipe: ErrorType,
|
||||
) -> Diff<RocDocBuilder<'b>> {
|
||||
let doc = to_doc(alloc, parens, tipe);
|
||||
let (doc, able) = to_doc(alloc, parens, tipe);
|
||||
|
||||
Diff {
|
||||
left: doc.clone(),
|
||||
right: doc,
|
||||
status: Status::Similar,
|
||||
left_able: able.clone(),
|
||||
right_able: able,
|
||||
}
|
||||
}
|
||||
|
||||
fn type_with_able_vars<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
typ: RocDocBuilder<'b>,
|
||||
able: AbleVariables,
|
||||
) -> RocDocBuilder<'b> {
|
||||
if able.is_empty() {
|
||||
// fast path: taken the vast majority of the time
|
||||
return typ;
|
||||
}
|
||||
|
||||
let mut doc = Vec::with_capacity(1 + 6 * able.len());
|
||||
doc.push(typ);
|
||||
|
||||
for (i, (var, ability)) in able.into_iter().enumerate() {
|
||||
doc.push(alloc.string(if i == 0 { " | " } else { ", " }.to_string()));
|
||||
doc.push(alloc.type_variable(var));
|
||||
doc.push(alloc.space());
|
||||
doc.push(alloc.keyword("has"));
|
||||
doc.push(alloc.space());
|
||||
doc.push(alloc.symbol_foreign_qualified(ability));
|
||||
}
|
||||
|
||||
alloc.concat(doc)
|
||||
}
|
||||
|
||||
fn to_diff<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
parens: Parens,
|
||||
@ -1875,15 +2176,21 @@ fn to_diff<'b>(
|
||||
|
||||
let left = report_text::function(alloc, parens, arg_diff.left, ret_diff.left);
|
||||
let right = report_text::function(alloc, parens, arg_diff.right, ret_diff.right);
|
||||
let mut left_able = arg_diff.left_able;
|
||||
left_able.extend(ret_diff.left_able);
|
||||
let mut right_able = arg_diff.right_able;
|
||||
right_able.extend(ret_diff.right_able);
|
||||
|
||||
Diff {
|
||||
left,
|
||||
right,
|
||||
status,
|
||||
left_able,
|
||||
right_able,
|
||||
}
|
||||
} else {
|
||||
let left = to_doc(alloc, Parens::InFn, type1);
|
||||
let right = to_doc(alloc, Parens::InFn, type2);
|
||||
let (left, left_able) = to_doc(alloc, Parens::InFn, type1);
|
||||
let (right, right_able) = to_doc(alloc, Parens::InFn, type2);
|
||||
|
||||
Diff {
|
||||
left,
|
||||
@ -1892,6 +2199,8 @@ fn to_diff<'b>(
|
||||
args1.len(),
|
||||
args2.len(),
|
||||
)]),
|
||||
left_able,
|
||||
right_able,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1914,6 +2223,8 @@ fn to_diff<'b>(
|
||||
left,
|
||||
right,
|
||||
status: args_diff.status,
|
||||
left_able: args_diff.left_able,
|
||||
right_able: args_diff.right_able,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1936,17 +2247,21 @@ fn to_diff<'b>(
|
||||
left,
|
||||
right,
|
||||
status: args_diff.status,
|
||||
left_able: args_diff.left_able,
|
||||
right_able: args_diff.right_able,
|
||||
}
|
||||
}
|
||||
|
||||
(Alias(_, _, _, AliasKind::Opaque), _) | (_, Alias(_, _, _, AliasKind::Opaque)) => {
|
||||
let left = to_doc(alloc, Parens::InFn, type1);
|
||||
let right = to_doc(alloc, Parens::InFn, type2);
|
||||
let (left, left_able) = to_doc(alloc, Parens::InFn, type1);
|
||||
let (right, right_able) = to_doc(alloc, Parens::InFn, type2);
|
||||
|
||||
Diff {
|
||||
left,
|
||||
right,
|
||||
status: Status::Different(vec![Problem::OpaqueComparedToNonOpaque]),
|
||||
left_able,
|
||||
right_able,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1973,20 +2288,22 @@ fn to_diff<'b>(
|
||||
|
||||
(RecursiveTagUnion(_rec1, _tags1, _ext1), RecursiveTagUnion(_rec2, _tags2, _ext2)) => {
|
||||
// TODO do a better job here
|
||||
let left = to_doc(alloc, Parens::Unnecessary, type1);
|
||||
let right = to_doc(alloc, Parens::Unnecessary, type2);
|
||||
let (left, left_able) = to_doc(alloc, Parens::Unnecessary, type1);
|
||||
let (right, right_able) = to_doc(alloc, Parens::Unnecessary, type2);
|
||||
|
||||
Diff {
|
||||
left,
|
||||
right,
|
||||
status: Status::Similar,
|
||||
left_able,
|
||||
right_able,
|
||||
}
|
||||
}
|
||||
|
||||
pair => {
|
||||
// We hit none of the specific cases where we give more detailed information
|
||||
let left = to_doc(alloc, parens, type1);
|
||||
let right = to_doc(alloc, parens, type2);
|
||||
let (left, left_able) = to_doc(alloc, parens, type1);
|
||||
let (right, right_able) = to_doc(alloc, parens, type2);
|
||||
|
||||
let is_int = |t: &ErrorType| match t {
|
||||
ErrorType::Type(Symbol::NUM_INT, _) => true,
|
||||
@ -2042,6 +2359,8 @@ fn to_diff<'b>(
|
||||
left,
|
||||
right,
|
||||
status: Status::Different(problems),
|
||||
left_able,
|
||||
right_able,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2061,6 +2380,8 @@ where
|
||||
// TODO use ExactSizeIterator to pre-allocate here
|
||||
let mut left = Vec::new();
|
||||
let mut right = Vec::new();
|
||||
let mut left_able = Vec::new();
|
||||
let mut right_able = Vec::new();
|
||||
|
||||
for (arg1, arg2) in args1.into_iter().zip(args2.into_iter()) {
|
||||
let diff = to_diff(alloc, parens, arg1, arg2);
|
||||
@ -2068,12 +2389,16 @@ where
|
||||
left.push(diff.left);
|
||||
right.push(diff.right);
|
||||
status.merge(diff.status);
|
||||
left_able.extend(diff.left_able);
|
||||
right_able.extend(diff.right_able);
|
||||
}
|
||||
|
||||
Diff {
|
||||
left,
|
||||
right,
|
||||
status,
|
||||
left_able,
|
||||
right_able,
|
||||
}
|
||||
}
|
||||
|
||||
@ -2140,6 +2465,8 @@ fn diff_record<'b>(
|
||||
_ => diff.status,
|
||||
}
|
||||
},
|
||||
left_able: diff.left_able,
|
||||
right_able: diff.right_able,
|
||||
}
|
||||
};
|
||||
|
||||
@ -2147,7 +2474,7 @@ fn diff_record<'b>(
|
||||
(
|
||||
field.clone(),
|
||||
alloc.string(field.as_str().to_string()),
|
||||
tipe.map(|t| to_doc(alloc, Parens::Unnecessary, t.clone())),
|
||||
tipe.map(|t| to_doc(alloc, Parens::Unnecessary, t.clone()).0),
|
||||
)
|
||||
};
|
||||
let shared_keys = fields1
|
||||
@ -2205,12 +2532,16 @@ fn diff_record<'b>(
|
||||
left: vec![],
|
||||
right: vec![],
|
||||
status: Status::Similar,
|
||||
left_able: vec![],
|
||||
right_able: vec![],
|
||||
};
|
||||
|
||||
for diff in both {
|
||||
fields_diff.left.push(diff.left);
|
||||
fields_diff.right.push(diff.right);
|
||||
fields_diff.status.merge(diff.status);
|
||||
fields_diff.left_able.extend(diff.left_able);
|
||||
fields_diff.right_able.extend(diff.right_able);
|
||||
}
|
||||
|
||||
if !all_fields_shared {
|
||||
@ -2248,6 +2579,8 @@ fn diff_record<'b>(
|
||||
left: doc1,
|
||||
right: doc2,
|
||||
status: fields_diff.status,
|
||||
left_able: fields_diff.left_able,
|
||||
right_able: fields_diff.right_able,
|
||||
}
|
||||
}
|
||||
|
||||
@ -2265,16 +2598,26 @@ fn diff_tag_union<'b>(
|
||||
left: (field.clone(), alloc.tag_name(field.clone()), diff.left),
|
||||
right: (field.clone(), alloc.tag_name(field), diff.right),
|
||||
status: diff.status,
|
||||
left_able: diff.left_able,
|
||||
right_able: diff.right_able,
|
||||
}
|
||||
};
|
||||
let to_unknown_docs = |(field, args): (&TagName, &Vec<ErrorType>)| {
|
||||
(
|
||||
field.clone(),
|
||||
alloc.tag_name(field.clone()),
|
||||
let to_unknown_docs = |(field, args): (&TagName, &Vec<ErrorType>)| -> (
|
||||
TagName,
|
||||
RocDocBuilder<'b>,
|
||||
Vec<RocDocBuilder<'b>>,
|
||||
AbleVariables,
|
||||
) {
|
||||
let (args, able): (_, Vec<AbleVariables>) =
|
||||
// TODO add spaces between args
|
||||
args.iter()
|
||||
.map(|arg| to_doc(alloc, Parens::InTypeParam, arg.clone()))
|
||||
.collect(),
|
||||
.unzip();
|
||||
(
|
||||
field.clone(),
|
||||
alloc.tag_name(field.clone()),
|
||||
args,
|
||||
able.into_iter().flatten().collect(),
|
||||
)
|
||||
};
|
||||
let shared_keys = fields1
|
||||
@ -2292,7 +2635,7 @@ fn diff_tag_union<'b>(
|
||||
|
||||
let status = match (ext_has_fixed_fields(&ext1), ext_has_fixed_fields(&ext2)) {
|
||||
(true, true) => match left.peek() {
|
||||
Some((f, _, _)) => Status::Different(vec![Problem::TagTypo(
|
||||
Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo(
|
||||
f.clone(),
|
||||
fields2.keys().cloned().collect(),
|
||||
)]),
|
||||
@ -2309,14 +2652,14 @@ fn diff_tag_union<'b>(
|
||||
}
|
||||
},
|
||||
(false, true) => match left.peek() {
|
||||
Some((f, _, _)) => Status::Different(vec![Problem::TagTypo(
|
||||
Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo(
|
||||
f.clone(),
|
||||
fields2.keys().cloned().collect(),
|
||||
)]),
|
||||
None => Status::Similar,
|
||||
},
|
||||
(true, false) => match right.peek() {
|
||||
Some((f, _, _)) => Status::Different(vec![Problem::TagTypo(
|
||||
Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo(
|
||||
f.clone(),
|
||||
fields1.keys().cloned().collect(),
|
||||
)]),
|
||||
@ -2331,17 +2674,27 @@ fn diff_tag_union<'b>(
|
||||
left: vec![],
|
||||
right: vec![],
|
||||
status: Status::Similar,
|
||||
left_able: vec![],
|
||||
right_able: vec![],
|
||||
};
|
||||
|
||||
for diff in both {
|
||||
fields_diff.left.push(diff.left);
|
||||
fields_diff.right.push(diff.right);
|
||||
fields_diff.status.merge(diff.status);
|
||||
fields_diff.left_able.extend(diff.left_able);
|
||||
fields_diff.right_able.extend(diff.right_able);
|
||||
}
|
||||
|
||||
if !all_fields_shared {
|
||||
fields_diff.left.extend(left);
|
||||
fields_diff.right.extend(right);
|
||||
for (tag, tag_doc, args, able) in left {
|
||||
fields_diff.left.push((tag, tag_doc, args));
|
||||
fields_diff.left_able.extend(able);
|
||||
}
|
||||
for (tag, tag_doc, args, able) in right {
|
||||
fields_diff.right.push((tag, tag_doc, args));
|
||||
fields_diff.right_able.extend(able);
|
||||
}
|
||||
fields_diff.status.merge(Status::Different(vec![]));
|
||||
}
|
||||
|
||||
@ -2368,6 +2721,8 @@ fn diff_tag_union<'b>(
|
||||
left: doc1,
|
||||
right: doc2,
|
||||
status: fields_diff.status,
|
||||
left_able: fields_diff.left_able,
|
||||
right_able: fields_diff.right_able,
|
||||
}
|
||||
}
|
||||
|
||||
@ -2385,12 +2740,16 @@ fn ext_to_diff<'b>(
|
||||
left: ext_doc_1,
|
||||
right: ext_doc_2,
|
||||
status,
|
||||
left_able: vec![],
|
||||
right_able: vec![],
|
||||
},
|
||||
Status::Different(_) => Diff {
|
||||
// NOTE elm colors these differently at this point
|
||||
left: ext_doc_1,
|
||||
right: ext_doc_2,
|
||||
status,
|
||||
left_able: vec![],
|
||||
right_able: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -2530,7 +2889,7 @@ mod report_text {
|
||||
let entry_to_doc = |(name, tipe): (Lowercase, RecordField<ErrorType>)| {
|
||||
(
|
||||
alloc.string(name.as_str().to_string()),
|
||||
to_doc(alloc, Parens::Unnecessary, tipe.into_inner()),
|
||||
to_doc(alloc, Parens::Unnecessary, tipe.into_inner()).0,
|
||||
)
|
||||
};
|
||||
|
||||
@ -2875,7 +3234,14 @@ fn type_problem_to_pretty<'b>(
|
||||
|
||||
match tipe {
|
||||
Infinite | Error | FlexVar(_) => alloc.nil(),
|
||||
RigidVar(y) => bad_double_rigid(x, y),
|
||||
FlexAbleVar(_, ability) => bad_rigid_var(
|
||||
x,
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("an instance of the ability "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
]),
|
||||
),
|
||||
RigidVar(y) | RigidAbleVar(y, _) => bad_double_rigid(x, y),
|
||||
Function(_, _, _) => bad_rigid_var(x, alloc.reflow("a function value")),
|
||||
Record(_, _) => bad_rigid_var(x, alloc.reflow("a record value")),
|
||||
TagUnion(_, _) | RecursiveTagUnion(_, _, _) => {
|
||||
|
@ -72,6 +72,12 @@ pub enum Severity {
|
||||
Warning,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum RenderTarget {
|
||||
ColorTerminal,
|
||||
Generic,
|
||||
}
|
||||
|
||||
/// A textual report.
|
||||
pub struct Report<'b> {
|
||||
pub title: String,
|
||||
@ -81,6 +87,19 @@ pub struct Report<'b> {
|
||||
}
|
||||
|
||||
impl<'b> Report<'b> {
|
||||
pub fn render(
|
||||
self,
|
||||
target: RenderTarget,
|
||||
buf: &'b mut String,
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
palette: &'b Palette,
|
||||
) {
|
||||
match target {
|
||||
RenderTarget::Generic => self.render_ci(buf, alloc),
|
||||
RenderTarget::ColorTerminal => self.render_color_terminal(buf, alloc, palette),
|
||||
}
|
||||
}
|
||||
|
||||
/// Render to CI console output, where no colors are available.
|
||||
pub fn render_ci(self, buf: &'b mut String, alloc: &'b RocDocAllocator<'b>) {
|
||||
let err_msg = "<buffer is not a utf-8 encoded string>";
|
||||
|
@ -1,6 +1,7 @@
|
||||
extern crate bumpalo;
|
||||
|
||||
use self::bumpalo::Bump;
|
||||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::constraint::{Constraint, Constraints};
|
||||
use roc_can::env::Env;
|
||||
use roc_can::expected::Expected;
|
||||
@ -31,10 +32,19 @@ pub fn infer_expr(
|
||||
constraints: &Constraints,
|
||||
constraint: &Constraint,
|
||||
aliases: &mut Aliases,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
expr_var: Variable,
|
||||
) -> (Content, Subs) {
|
||||
let env = solve::Env::default();
|
||||
let (solved, _) = solve::run(constraints, &env, problems, subs, aliases, constraint);
|
||||
let (solved, _) = solve::run(
|
||||
constraints,
|
||||
&env,
|
||||
problems,
|
||||
subs,
|
||||
aliases,
|
||||
constraint,
|
||||
abilities_store,
|
||||
);
|
||||
|
||||
let content = *solved.inner().get_content_without_compacting(expr_var);
|
||||
|
||||
|
@ -8,17 +8,20 @@ mod helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_reporting {
|
||||
use crate::helpers::test_home;
|
||||
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
|
||||
use crate::helpers::{can_expr, infer_expr, test_home, CanExprOut, ParseErrOut};
|
||||
use bumpalo::Bump;
|
||||
use indoc::indoc;
|
||||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::def::Declaration;
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_load::{self, LoadedModule, LoadingProblem};
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_mono::ir::{Procs, Stmt, UpdateModeIds};
|
||||
use roc_mono::layout::LayoutCache;
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, parse_problem, type_problem, Report, Severity, ANSI_STYLE_CODES,
|
||||
DEFAULT_PALETTE,
|
||||
can_problem, mono_problem, parse_problem, type_problem, RenderTarget, Report, Severity,
|
||||
ANSI_STYLE_CODES, DEFAULT_PALETTE,
|
||||
};
|
||||
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
||||
use roc_solve::solve;
|
||||
@ -43,6 +46,214 @@ mod test_reporting {
|
||||
}
|
||||
}
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer =
|
||||
String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n");
|
||||
|
||||
for line in src.lines() {
|
||||
// indent the body!
|
||||
buffer.push_str(" ");
|
||||
buffer.push_str(line);
|
||||
buffer.push('\n');
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
||||
|
||||
fn run_load_and_infer<'a>(
|
||||
arena: &'a Bump,
|
||||
src: &'a str,
|
||||
) -> (String, Result<LoadedModule, LoadingProblem<'a>>) {
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use tempfile::tempdir;
|
||||
|
||||
let module_src = if src.starts_with("app") {
|
||||
// this is already a module
|
||||
src.to_string()
|
||||
} else {
|
||||
// this is an expression, promote it to a module
|
||||
promote_expr_to_module(src)
|
||||
};
|
||||
|
||||
let exposed_types = Default::default();
|
||||
let loaded = {
|
||||
let dir = tempdir().unwrap();
|
||||
let filename = PathBuf::from("Test.roc");
|
||||
let file_path = dir.path().join(filename);
|
||||
let full_file_path = file_path.clone();
|
||||
let mut file = File::create(file_path).unwrap();
|
||||
writeln!(file, "{}", module_src).unwrap();
|
||||
let result = roc_load::load_and_typecheck(
|
||||
arena,
|
||||
full_file_path,
|
||||
dir.path(),
|
||||
exposed_types,
|
||||
roc_target::TargetInfo::default_x86_64(),
|
||||
RenderTarget::Generic,
|
||||
);
|
||||
drop(file);
|
||||
|
||||
dir.close().unwrap();
|
||||
|
||||
result
|
||||
};
|
||||
|
||||
(module_src, loaded)
|
||||
}
|
||||
|
||||
fn infer_expr_help_new<'a>(
|
||||
arena: &'a Bump,
|
||||
expr_src: &'a str,
|
||||
) -> Result<
|
||||
(
|
||||
String,
|
||||
Vec<solve::TypeError>,
|
||||
Vec<roc_problem::can::Problem>,
|
||||
Vec<roc_mono::ir::MonoProblem>,
|
||||
ModuleId,
|
||||
Interns,
|
||||
),
|
||||
LoadingProblem<'a>,
|
||||
> {
|
||||
let (module_src, result) = run_load_and_infer(arena, expr_src);
|
||||
let LoadedModule {
|
||||
module_id: home,
|
||||
mut can_problems,
|
||||
mut type_problems,
|
||||
interns,
|
||||
mut solved,
|
||||
exposed_to_host,
|
||||
mut declarations_by_id,
|
||||
..
|
||||
} = result?;
|
||||
|
||||
let can_problems = can_problems.remove(&home).unwrap_or_default();
|
||||
let type_problems = type_problems.remove(&home).unwrap_or_default();
|
||||
|
||||
let subs = solved.inner_mut();
|
||||
|
||||
for var in exposed_to_host.values() {
|
||||
name_all_type_vars(*var, subs);
|
||||
}
|
||||
|
||||
let mut mono_problems = Vec::new();
|
||||
|
||||
// MONO
|
||||
|
||||
if type_problems.is_empty() && can_problems.is_empty() {
|
||||
let arena = Bump::new();
|
||||
|
||||
assert!(exposed_to_host.len() == 1);
|
||||
let (sym, _var) = exposed_to_host.into_iter().next().unwrap();
|
||||
|
||||
let home_decls = declarations_by_id.remove(&home).unwrap();
|
||||
let (loc_expr, var) = home_decls
|
||||
.into_iter()
|
||||
.find_map(|decl| match decl {
|
||||
Declaration::Declare(def) => match def.loc_pattern.value {
|
||||
Pattern::Identifier(s) if s == sym => Some((def.loc_expr, def.expr_var)),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.expect("No expression to monomorphize found!");
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let mut procs = Procs::new_in(&arena);
|
||||
let mut ident_ids = interns.all_ident_ids.get(&home).unwrap().clone();
|
||||
let mut update_mode_ids = UpdateModeIds::new();
|
||||
|
||||
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
|
||||
let target_info = roc_target::TargetInfo::default_x86_64();
|
||||
let mut layout_cache = LayoutCache::new(target_info);
|
||||
let mut mono_env = roc_mono::ir::Env {
|
||||
arena: &arena,
|
||||
subs,
|
||||
problems: &mut mono_problems,
|
||||
home,
|
||||
ident_ids: &mut ident_ids,
|
||||
update_mode_ids: &mut update_mode_ids,
|
||||
target_info,
|
||||
// call_specialization_counter=0 is reserved
|
||||
call_specialization_counter: 1,
|
||||
};
|
||||
let _mono_expr = Stmt::new(
|
||||
&mut mono_env,
|
||||
loc_expr.value,
|
||||
var,
|
||||
&mut procs,
|
||||
&mut layout_cache,
|
||||
);
|
||||
}
|
||||
|
||||
Ok((
|
||||
module_src,
|
||||
type_problems,
|
||||
can_problems,
|
||||
mono_problems,
|
||||
home,
|
||||
interns,
|
||||
))
|
||||
}
|
||||
|
||||
fn list_reports_new<F>(arena: &Bump, src: &str, finalize_render: F) -> String
|
||||
where
|
||||
F: FnOnce(RocDocBuilder<'_>, &mut String),
|
||||
{
|
||||
use ven_pretty::DocAllocator;
|
||||
|
||||
let filename = filename_from_string(r"\code\proj\Main.roc");
|
||||
|
||||
let mut buf = String::new();
|
||||
|
||||
match infer_expr_help_new(arena, src) {
|
||||
Err(LoadingProblem::FormattedReport(fail)) => fail,
|
||||
Ok((module_src, type_problems, can_problems, mono_problems, home, interns)) => {
|
||||
let lines = LineInfo::new(&module_src);
|
||||
let src_lines: Vec<&str> = module_src.split('\n').collect();
|
||||
let mut reports = Vec::new();
|
||||
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
for problem in can_problems {
|
||||
let report = can_problem(&alloc, &lines, filename.clone(), problem.clone());
|
||||
reports.push(report);
|
||||
}
|
||||
|
||||
for problem in type_problems {
|
||||
if let Some(report) =
|
||||
type_problem(&alloc, &lines, filename.clone(), problem.clone())
|
||||
{
|
||||
reports.push(report);
|
||||
}
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_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()
|
||||
});
|
||||
|
||||
finalize_render(doc, &mut buf);
|
||||
buf
|
||||
}
|
||||
Err(other) => {
|
||||
assert!(false, "failed to load: {:?}", other);
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_expr_help<'a>(
|
||||
arena: &'a Bump,
|
||||
expr_src: &'a str,
|
||||
@ -85,12 +296,14 @@ mod test_reporting {
|
||||
}
|
||||
|
||||
let mut unify_problems = Vec::new();
|
||||
let mut abilities_store = AbilitiesStore::default();
|
||||
let (_content, mut subs) = infer_expr(
|
||||
subs,
|
||||
&mut unify_problems,
|
||||
&constraints,
|
||||
&constraint,
|
||||
&mut solve_aliases,
|
||||
&mut abilities_store,
|
||||
var,
|
||||
);
|
||||
|
||||
@ -298,6 +511,27 @@ mod test_reporting {
|
||||
assert_eq!(readable, expected_rendering);
|
||||
}
|
||||
|
||||
fn new_report_problem_as(src: &str, expected_rendering: &str) {
|
||||
let arena = Bump::new();
|
||||
|
||||
let finalize_render = |doc: RocDocBuilder<'_>, buf: &mut String| {
|
||||
doc.1
|
||||
.render_raw(70, &mut roc_reporting::report::CiWrite::new(buf))
|
||||
.expect("list_reports")
|
||||
};
|
||||
|
||||
let buf = list_reports_new(&arena, src, finalize_render);
|
||||
|
||||
// convenient to copy-paste the generated message
|
||||
if buf != expected_rendering {
|
||||
for line in buf.split('\n') {
|
||||
println!(" {}", line);
|
||||
}
|
||||
}
|
||||
|
||||
assert_multiline_str_eq!(expected_rendering, buf.as_str());
|
||||
}
|
||||
|
||||
fn human_readable(str: &str) -> String {
|
||||
str.replace(ANSI_STYLE_CODES.red, "<red>")
|
||||
.replace(ANSI_STYLE_CODES.white, "<white>")
|
||||
@ -8688,7 +8922,7 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
#[test]
|
||||
fn ability_demands_not_indented_with_first() {
|
||||
report_problem_as(
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Eq has
|
||||
@ -8705,19 +8939,18 @@ I need all branches in an `if` to have the same type!
|
||||
I was partway through parsing an ability definition, but I got stuck
|
||||
here:
|
||||
|
||||
2│ eq : a, a -> U64 | a has Eq
|
||||
3│ neq : a, a -> U64 | a has Eq
|
||||
^
|
||||
5│ eq : a, a -> U64 | a has Eq
|
||||
6│ neq : a, a -> U64 | a has Eq
|
||||
^
|
||||
|
||||
I suspect this line is indented too much (by 4 spaces)
|
||||
"#
|
||||
I suspect this line is indented too much (by 4 spaces)"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_demand_value_has_args() {
|
||||
report_problem_as(
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Eq has
|
||||
@ -8733,12 +8966,11 @@ I need all branches in an `if` to have the same type!
|
||||
I was partway through parsing an ability definition, but I got stuck
|
||||
here:
|
||||
|
||||
2│ eq b c : a, a -> U64 | a has Eq
|
||||
^
|
||||
5│ eq b c : a, a -> U64 | a has Eq
|
||||
^
|
||||
|
||||
I was expecting to see a : annotating the signature of this value
|
||||
next.
|
||||
"#
|
||||
next."#
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -8904,8 +9136,8 @@ I need all branches in an `if` to have the same type!
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_type_parameter_in_ability() {
|
||||
report_problem_as(
|
||||
fn ability_bad_type_parameter() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Hash a b c has
|
||||
@ -8920,8 +9152,8 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
The definition of the `Hash` ability includes type variables:
|
||||
|
||||
1│ Hash a b c has
|
||||
^^^^^
|
||||
4│ Hash a b c has
|
||||
^^^^^
|
||||
|
||||
Abilities cannot depend on type variables, but their member values
|
||||
can!
|
||||
@ -8930,8 +9162,8 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
`Hash` is not used anywhere in your code.
|
||||
|
||||
1│ Hash a b c has
|
||||
^^^^
|
||||
4│ Hash a b c has
|
||||
^^^^
|
||||
|
||||
If you didn't intend on using `Hash` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
@ -8942,12 +9174,12 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
#[test]
|
||||
fn alias_in_has_clause() {
|
||||
report_problem_as(
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool
|
||||
app "test" provides [ hash ] to "./platform"
|
||||
|
||||
1
|
||||
Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
@ -8956,14 +9188,14 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
The type referenced in this "has" clause is not an ability:
|
||||
|
||||
1│ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool
|
||||
3│ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool
|
||||
^^^^^^^^^
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`hash` is not used anywhere in your code.
|
||||
|
||||
1│ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool
|
||||
3│ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool
|
||||
^^^^
|
||||
|
||||
If you didn't intend on using `hash` then remove it so future readers of
|
||||
@ -8975,12 +9207,12 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
#[test]
|
||||
fn shadowed_type_variable_in_has_clause() {
|
||||
report_problem_as(
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||||
app "test" provides [ ab1 ] to "./platform"
|
||||
|
||||
1
|
||||
Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
@ -8989,26 +9221,16 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
The `a` name is first defined here:
|
||||
|
||||
1│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||||
3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||||
^^^^^^^^^
|
||||
|
||||
But then it's defined a second time here:
|
||||
|
||||
1│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||||
3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||||
^^^^^^^^^
|
||||
|
||||
Since these variables have the same name, it's easy to use the wrong
|
||||
one on accident. Give one of them a new name.
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`ab1` is not used anywhere in your code.
|
||||
|
||||
1│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||||
^^^
|
||||
|
||||
If you didn't intend on using `ab1` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
@ -9016,7 +9238,7 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
#[test]
|
||||
fn alias_using_ability() {
|
||||
report_problem_as(
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Ability has ab : a -> {} | a has Ability
|
||||
@ -9033,8 +9255,8 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
The definition of the `Alias` aliases references the ability `Ability`:
|
||||
|
||||
3│ Alias : Ability
|
||||
^^^^^
|
||||
6│ Alias : Ability
|
||||
^^^^^
|
||||
|
||||
Abilities are not types, but you can add an ability constraint to a
|
||||
type variable `a` by writing
|
||||
@ -9047,8 +9269,8 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
`ab` is not used anywhere in your code.
|
||||
|
||||
1│ Ability has ab : a -> {} | a has Ability
|
||||
^^
|
||||
4│ Ability has ab : a -> {} | a has Ability
|
||||
^^
|
||||
|
||||
If you didn't intend on using `ab` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
@ -9059,7 +9281,7 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
#[test]
|
||||
fn ability_shadows_ability() {
|
||||
report_problem_as(
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Ability has ab : a -> Num.U64 | a has Ability
|
||||
@ -9102,12 +9324,12 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
#[test]
|
||||
fn ability_member_does_not_bind_ability() {
|
||||
report_problem_as(
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Ability has ab : {} -> {}
|
||||
app "test" provides [ ] to "./platform"
|
||||
|
||||
1
|
||||
Ability has ab : {} -> {}
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
@ -9117,7 +9339,7 @@ I need all branches in an `if` to have the same type!
|
||||
The definition of the ability member `ab` does not include a `has` clause
|
||||
binding a type variable to the ability `Ability`:
|
||||
|
||||
1│ Ability has ab : {} -> {}
|
||||
3│ Ability has ab : {} -> {}
|
||||
^^
|
||||
|
||||
Ability members must include a `has` clause binding a type variable to
|
||||
@ -9131,7 +9353,7 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
`Ability` is not used anywhere in your code.
|
||||
|
||||
1│ Ability has ab : {} -> {}
|
||||
3│ Ability has ab : {} -> {}
|
||||
^^^^^^^
|
||||
|
||||
If you didn't intend on using `Ability` then remove it so future readers
|
||||
@ -9141,7 +9363,7 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
`ab` is not used anywhere in your code.
|
||||
|
||||
1│ Ability has ab : {} -> {}
|
||||
3│ Ability has ab : {} -> {}
|
||||
^^
|
||||
|
||||
If you didn't intend on using `ab` then remove it so future readers of
|
||||
@ -9153,13 +9375,13 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
#[test]
|
||||
fn ability_member_binds_extra_ability() {
|
||||
report_problem_as(
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ eq ] to "./platform"
|
||||
|
||||
Eq has eq : a, a -> Bool.Bool | a has Eq
|
||||
Hash has hash : a, b -> Num.U64 | a has Eq, b has Hash
|
||||
|
||||
1
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
@ -9169,9 +9391,8 @@ I need all branches in an `if` to have the same type!
|
||||
The definition of the ability member `hash` includes a has clause
|
||||
binding an ability it is not a part of:
|
||||
|
||||
2│ Hash has hash : a, b -> Num.U64 | a has Eq, b has Hash
|
||||
4│ Hash has hash : a, b -> Num.U64 | a has Eq, b has Hash
|
||||
^^^^^^^^
|
||||
|
||||
Currently, ability members can only bind variables to the ability they
|
||||
are a part of.
|
||||
|
||||
@ -9179,19 +9400,9 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`eq` is not used anywhere in your code.
|
||||
|
||||
1│ Eq has eq : a, a -> Bool.Bool | a has Eq
|
||||
^^
|
||||
|
||||
If you didn't intend on using `eq` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`hash` is not used anywhere in your code.
|
||||
|
||||
2│ Hash has hash : a, b -> Num.U64 | a has Eq, b has Hash
|
||||
4│ Hash has hash : a, b -> Num.U64 | a has Eq, b has Hash
|
||||
^^^^
|
||||
|
||||
If you didn't intend on using `hash` then remove it so future readers of
|
||||
@ -9202,15 +9413,55 @@ I need all branches in an `if` to have the same type!
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_clause_outside_of_ability() {
|
||||
report_problem_as(
|
||||
fn ability_member_binds_parent_twice() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ ] to "./platform"
|
||||
|
||||
Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── ABILITY MEMBER BINDS MULTIPLE VARIABLES ─────────────────────────────────────
|
||||
|
||||
The definition of the ability member `eq` includes multiple variables
|
||||
bound to the `Eq`` ability:`
|
||||
|
||||
3│ Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Ability members can only bind one type variable to their parent
|
||||
ability. Otherwise, I wouldn't know what type implements an ability by
|
||||
looking at specializations!
|
||||
|
||||
Hint: Did you mean to only bind `a` to `Eq`?
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`eq` is not used anywhere in your code.
|
||||
|
||||
3│ Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq
|
||||
^^
|
||||
|
||||
If you didn't intend on using `eq` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_clause_outside_of_ability() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ hash, f ] to "./platform"
|
||||
|
||||
Hash has hash : a -> Num.U64 | a has Hash
|
||||
|
||||
f : a -> Num.U64 | a has Hash
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
@ -9219,21 +9470,327 @@ I need all branches in an `if` to have the same type!
|
||||
|
||||
A `has` clause is not allowed here:
|
||||
|
||||
3│ f : a -> Num.U64 | a has Hash
|
||||
^^^^^^^^^^
|
||||
5│ f : a -> Num.U64 | a has Hash
|
||||
^^^^^^^^^^
|
||||
|
||||
`has` clauses can only be specified on the top-level type annotation of
|
||||
an ability member.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
#[test]
|
||||
fn ability_specialization_with_non_implementing_type() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ hash ] to "./platform"
|
||||
|
||||
`hash` is not used anywhere in your code.
|
||||
Hash has hash : a -> Num.U64 | a has Hash
|
||||
|
||||
1│ Hash has hash : a -> Num.U64 | a has Hash
|
||||
^^^^
|
||||
hash = \{} -> 0u64
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
If you didn't intend on using `hash` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
Something is off with this specialization of `hash`:
|
||||
|
||||
5│ hash = \{} -> 0u64
|
||||
^^^^
|
||||
|
||||
This value is a declared specialization of type:
|
||||
|
||||
{}a -> U64
|
||||
|
||||
But the type annotation on `hash` says it must match:
|
||||
|
||||
a -> U64 | a has Hash
|
||||
|
||||
Note: Some types in this specialization don't implement the abilities
|
||||
they are expected to. I found the following missing implementations:
|
||||
|
||||
{}a does not implement Hash
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_does_not_match_type() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ hash ] to "./platform"
|
||||
|
||||
Hash has hash : a -> U64 | a has Hash
|
||||
|
||||
Id := U32
|
||||
|
||||
hash = \$Id n -> n
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
Something is off with this specialization of `hash`:
|
||||
|
||||
7│ hash = \$Id n -> n
|
||||
^^^^
|
||||
|
||||
This value is a declared specialization of type:
|
||||
|
||||
Id -> U32
|
||||
|
||||
But the type annotation on `hash` says it must match:
|
||||
|
||||
Id -> U64
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_is_incomplete() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ eq, le ] to "./platform"
|
||||
|
||||
Eq has
|
||||
eq : a, a -> Bool | a has Eq
|
||||
le : a, a -> Bool | a has Eq
|
||||
|
||||
Id := U64
|
||||
|
||||
eq = \$Id m, $Id n -> m == n
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────────────────────────────
|
||||
|
||||
The type `Id` does not fully implement the ability `Eq`. The following
|
||||
specializations are missing:
|
||||
|
||||
A specialization for `le`, which is defined here:
|
||||
|
||||
5│ le : a, a -> Bool | a has Eq
|
||||
^^
|
||||
|
||||
Note: `Id` specializes the following members of `Eq`:
|
||||
|
||||
`eq`, specialized here:
|
||||
|
||||
9│ eq = \$Id m, $Id n -> m == n
|
||||
^^
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_overly_generalized() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ hash ] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
|
||||
hash = \_ -> 0u64
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
This specialization of `hash` is overly general:
|
||||
|
||||
6│ hash = \_ -> 0u64
|
||||
^^^^
|
||||
|
||||
This value is a declared specialization of type:
|
||||
|
||||
a -> U64
|
||||
|
||||
But the type annotation on `hash` says it must match:
|
||||
|
||||
a -> U64 | a has Hash
|
||||
|
||||
Note: The specialized type is too general, and does not provide a
|
||||
concrete type where a type variable is bound to an ability.
|
||||
|
||||
Specializations can only be made for concrete types. If you have a
|
||||
generic implementation for this value, perhaps you don't need an
|
||||
ability?
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_conflicting_specialization_types() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ eq ] to "./platform"
|
||||
|
||||
Eq has
|
||||
eq : a, a -> Bool | a has Eq
|
||||
|
||||
You := {}
|
||||
AndI := {}
|
||||
|
||||
eq = \$You {}, $AndI {} -> False
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
Something is off with this specialization of `eq`:
|
||||
|
||||
9│ eq = \$You {}, $AndI {} -> False
|
||||
^^
|
||||
|
||||
This value is a declared specialization of type:
|
||||
|
||||
You, AndI -> [ False, True ]
|
||||
|
||||
But the type annotation on `eq` says it must match:
|
||||
|
||||
You, You -> Bool
|
||||
|
||||
Tip: Type comparisons between an opaque type are only ever equal if
|
||||
both types are the same opaque type. Did you mean to create an opaque
|
||||
type by wrapping it? If I have an opaque type Age := U32 I can create
|
||||
an instance of this opaque type by doing @Age 23.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_checked_against_annotation() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ hash ] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
|
||||
Id := U64
|
||||
|
||||
hash : Id -> U32
|
||||
hash = \$Id n -> n
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
Something is off with the body of this definition:
|
||||
|
||||
8│ hash : Id -> U32
|
||||
9│ hash = \$Id n -> n
|
||||
^
|
||||
|
||||
This `n` value is a:
|
||||
|
||||
U64
|
||||
|
||||
But the type annotation says it should be:
|
||||
|
||||
U32
|
||||
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
Something is off with this specialization of `hash`:
|
||||
|
||||
9│ hash = \$Id n -> n
|
||||
^^^^
|
||||
|
||||
This value is a declared specialization of type:
|
||||
|
||||
Id -> U32
|
||||
|
||||
But the type annotation on `hash` says it must match:
|
||||
|
||||
Id -> U64
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_called_with_non_specializing() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ noGoodVeryBadTerrible ] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
|
||||
Id := U64
|
||||
|
||||
hash = \$Id n -> n
|
||||
|
||||
User := {}
|
||||
|
||||
noGoodVeryBadTerrible =
|
||||
{
|
||||
nope: hash ($User {}),
|
||||
notYet: hash (A 1),
|
||||
}
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
The 1st argument to `hash` is not what I expect:
|
||||
|
||||
15│ notYet: hash (A 1),
|
||||
^^^
|
||||
|
||||
This `A` global tag application has the type:
|
||||
|
||||
[ A (Num a) ]b
|
||||
|
||||
But `hash` needs the 1st argument to be:
|
||||
|
||||
a | a has Hash
|
||||
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
This expression has a type that does not implement the abilities it's expected to:
|
||||
|
||||
14│ nope: hash ($User {}),
|
||||
^^^^^^^^
|
||||
|
||||
This User opaque wrapping has the type:
|
||||
|
||||
User
|
||||
|
||||
The ways this expression is used requires that the following types
|
||||
implement the following abilities, which they do not:
|
||||
|
||||
User does not implement Hash
|
||||
|
||||
The type `User` does not fully implement the ability `Hash`. The following
|
||||
specializations are missing:
|
||||
|
||||
A specialization for `hash`, which is defined here:
|
||||
|
||||
4│ hash : a -> U64 | a has Hash
|
||||
^^^^
|
||||
"#
|
||||
),
|
||||
)
|
||||
|
@ -1130,7 +1130,7 @@ so calculations involving them take longer.
|
||||
|
||||
Roc does not let floating point calculations result in `Infinity`, `-Infinity`,
|
||||
or `NaN`. Any operation which would result in one of these
|
||||
(such as `sqrt` or `/`) will return a `Result`.
|
||||
(such as `sqrt` or `/`) will panic.
|
||||
|
||||
Similarly to how there are different sizes of floating point numbers,
|
||||
there are also different sizes of integer to choose from:
|
||||
|
Loading…
Reference in New Issue
Block a user