Merge branch 'trunk' into update_zig_09

This commit is contained in:
Brian Carroll 2022-04-15 21:17:25 +01:00 committed by GitHub
commit 9491d5fae9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
83 changed files with 4010 additions and 797 deletions

View file

@ -74,3 +74,5 @@ Ananda Umamil <zweimach@zweimach.org>
SylvanSign <jake.d.bray@gmail.com> SylvanSign <jake.d.bray@gmail.com>
Nikita Mounier <36044205+nikitamounier@users.noreply.github.com> Nikita Mounier <36044205+nikitamounier@users.noreply.github.com>
Cai Bingjun <62678643+C-BJ@users.noreply.github.com> Cai Bingjun <62678643+C-BJ@users.noreply.github.com>
Jared Cone <jared.cone@gmail.com>
Sean Hagstrom <sean@seanhagstrom.com>

View file

@ -109,7 +109,7 @@ Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specifi
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it. For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal developtment you should be fine without it.
### libcxb libraries ### libxcb libraries
You may see an error like this during builds: You may see an error like this during builds:

10
Cargo.lock generated
View file

@ -3351,6 +3351,7 @@ dependencies = [
"roc_parse", "roc_parse",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_reporting",
"roc_target", "roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
@ -3519,6 +3520,7 @@ dependencies = [
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_region", "roc_region",
"roc_reporting",
"roc_target", "roc_target",
"roc_types", "roc_types",
"snafu", "snafu",
@ -3703,6 +3705,7 @@ dependencies = [
"roc_constrain", "roc_constrain",
"roc_load_internal", "roc_load_internal",
"roc_module", "roc_module",
"roc_reporting",
"roc_target", "roc_target",
"roc_types", "roc_types",
] ]
@ -3734,7 +3737,6 @@ dependencies = [
"roc_target", "roc_target",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"strip-ansi-escapes",
"tempfile", "tempfile",
"ven_pretty", "ven_pretty",
] ]
@ -3893,6 +3895,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_exhaustive", "roc_exhaustive",
"roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_parse", "roc_parse",
@ -3902,6 +3905,7 @@ dependencies = [
"roc_target", "roc_target",
"roc_test_utils", "roc_test_utils",
"roc_types", "roc_types",
"tempfile",
"ven_pretty", "ven_pretty",
] ]
@ -3916,11 +3920,13 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_parse", "roc_parse",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
"roc_reporting",
"roc_solve", "roc_solve",
"roc_target", "roc_target",
"roc_types", "roc_types",
@ -3968,6 +3974,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"roc_collections", "roc_collections",
"roc_error_macros",
"roc_module", "roc_module",
"roc_types", "roc_types",
] ]
@ -4518,6 +4525,7 @@ dependencies = [
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_reporting",
"roc_target", "roc_target",
"test_mono_macros", "test_mono_macros",
] ]

View file

@ -19,6 +19,7 @@ roc_unify = { path = "../compiler/unify"}
roc_load = { path = "../compiler/load" } roc_load = { path = "../compiler/load" }
roc_target = { path = "../compiler/roc_target" } roc_target = { path = "../compiler/roc_target" }
roc_error_macros = { path = "../error_macros" } roc_error_macros = { path = "../error_macros" }
roc_reporting = { path = "../reporting" }
arrayvec = "0.7.2" arrayvec = "0.7.2"
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
page_size = "0.4.2" page_size = "0.4.2"

View file

@ -19,6 +19,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule {
}), }),
subs_by_module, subs_by_module,
TargetInfo::default_x86_64(), TargetInfo::default_x86_64(),
roc_reporting::report::RenderTarget::ColorTerminal,
); );
match loaded { match loaded {

View file

@ -227,12 +227,16 @@ fn solve<'a>(
); );
match unify(subs, actual, expected, Mode::EQ) { 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); introduce(subs, rank, pools, &vars);
state state
} }
Failure(vars, actual_type, expected_type) => { Failure(vars, actual_type, expected_type, _bad_impl) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr( let problem = TypeError::BadExpr(
@ -267,7 +271,7 @@ fn solve<'a>(
// //
// state // state
// } // }
// Failure(vars, _actual_type, _expected_type) => { // Failure(vars, _actual_type, _expected_type, _bad_impl) => {
// introduce(subs, rank, pools, &vars); // introduce(subs, rank, pools, &vars);
// //
// // ERROR NOT REPORTED // // ERROR NOT REPORTED
@ -320,13 +324,17 @@ fn solve<'a>(
); );
match unify(subs, actual, expected, Mode::EQ) { 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); introduce(subs, rank, pools, &vars);
state state
} }
Failure(vars, actual_type, expected_type) => { Failure(vars, actual_type, expected_type, _bad_impl) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr( let problem = TypeError::BadExpr(
@ -391,12 +399,16 @@ fn solve<'a>(
// TODO(ayazhafiz): presence constraints for Expr2/Type2 // TODO(ayazhafiz): presence constraints for Expr2/Type2
match unify(subs, actual, expected, Mode::EQ) { 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); introduce(subs, rank, pools, &vars);
state state
} }
Failure(vars, actual_type, expected_type) => { Failure(vars, actual_type, expected_type, _bad_impl) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
let problem = TypeError::BadPattern( 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); let includes = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, &tag_ty);
match unify(subs, actual, includes, Mode::PRESENT) { 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); introduce(subs, rank, pools, &vars);
state state
} }
Failure(vars, actual_type, expected_type) => { Failure(vars, actual_type, expected_type, _bad_impl) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
// TODO: do we need a better error type here? // TODO: do we need a better error type here?
@ -1281,7 +1297,7 @@ fn adjust_rank_content(
use roc_types::subs::FlatType::*; use roc_types::subs::FlatType::*;
match content { match content {
FlexVar(_) | RigidVar(_) | Error => group_rank, FlexVar(_) | RigidVar(_) | FlexAbleVar(..) | RigidAbleVar(..) | Error => group_rank,
RecursionVar { .. } => group_rank, RecursionVar { .. } => group_rank,
@ -1536,7 +1552,7 @@ fn instantiate_rigids_help(
}; };
} }
FlexVar(_) | Error => {} FlexVar(_) | FlexAbleVar(_, _) | Error => {}
RecursionVar { structure, .. } => { RecursionVar { structure, .. } => {
instantiate_rigids_help(subs, max_rank, pools, 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)))); 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, _) => { Alias(_, args, real_type_var, _) => {
for var_index in args.all_variables() { for var_index in args.all_variables() {
let var = subs[var_index]; let var = subs[var_index];
@ -1772,7 +1793,7 @@ fn deep_copy_var_help(
copy copy
} }
FlexVar(_) | Error => copy, FlexVar(_) | FlexAbleVar(_, _) | Error => copy,
RecursionVar { RecursionVar {
opt_name, opt_name,
@ -1797,6 +1818,12 @@ fn deep_copy_var_help(
copy copy
} }
RigidAbleVar(name, ability) => {
subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability)));
copy
}
Alias(symbol, mut args, real_type_var, kind) => { Alias(symbol, mut args, real_type_var, kind) => {
let mut new_args = Vec::with_capacity(args.all_variables().len()); let mut new_args = Vec::with_capacity(args.all_variables().len());

View file

@ -6,6 +6,7 @@ use roc_build::{
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_load::LoadingProblem; use roc_load::LoadingProblem;
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use roc_reporting::report::RenderTarget;
use roc_target::TargetInfo; use roc_target::TargetInfo;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
@ -68,6 +69,8 @@ pub fn build_file<'a>(
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
target_info, target_info,
// TODO: expose this from CLI?
RenderTarget::ColorTerminal,
)?; )?;
use target_lexicon::Architecture; use target_lexicon::Architecture;
@ -242,8 +245,11 @@ pub fn build_file<'a>(
let link_start = SystemTime::now(); let link_start = SystemTime::now();
let outcome = if surgically_link { let outcome = if surgically_link {
roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path) roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path)
.map_err(|_| { .map_err(|err| {
todo!("gracefully handle failing to surgically link"); todo!(
"gracefully handle failing to surgically link with error: {:?}",
err
);
})?; })?;
BuildOutcome::NoProblems BuildOutcome::NoProblems
} else if matches!(link_type, LinkType::None) { } else if matches!(link_type, LinkType::None) {
@ -374,6 +380,8 @@ pub fn check_file(
src_dir.as_path(), src_dir.as_path(),
subs_by_module, subs_by_module,
target_info, target_info,
// TODO: expose this from CLI?
RenderTarget::ColorTerminal,
)?; )?;
let buf = &mut String::with_capacity(1024); let buf = &mut String::with_capacity(1024);

View file

@ -15,7 +15,9 @@ use std::path::PathBuf;
use std::process; use std::process;
use std::process::Command; use std::process::Command;
use target_lexicon::BinaryFormat; 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; pub mod build;
mod format; mod format;
@ -38,6 +40,7 @@ pub const FLAG_NO_LINK: &str = "no-link";
pub const FLAG_TARGET: &str = "target"; pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time"; pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker"; pub const FLAG_LINK: &str = "roc-linker";
pub const FLAG_LINKER: &str = "linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host"; pub const FLAG_PRECOMPILED: &str = "precompiled-host";
pub const FLAG_VALGRIND: &str = "valgrind"; pub const FLAG_VALGRIND: &str = "valgrind";
pub const FLAG_CHECK: &str = "check"; pub const FLAG_CHECK: &str = "check";
@ -110,13 +113,22 @@ pub fn build_app<'a>() -> App<'a> {
.arg( .arg(
Arg::new(FLAG_LINK) Arg::new(FLAG_LINK)
.long(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), .required(false),
) )
.arg( .arg(
Arg::new(FLAG_PRECOMPILED) Arg::new(FLAG_PRECOMPILED)
.long(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"])
.default_value("false")
.required(false), .required(false),
) )
.arg( .arg(
@ -207,13 +219,22 @@ pub fn build_app<'a>() -> App<'a> {
.arg( .arg(
Arg::new(FLAG_LINK) Arg::new(FLAG_LINK)
.long(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), .required(false),
) )
.arg( .arg(
Arg::new(FLAG_PRECOMPILED) Arg::new(FLAG_PRECOMPILED)
.long(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"])
.default_value("false")
.required(false), .required(false),
) )
.arg( .arg(
@ -307,16 +328,27 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
(false, true) => LinkType::None, (false, true) => LinkType::None,
(false, false) => LinkType::Executable, (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) { // TODO remove FLAG_LINK from the code base anytime after the end of May 2022
panic!( if matches.is_present(FLAG_LINK) {
"Link type, {:?}, with target, {}, not supported by roc linker", 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.");
link_type, triple 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); let path = Path::new(filename);
// Spawn the root task // Spawn the root task
@ -507,65 +539,74 @@ fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) {
println!("Running wasm files not support"); println!("Running wasm files not support");
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum Target { enum Target {
Host, System,
X86_32, Linux32,
X86_64, Linux64,
Wasm32, Wasm32,
} }
impl Default for Target { impl Default for Target {
fn default() -> Self { fn default() -> Self {
Target::Host Target::System
} }
} }
impl Target { impl Target {
const fn as_str(&self) -> &'static str { const fn as_str(&self) -> &'static str {
use Target::*;
match self { match self {
Target::Host => "host", System => "system",
Target::X86_32 => "x86_32", Linux32 => "linux32",
Target::X86_64 => "x86_64", Linux64 => "linux64",
Target::Wasm32 => "wasm32", Wasm32 => "wasm32",
} }
} }
/// NOTE keep up to date! /// NOTE keep up to date!
const OPTIONS: &'static [&'static str] = &[ const OPTIONS: &'static [&'static str] = &[
Target::Host.as_str(), Target::System.as_str(),
Target::X86_32.as_str(), Target::Linux32.as_str(),
Target::X86_64.as_str(), Target::Linux64.as_str(),
Target::Wasm32.as_str(), Target::Wasm32.as_str(),
]; ];
fn to_triple(&self) -> Triple { fn to_triple(self) -> Triple {
let mut triple = Triple::unknown(); use Target::*;
match self { match self {
Target::Host => Triple::host(), System => Triple::host(),
Target::X86_32 => { Linux32 => Triple {
triple.architecture = Architecture::X86_32(X86_32Architecture::I386); architecture: Architecture::X86_32(X86_32Architecture::I386),
triple.binary_format = BinaryFormat::Elf; vendor: Vendor::Unknown,
operating_system: OperatingSystem::Linux,
// TODO make this user-specified? environment: Environment::Musl,
triple.operating_system = OperatingSystem::Linux; binary_format: BinaryFormat::Elf,
},
triple Linux64 => Triple {
} architecture: Architecture::X86_64,
Target::X86_64 => { vendor: Vendor::Unknown,
triple.architecture = Architecture::X86_64; operating_system: OperatingSystem::Linux,
triple.binary_format = BinaryFormat::Elf; environment: Environment::Musl,
binary_format: BinaryFormat::Elf,
triple },
} Wasm32 => Triple {
Target::Wasm32 => { architecture: Architecture::Wasm32,
triple.architecture = Architecture::Wasm32; vendor: Vendor::Unknown,
triple.binary_format = BinaryFormat::Wasm; operating_system: OperatingSystem::Unknown,
environment: Environment::Unknown,
triple binary_format: BinaryFormat::Wasm,
},
} }
} }
} }
impl From<&Target> for Triple {
fn from(target: &Target) -> Self {
target.to_triple()
}
} }
impl std::fmt::Display for Target { impl std::fmt::Display for Target {
@ -579,9 +620,9 @@ impl std::str::FromStr for Target {
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s {
"host" => Ok(Target::Host), "system" => Ok(Target::System),
"x86_32" => Ok(Target::X86_32), "linux32" => Ok(Target::Linux32),
"x86_64" => Ok(Target::X86_64), "linux64" => Ok(Target::Linux64),
"wasm32" => Ok(Target::Wasm32), "wasm32" => Ok(Target::Wasm32),
_ => Err(()), _ => Err(()),
} }

View file

@ -2,6 +2,7 @@
extern crate pretty_assertions; extern crate pretty_assertions;
extern crate bumpalo; extern crate bumpalo;
extern crate indoc;
extern crate roc_collections; extern crate roc_collections;
extern crate roc_load; extern crate roc_load;
extern crate roc_module; extern crate roc_module;
@ -24,11 +25,12 @@ mod cli_run {
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))] #[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")))] #[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"))] #[cfg(not(target_os = "macos"))]
const ALLOW_VALGRIND: bool = true; const ALLOW_VALGRIND: bool = true;
@ -255,12 +257,7 @@ mod cli_run {
} }
"hello-gui" => { "hello-gui" => {
// Since this one requires opening a window, we do `roc build` on it but don't run it. // 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; return;
} }
@ -291,14 +288,14 @@ mod cli_run {
example.use_valgrind, 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( check_output_with_stdin(
&file_name, &file_name,
example.stdin, example.stdin,
example.executable_filename, example.executable_filename,
&["--roc-linker"], &["--linker", "legacy"],
example.input_file.and_then(|file| Some(example_file(dir_name, file))), example.input_file.and_then(|file| Some(example_file(dir_name, file))),
example.expected_ending, example.expected_ending,
example.use_valgrind, example.use_valgrind,

View file

@ -5,7 +5,7 @@ use inkwell::{
}; };
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel; 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 { pub fn target_triple_str(target: &Triple) -> &'static str {
// Best guide I've found on how to determine these magic strings: // 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": // and an open proposal to unify them with the more typical "target triples":
// https://github.com/ziglang/zig/issues/4911 // https://github.com/ziglang/zig/issues/4911
match target { match target {
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux,
environment: Environment::Musl,
..
} => "x86_64-linux-musl",
Triple { Triple {
architecture: Architecture::X86_64, architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux, operating_system: OperatingSystem::Linux,
.. ..
} => "x86_64-linux-gnu", } => "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 { Triple {
architecture: Architecture::X86_32(target_lexicon::X86_32Architecture::I386), architecture: Architecture::X86_32(target_lexicon::X86_32Architecture::I386),
operating_system: OperatingSystem::Linux, operating_system: OperatingSystem::Linux,

View file

@ -60,7 +60,7 @@ Its one thing to actually write these functions, its _another_ thing to let the
## Specifying how we pass args to the function ## Specifying how we pass args to the function
### builtins/mono/src/borrow.rs ### builtins/mono/src/borrow.rs
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail. After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for your builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
## Testing it ## Testing it
### solve/tests/solve_expr.rs ### solve/tests/solve_expr.rs

View file

@ -28,12 +28,14 @@ pub fn build(b: *Builder) void {
// TODO allow for native target for maximum speed // TODO allow for native target for maximum speed
}, },
}); });
const i386_target = makeI386Target(); const linux32_target = makeLinux32Target();
const linux64_target = makeLinux64Target();
const wasm32_target = makeWasm32Target(); const wasm32_target = makeWasm32Target();
// LLVM IR // LLVM IR
generateLlvmIrFile(b, mode, host_target, main_path, "ir", "builtins-host"); 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"); generateLlvmIrFile(b, mode, wasm32_target, main_path, "ir-wasm32", "builtins-wasm32");
// Generate Object Files // Generate Object Files
@ -88,7 +90,7 @@ fn generateObjectFile(
obj_step.dependOn(&obj.step); obj_step.dependOn(&obj.step);
} }
fn makeI386Target() CrossTarget { fn makeLinux32Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable; var target = CrossTarget.parse(.{}) catch unreachable;
target.cpu_arch = std.Target.Cpu.Arch.i386; target.cpu_arch = std.Target.Cpu.Arch.i386;
@ -98,6 +100,16 @@ fn makeI386Target() CrossTarget {
return target; 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 { fn makeWasm32Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable; var target = CrossTarget.parse(.{}) catch unreachable;

View file

@ -310,9 +310,7 @@ pub const RocDec = extern struct {
// (n / 0) is an error // (n / 0) is an error
if (denominator_i128 == 0) { if (denominator_i128 == 0) {
// The compiler frontend does the `denominator == 0` check for us, @panic("TODO runtime exception for dividing by 0!");
// therefore this case is unreachable from roc user code
unreachable;
} }
// If they're both negative, or if neither is negative, the final answer // If they're both negative, or if neither is negative, the final answer

View file

@ -102,7 +102,7 @@ pub fn exportRound(comptime T: type, comptime name: []const u8) void {
pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void { pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void {
comptime var f = struct { comptime var f = struct {
fn func(a: T, b: T) callconv(.C) T { fn func(a: T, b: T) callconv(.C) T {
return math.divCeil(T, a, b) catch unreachable; return math.divCeil(T, a, b) catch @panic("TODO runtime exception for dividing by 0!");
} }
}.func; }.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });

View file

@ -468,8 +468,10 @@ fn strFromIntHelp(comptime T: type, int: T) RocStr {
const size = comptime blk: { const size = comptime blk: {
// the string representation of the minimum i128 value uses at most 40 characters // the string representation of the minimum i128 value uses at most 40 characters
var buf: [40]u8 = undefined; var buf: [40]u8 = undefined;
var result = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable; var resultMin = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable;
break :blk result.len; 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; var buf: [size]u8 = undefined;

View file

@ -44,6 +44,13 @@ fn main() {
generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386"); generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
generate_bc_file(
&bitcode_path,
&build_script_dir_path,
"ir-x86_64",
"builtins-x86_64",
);
// OBJECT FILES // OBJECT FILES
#[cfg(windows)] #[cfg(windows)]
const BUILTINS_HOST_FILE: &str = "builtins-host.obj"; const BUILTINS_HOST_FILE: &str = "builtins-host.obj";
@ -105,7 +112,12 @@ fn generate_object_file(
println!("Moving zig object `{}` to: {}", zig_object, dest_obj); 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) // 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
);
});
} }
} }

View file

@ -69,6 +69,7 @@ interface Num
isNegative, isNegative,
rem, rem,
div, div,
divChecked,
modInt, modInt,
modFloat, modFloat,
sqrt, sqrt,
@ -97,7 +98,9 @@ interface Num
bytesToU16, bytesToU16,
bytesToU32, bytesToU32,
divCeil, divCeil,
divCeilChecked,
divFloor, divFloor,
divFloorChecked,
toStr, toStr,
isMultipleOf, isMultipleOf,
minI8, minI8,
@ -229,10 +232,13 @@ atan : Float a -> Float a
sqrt : Float a -> Result (Float a) [ SqrtOfNegative ]* sqrt : Float a -> Result (Float a) [ SqrtOfNegative ]*
log : Float a -> Result (Float a) [ LogNeedsPositive ]* log : Float a -> Result (Float a) [ LogNeedsPositive ]*
div : Float a, Float a -> Result (Float a) [ DivByZero ]* div : Float a, Float a -> Float a
divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]*
divCeil: Int a, Int a -> Result (Int a) [ DivByZero ]* divCeil : Int a, Int a -> Int a
divFloor: Int a, Int a -> Result (Int a) [ DivByZero ]* divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
divFloor : Int a, Int a -> Int a
divFloorChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
# mod : Float a, Float a -> Result (Float a) [ DivByZero ]* # mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
rem : Int a, Int a -> Result (Int a) [ DivByZero ]* rem : Int a, Int a -> Result (Int a) [ DivByZero ]*

View file

@ -316,17 +316,31 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(SolvedType::Wildcard), Box::new(SolvedType::Wildcard),
); );
// divInt : Int a, Int a -> Result (Int a) [ DivByZero ]* // divFloor : Int a, Int a -> Int a
add_top_level_function_type!( 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)))
);
// divFloorChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_DIV_FLOOR_CHECKED,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
); );
//divCeil: Int a, Int a -> Result (Int a) [ DivByZero ]* // divCeil : Int a, Int a -> Int a
add_top_level_function_type!( add_top_level_function_type!(
Symbol::NUM_DIV_CEIL, Symbol::NUM_DIV_CEIL,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))], vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1)))
);
// 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))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())), Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
); );
@ -659,6 +673,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_top_level_function_type!( add_top_level_function_type!(
Symbol::NUM_DIV_FLOAT, Symbol::NUM_DIV_FLOAT,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))], vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1)))
);
// divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_DIV_FLOAT_CHECKED,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())), Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())),
); );

View file

@ -1,55 +1,77 @@
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::MutMap;
use roc_module::symbol::Symbol; 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 /// 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. /// defining type, and what type variables need to be instantiated with instances of the ability.
#[derive(Debug)] // TODO: SoA and put me in an arena
struct AbilityMemberData { #[derive(Debug, Clone, PartialEq, Eq)]
#[allow(unused)] pub struct AbilityMemberData {
parent_ability: Symbol, pub parent_ability: Symbol,
#[allow(unused)] pub signature: Type,
signature: Type, pub variables: MemberVariables,
#[allow(unused)] pub region: Region,
bound_has_clauses: Vec<HasClause>, }
/// 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 /// Stores information about what abilities exist in a scope, what it means to implement an
/// ability, and what types implement them. /// 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 // 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. // are only dealing with inter-module abilities for now.
#[derive(Default, Debug)] #[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct AbilitiesStore { pub struct AbilitiesStore {
/// Maps an ability to the members defining it. /// Maps an ability to the members defining it.
#[allow(unused)]
members_of_ability: MutMap<Symbol, Vec<Symbol>>, members_of_ability: MutMap<Symbol, Vec<Symbol>>,
/// Information about all members composing abilities. /// Information about all members composing abilities.
ability_members: MutMap<Symbol, AbilityMemberData>, ability_members: MutMap<Symbol, AbilityMemberData>,
/// Tuples of (type, member) specifying that `type` declares an implementation of an ability /// Map of symbols that specialize an ability member to the root ability symbol name.
/// member `member`. /// For example, for the program
#[allow(unused)] /// Hash has hash : a -> U64 | a has Hash
declared_implementations: MutSet<(Symbol, Symbol)>, /// ^^^^ 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 { impl AbilitiesStore {
/// Records the definition of an ability, including its members.
pub fn register_ability( pub fn register_ability(
&mut self, &mut self,
ability: Symbol, ability: Symbol,
members: Vec<(Symbol, Type, Vec<HasClause>)>, members: Vec<(Symbol, Region, Type, MemberVariables)>,
) { ) {
let mut members_vec = Vec::with_capacity(members.len()); 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); members_vec.push(member);
let old_member = self.ability_members.insert( let old_member = self.ability_members.insert(
member, member,
AbilityMemberData { AbilityMemberData {
parent_ability: ability, parent_ability: ability,
signature, signature,
bound_has_clauses, region,
variables,
}, },
); );
debug_assert!(old_member.is_none(), "Replacing existing member definition"); 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) { /// Records a specialization of `ability_member` with specialized type `implementing_type`.
let old_impl = self /// Entries via this function are considered a source of truth. It must be ensured that a
.declared_implementations /// specialization is validated before being registered here.
.insert((implementing_type, ability_member)); pub fn register_specialization_for_type(
debug_assert!(!old_impl, "Replacing existing implementation"); &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 { pub fn is_ability_member_name(&self, name: Symbol) -> bool {
self.ability_members.contains_key(&name) 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())
}
} }

View file

@ -19,6 +19,22 @@ pub struct Annotation {
pub aliases: SendMap<Symbol, Alias>, 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)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct NamedVariable { pub struct NamedVariable {
pub variable: Variable, pub variable: Variable,
@ -27,21 +43,40 @@ pub struct NamedVariable {
pub first_seen: Region, 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)] #[derive(Clone, Debug, PartialEq, Default)]
pub struct IntroducedVariables { pub struct IntroducedVariables {
pub wildcards: Vec<Loc<Variable>>, pub wildcards: Vec<Loc<Variable>>,
pub lambda_sets: Vec<Variable>, pub lambda_sets: Vec<Variable>,
pub inferred: Vec<Loc<Variable>>, pub inferred: Vec<Loc<Variable>>,
pub named: Vec<NamedVariable>, pub named: Vec<NamedVariable>,
pub able: Vec<AbleVariable>,
pub host_exposed_aliases: MutMap<Symbol, Variable>, pub host_exposed_aliases: MutMap<Symbol, Variable>,
} }
impl IntroducedVariables { 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>) { pub fn insert_named(&mut self, name: Lowercase, var: Loc<Variable>) {
debug_assert!(!self self.debug_assert_not_already_present(var.value);
.named
.iter()
.any(|nv| nv.name == name || nv.variable == var.value));
let named_variable = NamedVariable { let named_variable = NamedVariable {
name, name,
@ -52,19 +87,36 @@ impl IntroducedVariables {
self.named.push(named_variable); 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>) { pub fn insert_wildcard(&mut self, var: Loc<Variable>) {
self.debug_assert_not_already_present(var.value);
self.wildcards.push(var); self.wildcards.push(var);
} }
pub fn insert_inferred(&mut self, var: Loc<Variable>) { pub fn insert_inferred(&mut self, var: Loc<Variable>) {
self.debug_assert_not_already_present(var.value);
self.inferred.push(var); self.inferred.push(var);
} }
fn insert_lambda_set(&mut self, var: Variable) { fn insert_lambda_set(&mut self, var: Variable) {
self.debug_assert_not_already_present(var);
self.lambda_sets.push(var); self.lambda_sets.push(var);
} }
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { 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); self.host_exposed_aliases.insert(symbol, var);
} }
@ -78,6 +130,10 @@ impl IntroducedVariables {
self.named.extend(other.named.iter().cloned()); self.named.extend(other.named.iter().cloned());
self.named.sort(); self.named.sort();
self.named.dedup(); self.named.dedup();
self.able.extend(other.able.iter().cloned());
self.able.sort();
self.able.dedup();
} }
pub fn union_owned(&mut self, other: Self) { pub fn union_owned(&mut self, other: Self) {
@ -91,22 +147,42 @@ impl IntroducedVariables {
self.named.dedup(); self.named.dedup();
} }
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { pub fn var_by_name(&self, name: &Lowercase) -> Option<Variable> {
self.named (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() .iter()
.find(|nv| &nv.name == name) .find(|nv| &nv.name == name)
.map(|nv| &nv.variable) .map(NamedOrAbleVariable::Named)
{
return Some(nav);
} }
self.able
pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
self.named
.iter() .iter()
.find(|nv| nv.variable == var) .find(|av| &av.name == name)
.map(|nv| &nv.name) .map(NamedOrAbleVariable::Able)
} }
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<&NamedVariable> { pub fn collect_able(&self) -> Vec<Variable> {
self.named.iter().find(|nv| &nv.name == name) 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( pub fn canonicalize_annotation_with_possible_clauses(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
@ -161,16 +230,17 @@ pub fn canonicalize_annotation_with_possible_clauses(
region: Region, region: Region,
var_store: &mut VarStore, var_store: &mut VarStore,
abilities_in_scope: &[Symbol], abilities_in_scope: &[Symbol],
) -> (Annotation, Vec<Loc<HasClause>>) { ) -> Annotation {
let mut introduced_variables = IntroducedVariables::default(); let mut introduced_variables = IntroducedVariables::default();
let mut references = MutSet::default(); let mut references = MutSet::default();
let mut aliases = SendMap::default(); let mut aliases = SendMap::default();
let (annotation, region, clauses) = match annotation { let (annotation, region) = match annotation {
TypeAnnotation::Where(annotation, clauses) => { 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() { for clause in clauses.iter() {
match canonicalize_has_clause( let opt_err = canonicalize_has_clause(
env, env,
scope, scope,
var_store, var_store,
@ -178,24 +248,19 @@ pub fn canonicalize_annotation_with_possible_clauses(
clause, clause,
abilities_in_scope, abilities_in_scope,
&mut references, &mut references,
) { );
Ok(result) => can_clauses.push(Loc::at(clause.region, result)), if let Err(err_type) = opt_err {
Err(err_type) => { return Annotation {
return (
Annotation {
typ: err_type, typ: err_type,
introduced_variables, introduced_variables,
references, references,
aliases, aliases,
},
can_clauses,
)
}
}; };
} }
(&annotation.value, annotation.region, can_clauses)
} }
annot => (annot, region, vec![]), (&annotation.value, annotation.region)
}
annot => (annot, region),
}; };
let typ = can_annotation_help( let typ = can_annotation_help(
@ -209,14 +274,12 @@ pub fn canonicalize_annotation_with_possible_clauses(
&mut references, &mut references,
); );
let annot = Annotation { Annotation {
typ, typ,
introduced_variables, introduced_variables,
references, references,
aliases, aliases,
}; }
(annot, clauses)
} }
fn make_apply_symbol( fn make_apply_symbol(
@ -502,7 +565,7 @@ fn can_annotation_help(
let name = Lowercase::from(*v); let name = Lowercase::from(*v);
match introduced_variables.var_by_name(&name) { match introduced_variables.var_by_name(&name) {
Some(var) => Type::Variable(*var), Some(var) => Type::Variable(var),
None => { None => {
let var = var_store.fresh(); let var = var_store.fresh();
@ -566,8 +629,8 @@ fn can_annotation_help(
let var_name = Lowercase::from(var); let var_name = Lowercase::from(var);
if let Some(var) = introduced_variables.var_by_name(&var_name) { if let Some(var) = introduced_variables.var_by_name(&var_name) {
vars.push((var_name.clone(), Type::Variable(*var))); vars.push((var_name.clone(), Type::Variable(var)));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, *var))); lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
} else { } else {
let var = var_store.fresh(); let var = var_store.fresh();
@ -799,7 +862,7 @@ fn canonicalize_has_clause(
clause: &Loc<roc_parse::ast::HasClause<'_>>, clause: &Loc<roc_parse::ast::HasClause<'_>>,
abilities_in_scope: &[Symbol], abilities_in_scope: &[Symbol],
references: &mut MutSet<Symbol>, references: &mut MutSet<Symbol>,
) -> Result<HasClause, Type> { ) -> Result<(), Type> {
let Loc { let Loc {
region, region,
value: roc_parse::ast::HasClause { var, ability }, 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 var_name_ident = var_name.to_string().into();
let shadow = Loc::at(region, var_name_ident); let shadow = Loc::at(region, var_name_ident);
env.problem(roc_problem::can::Problem::Shadowing { env.problem(roc_problem::can::Problem::Shadowing {
original_region: shadowing.first_seen, original_region: shadowing.first_seen(),
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable, kind: ShadowKind::Variable,
}); });
return Err(Type::Erroneous(Problem::Shadowed( return Err(Type::Erroneous(Problem::Shadowed(
shadowing.first_seen, shadowing.first_seen(),
shadow, shadow,
))); )));
} }
let var = var_store.fresh(); 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 { Ok(())
var_name,
var,
ability,
})
} }
#[allow(clippy::too_many_arguments)] #[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_name = Lowercase::from(loc_field_name.value);
let field_type = { let field_type = {
if let Some(var) = introduced_variables.var_by_name(&field_name) { if let Some(var) = introduced_variables.var_by_name(&field_name) {
Type::Variable(*var) Type::Variable(var)
} else { } else {
let field_var = var_store.fresh(); let field_var = var_store.fresh();
introduced_variables.insert_named( introduced_variables.insert_named(

View file

@ -195,8 +195,11 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_COS => num_cos, NUM_COS => num_cos,
NUM_TAN => num_tan, NUM_TAN => num_tan,
NUM_DIV_FLOAT => num_div_float, NUM_DIV_FLOAT => num_div_float,
NUM_DIV_INT => num_div_int, NUM_DIV_FLOAT_CHECKED => num_div_float_checked,
NUM_DIV_FLOOR => num_div_floor,
NUM_DIV_FLOOR_CHECKED => num_div_floor_checked,
NUM_DIV_CEIL => num_div_ceil, NUM_DIV_CEIL => num_div_ceil,
NUM_DIV_CEIL_CHECKED => num_div_ceil_checked,
NUM_ABS => num_abs, NUM_ABS => num_abs,
NUM_NEG => num_neg, NUM_NEG => num_neg,
NUM_REM => num_rem, NUM_REM => num_rem,
@ -4295,8 +4298,13 @@ fn num_abs(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// Num.div : Float, Float -> Result Float [ DivByZero ]* /// Num.div : Float, Float -> Float
fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumDivUnchecked)
}
/// Num.divChecked : Float, Float -> Result Float [ DivByZero ]*
fn num_div_float_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let num_var = var_store.fresh(); let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh(); let unbound_zero_var = var_store.fresh();
@ -4361,8 +4369,13 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// Num.div : Int a , Int a -> Result (Int a) [ DivByZero ]* /// Num.divFloor : Int a, Int a -> Int a
fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_div_floor(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumDivUnchecked)
}
/// 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 bool_var = var_store.fresh();
let num_var = var_store.fresh(); let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh(); let unbound_zero_var = var_store.fresh();
@ -4432,8 +4445,13 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// Num.divCeil : Int a , Int a -> Result (Int a) [ DivByZero ]* /// Num.divCeil : Int a, Int a -> Int a
fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def { fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumDivCeilUnchecked)
}
/// Num.divCeilChecked : Int a , Int a -> Result (Int a) [ DivByZero ]*
fn num_div_ceil_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let num_var = var_store.fresh(); let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh(); let unbound_zero_var = var_store.fresh();

View file

@ -1,4 +1,4 @@
use crate::abilities::AbilitiesStore; use crate::abilities::MemberVariables;
use crate::annotation::canonicalize_annotation; use crate::annotation::canonicalize_annotation;
use crate::annotation::canonicalize_annotation_with_possible_clauses; use crate::annotation::canonicalize_annotation_with_possible_clauses;
use crate::annotation::IntroducedVariables; 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. // 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() { for (loc_ability_name, members) in abilities.into_values() {
let mut can_members = Vec::with_capacity(members.len()); let mut can_members = Vec::with_capacity(members.len());
for member in members { for member in members {
let (member_annot, clauses) = canonicalize_annotation_with_possible_clauses( let member_annot = canonicalize_annotation_with_possible_clauses(
env, env,
&mut scope, &mut scope,
&member.typ.value, &member.typ.value,
@ -450,13 +449,14 @@ pub fn canonicalize_defs<'a>(
output.references.referenced_type_defs.insert(symbol); output.references.referenced_type_defs.insert(symbol);
} }
let name_region = member.name.region;
let member_name = member.name.extract_spaces().item; let member_name = member.name.extract_spaces().item;
let member_sym = match scope.introduce( let member_sym = match scope.introduce(
member_name.into(), member_name.into(),
&env.exposed_ident_ids, &env.exposed_ident_ids,
&mut env.ident_ids, &mut env.ident_ids,
member.name.region, name_region,
) { ) {
Ok(sym) => sym, Ok(sym) => sym,
Err((original_region, shadow, _new_symbol)) => { 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 // What variables in the annotation are bound to the parent ability, and what variables
// are bound to some other ability? // are bound to some other ability?
let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) = let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) =
clauses member_annot
.into_iter() .introduced_variables
.partition(|has_clause| has_clause.value.ability == loc_ability_name.value); .able
.iter()
.partition(|av| av.ability == loc_ability_name.value);
let mut bad_has_clauses = false; let mut bad_has_clauses = false;
@ -485,18 +487,38 @@ pub fn canonicalize_defs<'a>(
env.problem(Problem::AbilityMemberMissingHasClause { env.problem(Problem::AbilityMemberMissingHasClause {
member: member_sym, member: member_sym,
ability: loc_ability_name.value, 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; bad_has_clauses = true;
} }
if !variables_bound_to_other_abilities.is_empty() { if !variables_bound_to_other_abilities.is_empty() {
// Disallow variables bound to other abilities, for now. // 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 { env.problem(Problem::AbilityMemberBindsExternalAbility {
member: member_sym, member: member_sym,
ability: loc_ability_name.value, ability: loc_ability_name.value,
region: bad_clause.region, region: bad_variable.first_seen,
}); });
} }
bad_has_clauses = true; bad_has_clauses = true;
@ -507,15 +529,26 @@ pub fn canonicalize_defs<'a>(
continue; continue;
} }
let has_clauses = variables_bound_to_ability // The introduced variables are good; add them to the output.
.into_iter() output
.map(|clause| clause.value) .introduced_variables
.collect(); .union(&member_annot.introduced_variables);
can_members.push((member_sym, member_annot.typ, has_clauses));
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. // 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, // 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. // once we've finished assembling the entire scope.
let mut pending_value_defs = Vec::with_capacity(value_defs.len()); let mut pending_value_defs = Vec::with_capacity(value_defs.len());
for loc_def in value_defs.into_iter() { for loc_def in value_defs.into_iter() {
match to_pending_value_def( match to_pending_value_def(env, var_store, loc_def.value, &mut scope, pattern_type) {
env,
var_store,
loc_def.value,
&mut scope,
&abilities_store,
pattern_type,
) {
None => { /* skip */ } None => { /* skip */ }
Some((new_output, pending_def)) => { Some((new_output, pending_def)) => {
// store the top-level defs, used to ensure that closures won't capture them // 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( let def = single_can_def(
loc_can_pattern, loc_can_pattern,
loc_can_expr, loc_can_expr,
@ -1289,7 +1317,9 @@ fn canonicalize_pending_value_def<'a>(
// which also implies it's not a self tail call! // which also implies it's not a self tail call!
// //
// Only defs of the form (foo = ...) can be closure declarations or self tail calls. // 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 { if let Closure(ClosureData {
function_type, function_type,
closure_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 // 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. // we defined went unused by the return expression. If any were unused, report it.
for (symbol, region) in symbols_introduced { 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)); env.problem(Problem::UnusedDef(symbol, region));
} }
@ -1772,7 +1804,6 @@ fn to_pending_value_def<'a>(
var_store: &mut VarStore, var_store: &mut VarStore,
def: &'a ast::ValueDef<'a>, def: &'a ast::ValueDef<'a>,
scope: &mut Scope, scope: &mut Scope,
abilities_store: &AbilitiesStore,
pattern_type: PatternType, pattern_type: PatternType,
) -> Option<(Output, PendingValueDef<'a>)> { ) -> Option<(Output, PendingValueDef<'a>)> {
use ast::ValueDef::*; use ast::ValueDef::*;
@ -1784,7 +1815,6 @@ fn to_pending_value_def<'a>(
env, env,
var_store, var_store,
scope, scope,
abilities_store,
pattern_type, pattern_type,
&loc_pattern.value, &loc_pattern.value,
loc_pattern.region, loc_pattern.region,
@ -1801,7 +1831,6 @@ fn to_pending_value_def<'a>(
env, env,
var_store, var_store,
scope, scope,
abilities_store,
pattern_type, pattern_type,
&loc_pattern.value, &loc_pattern.value,
loc_pattern.region, loc_pattern.region,
@ -1832,7 +1861,6 @@ fn to_pending_value_def<'a>(
env, env,
var_store, var_store,
scope, scope,
abilities_store,
pattern_type, pattern_type,
&body_pattern.value, &body_pattern.value,
body_pattern.region, body_pattern.region,

View file

@ -44,6 +44,15 @@ impl<T> PExpected<T> {
PExpected::ForReason(reason, _val, region) => PExpected::ForReason(reason, new, region), 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> { impl<T> Expected<T> {

View file

@ -1083,6 +1083,7 @@ fn canonicalize_when_branch<'a>(
&& !branch_output.references.has_value_lookup(symbol) && !branch_output.references.has_value_lookup(symbol)
&& !branch_output.references.has_type_lookup(symbol) && !branch_output.references.has_type_lookup(symbol)
&& !original_scope.contains_symbol(symbol) && !original_scope.contains_symbol(symbol)
&& !scope.abilities_store.is_specialization_name(symbol)
{ {
env.problem(Problem::UnusedDef(symbol, *region)); env.problem(Problem::UnusedDef(symbol, *region));
} }

View file

@ -1,3 +1,4 @@
use crate::abilities::AbilitiesStore;
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::effect_module::HostedGeneratedFunctions; use crate::effect_module::HostedGeneratedFunctions;
use crate::env::Env; use crate::env::Env;
@ -28,11 +29,13 @@ pub struct Module {
/// all aliases. `bool` indicates whether it is exposed /// all aliases. `bool` indicates whether it is exposed
pub aliases: MutMap<Symbol, (bool, Alias)>, pub aliases: MutMap<Symbol, (bool, Alias)>,
pub rigid_variables: RigidVariables, pub rigid_variables: RigidVariables,
pub abilities_store: AbilitiesStore,
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct RigidVariables { pub struct RigidVariables {
pub named: MutMap<Variable, Lowercase>, pub named: MutMap<Variable, Lowercase>,
pub able: MutMap<Variable, (Lowercase, Symbol)>,
pub wildcards: MutSet<Variable>, pub wildcards: MutSet<Variable>,
} }
@ -250,6 +253,7 @@ pub fn canonicalize_module_defs<'a>(
if !output.references.has_value_lookup(symbol) if !output.references.has_value_lookup(symbol)
&& !output.references.has_type_lookup(symbol) && !output.references.has_type_lookup(symbol)
&& !exposed_symbols.contains(&symbol) && !exposed_symbols.contains(&symbol)
&& !scope.abilities_store.is_specialization_name(symbol)
{ {
env.problem(Problem::UnusedDef(symbol, region)); env.problem(Problem::UnusedDef(symbol, region));
} }
@ -259,6 +263,12 @@ pub fn canonicalize_module_defs<'a>(
rigid_variables.named.insert(named.variable, named.name); 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 { for var in output.introduced_variables.wildcards {
rigid_variables.wildcards.insert(var.value); rigid_variables.wildcards.insert(var.value);
} }
@ -444,6 +454,10 @@ pub fn canonicalize_module_defs<'a>(
aliases.insert(symbol, alias); 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 // By this point, all exposed symbols should have been removed from
// exposed_symbols and added to exposed_vars_by_symbol. If any were // exposed_symbols and added to exposed_vars_by_symbol. If any were
// not, that means they were declared as exposed but there was // not, that means they were declared as exposed but there was

View file

@ -1,4 +1,3 @@
use crate::abilities::AbilitiesStore;
use crate::annotation::freshen_opaque_def; use crate::annotation::freshen_opaque_def;
use crate::env::Env; use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output}; 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>, env: &mut Env<'a>,
var_store: &mut VarStore, var_store: &mut VarStore,
scope: &mut Scope, scope: &mut Scope,
abilities_store: &AbilitiesStore,
pattern_type: PatternType, pattern_type: PatternType,
pattern: &ast::Pattern<'a>, pattern: &ast::Pattern<'a>,
region: Region, region: Region,
@ -172,7 +170,6 @@ pub fn canonicalize_def_header_pattern<'a>(
&env.exposed_ident_ids, &env.exposed_ident_ids,
&mut env.ident_ids, &mut env.ident_ids,
region, region,
abilities_store,
) { ) {
Ok((symbol, shadowing_ability_member)) => { Ok((symbol, shadowing_ability_member)) => {
output.references.bound_symbols.insert(symbol); output.references.bound_symbols.insert(symbol);

View file

@ -22,7 +22,7 @@ pub struct Scope {
pub aliases: SendMap<Symbol, Alias>, pub aliases: SendMap<Symbol, Alias>,
/// The abilities currently in scope, and their implementors. /// 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 /// The current module being processed. This will be used to turn
/// unqualified idents into Symbols. /// unqualified idents into Symbols.
@ -68,7 +68,7 @@ impl Scope {
symbols: SendMap::default(), symbols: SendMap::default(),
aliases, aliases,
// TODO(abilities): default abilities in scope // TODO(abilities): default abilities in scope
abilities: SendMap::default(), abilities_store: AbilitiesStore::default(),
} }
} }
@ -247,7 +247,6 @@ impl Scope {
exposed_ident_ids: &IdentIds, exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds, all_ident_ids: &mut IdentIds,
region: Region, region: Region,
abilities_store: &AbilitiesStore,
) -> Result<(Symbol, Option<Symbol>), (Region, Loc<Ident>, Symbol)> { ) -> Result<(Symbol, Option<Symbol>), (Region, Loc<Ident>, Symbol)> {
match self.idents.get(&ident) { match self.idents.get(&ident) {
Some(&(original_symbol, original_region)) => { Some(&(original_symbol, original_region)) => {
@ -256,7 +255,9 @@ impl Scope {
self.symbols.insert(shadow_symbol, region); 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. // Add a symbol for the shadow, but don't re-associate the member name.
Ok((shadow_symbol, Some(original_symbol))) Ok((shadow_symbol, Some(original_symbol)))
} else { } else {

View file

@ -1,4 +1,5 @@
use roc_builtins::std::StdLib; use roc_builtins::std::StdLib;
use roc_can::abilities::AbilitiesStore;
use roc_can::constraint::{Constraint, Constraints}; use roc_can::constraint::{Constraint, Constraints};
use roc_can::def::Declaration; use roc_can::def::Declaration;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
@ -100,10 +101,32 @@ pub enum ExposedModuleTypes {
pub fn constrain_module( pub fn constrain_module(
constraints: &mut Constraints, constraints: &mut Constraints,
abilities_store: &AbilitiesStore,
declarations: &[Declaration], declarations: &[Declaration],
home: ModuleId, home: ModuleId,
) -> Constraint { ) -> 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)] #[derive(Debug, Clone)]

View file

@ -188,9 +188,23 @@ pub fn constrain_pattern(
// Erroneous patterns don't add any constraints. // Erroneous patterns don't add any constraints.
} }
Identifier(symbol) | Shadowed(_, _, symbol) Identifier(symbol) | Shadowed(_, _, symbol) => {
// TODO(abilities): handle linking the member def to the specialization ident if could_be_a_tag_union(expected.get_type_ref()) {
| AbilityMemberSpecialization { 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, ident: symbol,
specializes: _, specializes: _,
} => { } => {

View file

@ -429,6 +429,13 @@ pub fn module_from_builtins<'ctx>(
} => { } => {
include_bytes!("../../../builtins/bitcode/builtins-i386.bc") 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!( _ => panic!(
"The zig builtins are not currently built for this target: {:?}", "The zig builtins are not currently built for this target: {:?}",
target target

View file

@ -13,10 +13,12 @@ roc_constrain= { path = "../constrain" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_reporting = { path = "../../reporting" }
[build-dependencies] [build-dependencies]
roc_load_internal = { path = "../load_internal" } roc_load_internal = { path = "../load_internal" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_reporting = { path = "../../reporting" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }

View file

@ -36,6 +36,7 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) {
&src_dir, &src_dir,
Default::default(), Default::default(),
target_info, target_info,
roc_reporting::report::RenderTarget::ColorTerminal,
); );
let module = res_module.unwrap(); let module = res_module.unwrap();

View file

@ -2,6 +2,7 @@ use bumpalo::Bump;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_constrain::module::ExposedByModule; use roc_constrain::module::ExposedByModule;
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_reporting::report::RenderTarget;
use roc_target::TargetInfo; use roc_target::TargetInfo;
use roc_types::subs::{Subs, Variable}; use roc_types::subs::{Subs, Variable};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -18,6 +19,7 @@ fn load<'a>(
exposed_types: ExposedByModule, exposed_types: ExposedByModule,
goal_phase: Phase, goal_phase: Phase,
target_info: TargetInfo, target_info: TargetInfo,
render: RenderTarget,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> { ) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let cached_subs = read_cached_subs(); let cached_subs = read_cached_subs();
@ -29,6 +31,7 @@ fn load<'a>(
goal_phase, goal_phase,
target_info, target_info,
cached_subs, cached_subs,
render,
) )
} }
@ -39,6 +42,7 @@ pub fn load_and_monomorphize_from_str<'a>(
src_dir: &Path, src_dir: &Path,
exposed_types: ExposedByModule, exposed_types: ExposedByModule,
target_info: TargetInfo, target_info: TargetInfo,
render: RenderTarget,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> { ) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
use LoadResult::*; use LoadResult::*;
@ -51,6 +55,7 @@ pub fn load_and_monomorphize_from_str<'a>(
exposed_types, exposed_types,
Phase::MakeSpecializations, Phase::MakeSpecializations,
target_info, target_info,
render,
)? { )? {
Monomorphized(module) => Ok(module), Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""), TypeChecked(_) => unreachable!(""),
@ -63,10 +68,11 @@ pub fn load_and_monomorphize<'a>(
src_dir: &Path, src_dir: &Path,
exposed_types: ExposedByModule, exposed_types: ExposedByModule,
target_info: TargetInfo, target_info: TargetInfo,
render: RenderTarget,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> { ) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
use LoadResult::*; use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename)?; let load_start = LoadStart::from_path(arena, filename, render)?;
match load( match load(
arena, arena,
@ -75,6 +81,7 @@ pub fn load_and_monomorphize<'a>(
exposed_types, exposed_types,
Phase::MakeSpecializations, Phase::MakeSpecializations,
target_info, target_info,
render,
)? { )? {
Monomorphized(module) => Ok(module), Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""), TypeChecked(_) => unreachable!(""),
@ -87,10 +94,11 @@ pub fn load_and_typecheck<'a>(
src_dir: &Path, src_dir: &Path,
exposed_types: ExposedByModule, exposed_types: ExposedByModule,
target_info: TargetInfo, target_info: TargetInfo,
render: RenderTarget,
) -> Result<LoadedModule, LoadingProblem<'a>> { ) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*; use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename)?; let load_start = LoadStart::from_path(arena, filename, render)?;
match load( match load(
arena, arena,
@ -99,6 +107,7 @@ pub fn load_and_typecheck<'a>(
exposed_types, exposed_types,
Phase::SolveTypes, Phase::SolveTypes,
target_info, target_info,
render,
)? { )? {
Monomorphized(_) => unreachable!(""), Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module), TypeChecked(module) => Ok(module),

View file

@ -33,4 +33,3 @@ tempfile = "3.2.0"
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"
maplit = "1.0.2" maplit = "1.0.2"
indoc = "1.0.3" indoc = "1.0.3"
strip-ansi-escapes = "0.1.1"

View file

@ -5,6 +5,7 @@ use crossbeam::deque::{Injector, Stealer, Worker};
use crossbeam::thread; use crossbeam::thread;
use parking_lot::Mutex; use parking_lot::Mutex;
use roc_builtins::std::borrow_stdlib; use roc_builtins::std::borrow_stdlib;
use roc_can::abilities::AbilitiesStore;
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
use roc_can::def::Declaration; use roc_can::def::Declaration;
use roc_can::module::{canonicalize_module_defs, Module}; use roc_can::module::{canonicalize_module_defs, Module};
@ -31,6 +32,7 @@ use roc_parse::ident::UppercaseIdent;
use roc_parse::module::module_defs; use roc_parse::module::module_defs;
use roc_parse::parser::{FileError, Parser, SyntaxError}; use roc_parse::parser::{FileError, Parser, SyntaxError};
use roc_region::all::{LineInfo, Loc, Region}; use roc_region::all::{LineInfo, Loc, Region};
use roc_reporting::report::RenderTarget;
use roc_solve::module::SolvedModule; use roc_solve::module::SolvedModule;
use roc_solve::solve; use roc_solve::solve;
use roc_target::TargetInfo; use roc_target::TargetInfo;
@ -347,6 +349,7 @@ pub struct LoadedModule {
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>, pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>, pub timings: MutMap<ModuleId, ModuleTiming>,
pub documentation: MutMap<ModuleId, ModuleDocumentation>, pub documentation: MutMap<ModuleId, ModuleDocumentation>,
pub abilities_store: AbilitiesStore,
} }
impl LoadedModule { impl LoadedModule {
@ -508,6 +511,7 @@ enum Msg<'a> {
decls: Vec<Declaration>, decls: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: MutMap<ModuleId, IdentIds>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
abilities_store: AbilitiesStore,
}, },
FinishedAllTypeChecking { FinishedAllTypeChecking {
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
@ -515,6 +519,7 @@ enum Msg<'a> {
exposed_aliases_by_symbol: MutMap<Symbol, (bool, Alias)>, exposed_aliases_by_symbol: MutMap<Symbol, (bool, Alias)>,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: MutMap<ModuleId, IdentIds>,
documentation: MutMap<ModuleId, ModuleDocumentation>, documentation: MutMap<ModuleId, ModuleDocumentation>,
abilities_store: AbilitiesStore,
}, },
FoundSpecializations { FoundSpecializations {
module_id: ModuleId, module_id: ModuleId,
@ -604,6 +609,8 @@ struct State<'a> {
// (Granted, this has not been attempted or measured!) // (Granted, this has not been attempted or measured!)
pub layout_caches: std::vec::Vec<LayoutCache<'a>>, 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 (used for builtin modules, could include packages in the future too)
cached_subs: CachedSubs, cached_subs: CachedSubs,
} }
@ -611,6 +618,7 @@ struct State<'a> {
type CachedSubs = Arc<Mutex<MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>>>; type CachedSubs = Arc<Mutex<MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>>>;
impl<'a> State<'a> { impl<'a> State<'a> {
#[allow(clippy::too_many_arguments)]
fn new( fn new(
root_id: ModuleId, root_id: ModuleId,
target_info: TargetInfo, target_info: TargetInfo,
@ -619,6 +627,7 @@ impl<'a> State<'a> {
arc_modules: Arc<Mutex<PackageModuleIds<'a>>>, arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>, cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget,
) -> Self { ) -> Self {
let arc_shorthands = Arc::new(Mutex::new(MutMap::default())); let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
@ -643,6 +652,7 @@ impl<'a> State<'a> {
timings: MutMap::default(), timings: MutMap::default(),
layout_caches: std::vec::Vec::with_capacity(num_cpus::get()), layout_caches: std::vec::Vec::with_capacity(num_cpus::get()),
cached_subs: Arc::new(Mutex::new(cached_subs)), cached_subs: Arc::new(Mutex::new(cached_subs)),
render,
} }
} }
} }
@ -824,6 +834,7 @@ pub fn load_and_typecheck_str<'a>(
src_dir: &Path, src_dir: &Path,
exposed_types: ExposedByModule, exposed_types: ExposedByModule,
target_info: TargetInfo, target_info: TargetInfo,
render: RenderTarget,
) -> Result<LoadedModule, LoadingProblem<'a>> { ) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*; use LoadResult::*;
@ -841,12 +852,19 @@ pub fn load_and_typecheck_str<'a>(
Phase::SolveTypes, Phase::SolveTypes,
target_info, target_info,
cached_subs, cached_subs,
render,
)? { )? {
Monomorphized(_) => unreachable!(""), Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module), TypeChecked(module) => Ok(module),
} }
} }
#[derive(Clone, Copy)]
pub enum PrintTarget {
ColorTerminal,
Generic,
}
pub struct LoadStart<'a> { pub struct LoadStart<'a> {
arc_modules: Arc<Mutex<PackageModuleIds<'a>>>, arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
@ -855,7 +873,11 @@ pub struct LoadStart<'a> {
} }
impl<'a> 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 arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0); let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids)); let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
@ -887,7 +909,12 @@ impl<'a> LoadStart<'a> {
// if parsing failed, this module did not add any identifiers // if parsing failed, this module did not add any identifiers
let root_exposed_ident_ids = IdentIds::exposed_builtins(0); 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)); return Err(LoadingProblem::FormattedReport(buf));
} }
Err(LoadingProblem::FileProblem { filename, error }) => { Err(LoadingProblem::FileProblem { filename, error }) => {
@ -995,6 +1022,7 @@ pub fn load<'a>(
goal_phase: Phase, goal_phase: Phase,
target_info: TargetInfo, target_info: TargetInfo,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>, cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> { ) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
// When compiling to wasm, we cannot spawn extra threads // When compiling to wasm, we cannot spawn extra threads
// so we have a single-threaded implementation // so we have a single-threaded implementation
@ -1007,6 +1035,7 @@ pub fn load<'a>(
goal_phase, goal_phase,
target_info, target_info,
cached_subs, cached_subs,
render,
) )
} else { } else {
load_multi_threaded( load_multi_threaded(
@ -1017,6 +1046,7 @@ pub fn load<'a>(
goal_phase, goal_phase,
target_info, target_info,
cached_subs, cached_subs,
render,
) )
} }
} }
@ -1031,12 +1061,14 @@ fn load_single_threaded<'a>(
goal_phase: Phase, goal_phase: Phase,
target_info: TargetInfo, target_info: TargetInfo,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>, cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> { ) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let LoadStart { let LoadStart {
arc_modules, arc_modules,
ident_ids_by_module, ident_ids_by_module,
root_id, root_id,
root_msg, root_msg,
..
} = load_start; } = load_start;
let (msg_tx, msg_rx) = bounded(1024); let (msg_tx, msg_rx) = bounded(1024);
@ -1053,6 +1085,7 @@ fn load_single_threaded<'a>(
arc_modules, arc_modules,
ident_ids_by_module, ident_ids_by_module,
cached_subs, cached_subs,
render,
); );
// We'll add tasks to this, and then worker threads will take tasks from it. // We'll add tasks to this, and then worker threads will take tasks from it.
@ -1115,6 +1148,7 @@ fn state_thread_step<'a>(
exposed_aliases_by_symbol, exposed_aliases_by_symbol,
dep_idents, dep_idents,
documentation, documentation,
abilities_store,
} => { } => {
// We're done! There should be no more messages pending. // We're done! There should be no more messages pending.
debug_assert!(msg_rx.is_empty()); debug_assert!(msg_rx.is_empty());
@ -1131,6 +1165,7 @@ fn state_thread_step<'a>(
exposed_vars_by_symbol, exposed_vars_by_symbol,
dep_idents, dep_idents,
documentation, documentation,
abilities_store,
); );
Ok(ControlFlow::Break(LoadResult::TypeChecked(typechecked))) Ok(ControlFlow::Break(LoadResult::TypeChecked(typechecked)))
@ -1153,8 +1188,12 @@ fn state_thread_step<'a>(
Msg::FailedToParse(problem) => { Msg::FailedToParse(problem) => {
let module_ids = (*state.arc_modules).lock().clone().into_module_ids(); let module_ids = (*state.arc_modules).lock().clone().into_module_ids();
let buf = let buf = to_parse_problem_report(
to_parse_problem_report(problem, module_ids, state.constrained_ident_ids); problem,
module_ids,
state.constrained_ident_ids,
state.render,
);
Err(LoadingProblem::FormattedReport(buf)) Err(LoadingProblem::FormattedReport(buf))
} }
msg => { msg => {
@ -1164,6 +1203,8 @@ fn state_thread_step<'a>(
let constrained_ident_ids = state.constrained_ident_ids.clone(); let constrained_ident_ids = state.constrained_ident_ids.clone();
let arc_modules = state.arc_modules.clone(); let arc_modules = state.arc_modules.clone();
let render = state.render;
let res_state = update( let res_state = update(
state, state,
msg, msg,
@ -1185,8 +1226,12 @@ fn state_thread_step<'a>(
.into_inner() .into_inner()
.into_module_ids(); .into_module_ids();
let buf = let buf = to_parse_problem_report(
to_parse_problem_report(problem, module_ids, constrained_ident_ids); problem,
module_ids,
constrained_ident_ids,
render,
);
Err(LoadingProblem::FormattedReport(buf)) Err(LoadingProblem::FormattedReport(buf))
} }
Err(e) => Err(e), Err(e) => Err(e),
@ -1210,12 +1255,14 @@ fn load_multi_threaded<'a>(
goal_phase: Phase, goal_phase: Phase,
target_info: TargetInfo, target_info: TargetInfo,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>, cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> { ) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let LoadStart { let LoadStart {
arc_modules, arc_modules,
ident_ids_by_module, ident_ids_by_module,
root_id, root_id,
root_msg, root_msg,
..
} = load_start; } = load_start;
let mut state = State::new( let mut state = State::new(
@ -1226,6 +1273,7 @@ fn load_multi_threaded<'a>(
arc_modules, arc_modules,
ident_ids_by_module, ident_ids_by_module,
cached_subs, cached_subs,
render,
); );
let (msg_tx, msg_rx) = bounded(1024); let (msg_tx, msg_rx) = bounded(1024);
@ -1746,6 +1794,7 @@ fn update<'a>(
decls, decls,
dep_idents, dep_idents,
mut module_timing, mut module_timing,
abilities_store,
} => { } => {
log!("solved types for {:?}", module_id); log!("solved types for {:?}", module_id);
module_timing.end_time = SystemTime::now(); module_timing.end_time = SystemTime::now();
@ -1798,6 +1847,7 @@ fn update<'a>(
exposed_aliases_by_symbol: solved_module.aliases, exposed_aliases_by_symbol: solved_module.aliases,
dep_idents, dep_idents,
documentation, documentation,
abilities_store,
}) })
.map_err(|_| LoadingProblem::MsgChannelDied)?; .map_err(|_| LoadingProblem::MsgChannelDied)?;
@ -2126,6 +2176,7 @@ fn finish(
exposed_vars_by_symbol: Vec<(Symbol, Variable)>, exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: MutMap<ModuleId, IdentIds>,
documentation: MutMap<ModuleId, ModuleDocumentation>, documentation: MutMap<ModuleId, ModuleDocumentation>,
abilities_store: AbilitiesStore,
) -> LoadedModule { ) -> LoadedModule {
let module_ids = Arc::try_unwrap(state.arc_modules) let module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids")) .unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
@ -2160,6 +2211,7 @@ fn finish(
sources, sources,
timings: state.timings, timings: state.timings,
documentation, documentation,
abilities_store,
} }
} }
@ -3102,6 +3154,10 @@ fn add_imports(
rigid_vars.extend(copied_import.rigid); rigid_vars.extend(copied_import.rigid);
rigid_vars.extend(copied_import.flex); 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); import_variables.extend(copied_import.registered);
def_types.push(( def_types.push((
@ -3119,6 +3175,7 @@ fn add_imports(
import_variables import_variables
} }
#[allow(clippy::complexity)]
fn run_solve_solve( fn run_solve_solve(
imported_builtins: Vec<Symbol>, imported_builtins: Vec<Symbol>,
exposed_for_module: ExposedForModule, exposed_for_module: ExposedForModule,
@ -3126,11 +3183,17 @@ fn run_solve_solve(
constraint: ConstraintSoa, constraint: ConstraintSoa,
mut var_store: VarStore, mut var_store: VarStore,
module: Module, module: Module,
) -> (Solved<Subs>, Vec<(Symbol, Variable)>, Vec<solve::TypeError>) { ) -> (
Solved<Subs>,
Vec<(Symbol, Variable)>,
Vec<solve::TypeError>,
AbilitiesStore,
) {
let Module { let Module {
exposed_symbols, exposed_symbols,
aliases, aliases,
rigid_variables, rigid_variables,
abilities_store,
.. ..
} = module; } = module;
@ -3155,12 +3218,13 @@ fn run_solve_solve(
solve_aliases.insert(*name, alias.clone()); solve_aliases.insert(*name, alias.clone());
} }
let (solved_subs, solved_env, problems) = roc_solve::module::run_solve( let (solved_subs, solved_env, problems, abilities_store) = roc_solve::module::run_solve(
&constraints, &constraints,
actual_constraint, actual_constraint,
rigid_variables, rigid_variables,
subs, subs,
solve_aliases, solve_aliases,
abilities_store,
); );
let solved_subs = if true { let solved_subs = if true {
@ -3179,7 +3243,12 @@ fn run_solve_solve(
.filter(|(k, _)| exposed_symbols.contains(k)) .filter(|(k, _)| exposed_symbols.contains(k))
.collect(); .collect();
(solved_subs, exposed_vars_by_symbol, problems) (
solved_subs,
exposed_vars_by_symbol,
problems,
abilities_store,
)
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -3203,7 +3272,7 @@ fn run_solve<'a>(
// TODO remove when we write builtins in roc // TODO remove when we write builtins in roc
let aliases = module.aliases.clone(); 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() { if module_id.is_builtin() {
match cached_subs.lock().remove(&module_id) { match cached_subs.lock().remove(&module_id) {
None => { None => {
@ -3217,9 +3286,13 @@ fn run_solve<'a>(
module, module,
) )
} }
Some((subs, exposed_vars_by_symbol)) => { 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 { } else {
run_solve_solve( run_solve_solve(
@ -3258,6 +3331,7 @@ fn run_solve<'a>(
dep_idents, dep_idents,
solved_module, solved_module,
module_timing, module_timing,
abilities_store,
} }
} }
@ -3385,8 +3459,12 @@ fn canonicalize_and_constrain<'a>(
let mut constraints = Constraints::new(); let mut constraints = Constraints::new();
let constraint = let constraint = constrain_module(
constrain_module(&mut constraints, &module_output.declarations, module_id); &mut constraints,
&module_output.scope.abilities_store,
&module_output.declarations,
module_id,
);
let after = roc_types::types::get_type_clone_count(); let after = roc_types::types::get_type_clone_count();
@ -3426,6 +3504,7 @@ fn canonicalize_and_constrain<'a>(
referenced_types: module_output.referenced_types, referenced_types: module_output.referenced_types,
aliases, aliases,
rigid_variables: module_output.rigid_variables, rigid_variables: module_output.rigid_variables,
abilities_store: module_output.scope.abilities_store,
}; };
let constrained_module = ConstrainedModule { let constrained_module = ConstrainedModule {
@ -3859,6 +3938,7 @@ fn add_def_to_module<'a>(
// This is a top-level definition, so it cannot capture anything // This is a top-level definition, so it cannot capture anything
captured_symbols: CapturedSymbols::None, captured_symbols: CapturedSymbols::None,
body, body,
body_var: def.expr_var,
// This is a 0-arity thunk, so it cannot be recursive // This is a 0-arity thunk, so it cannot be recursive
is_self_recursive: false, is_self_recursive: false,
}; };
@ -4068,6 +4148,7 @@ fn to_parse_problem_report<'a>(
problem: FileError<'a, SyntaxError<'a>>, problem: FileError<'a, SyntaxError<'a>>,
mut module_ids: ModuleIds, mut module_ids: ModuleIds,
all_ident_ids: MutMap<ModuleId, IdentIds>, all_ident_ids: MutMap<ModuleId, IdentIds>,
render: RenderTarget,
) -> String { ) -> String {
use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE}; use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE};
@ -4102,7 +4183,7 @@ fn to_parse_problem_report<'a>(
let mut buf = String::new(); let mut buf = String::new();
let palette = DEFAULT_PALETTE; let palette = DEFAULT_PALETTE;
report.render_color_terminal(&mut buf, &alloc, &palette); report.render(render, &mut buf, &alloc, &palette);
buf buf
} }

View file

@ -25,6 +25,7 @@ mod test_load {
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_region::all::LineInfo; use roc_region::all::LineInfo;
use roc_reporting::report::can_problem; use roc_reporting::report::can_problem;
use roc_reporting::report::RenderTarget;
use roc_reporting::report::RocDocAllocator; use roc_reporting::report::RocDocAllocator;
use roc_target::TargetInfo; use roc_target::TargetInfo;
use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use roc_types::pretty_print::{content_to_string, name_all_type_vars};
@ -41,7 +42,7 @@ mod test_load {
) -> Result<LoadedModule, LoadingProblem<'a>> { ) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*; 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( match roc_load_internal::file::load(
arena, arena,
@ -51,6 +52,7 @@ mod test_load {
Phase::SolveTypes, Phase::SolveTypes,
target_info, target_info,
Default::default(), // these tests will re-compile the builtins Default::default(), // these tests will re-compile the builtins
RenderTarget::Generic,
)? { )? {
Monomorphized(_) => unreachable!(""), Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module), TypeChecked(module) => Ok(module),
@ -424,12 +426,12 @@ mod test_load {
loaded_module, loaded_module,
hashmap! { hashmap! {
"floatTest" => "Float *", "floatTest" => "Float *",
"divisionFn" => "Float a, Float a -> Result (Float a) [ DivByZero ]*", "divisionFn" => "Float a, Float a -> Float a",
"divisionTest" => "Result (Float *) [ DivByZero ]*", "divisionTest" => "Float *",
"intTest" => "I64", "intTest" => "I64",
"x" => "Float *", "x" => "Float *",
"constantNum" => "Num *", "constantNum" => "Num *",
"divDep1ByDep2" => "Result (Float *) [ DivByZero ]*", "divDep1ByDep2" => "Float *",
"fromDep2" => "Float *", "fromDep2" => "Float *",
}, },
); );
@ -587,18 +589,18 @@ mod test_load {
report, report,
indoc!( 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 3 main = [
\u{1b}[31m^\u{1b}[0m ^
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. 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 Anything where there is an open and a close square bracket, and where
the elements of the list are separated by commas. 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"), Ok(_) => unreachable!("we expect failure here"),
@ -767,8 +769,6 @@ mod test_load {
]; ];
let err = multiple_modules(modules).unwrap_err(); let err = multiple_modules(modules).unwrap_err();
let err = strip_ansi_escapes::strip(err).unwrap();
let err = String::from_utf8(err).unwrap();
assert_eq!( assert_eq!(
err, err,
indoc!( indoc!(
@ -815,4 +815,65 @@ mod test_load {
err 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"),
}
}
} }

View file

@ -289,8 +289,10 @@ impl LowLevelWrapperType {
Symbol::NUM_LT => CanBeReplacedBy(NumLt), Symbol::NUM_LT => CanBeReplacedBy(NumLt),
Symbol::NUM_LTE => CanBeReplacedBy(NumLte), Symbol::NUM_LTE => CanBeReplacedBy(NumLte),
Symbol::NUM_COMPARE => CanBeReplacedBy(NumCompare), Symbol::NUM_COMPARE => CanBeReplacedBy(NumCompare),
Symbol::NUM_DIV_FLOAT => WrapperIsRequired, Symbol::NUM_DIV_FLOAT => CanBeReplacedBy(NumDivUnchecked),
Symbol::NUM_DIV_CEIL => WrapperIsRequired, Symbol::NUM_DIV_FLOAT_CHECKED => WrapperIsRequired,
Symbol::NUM_DIV_CEIL => CanBeReplacedBy(NumDivCeilUnchecked),
Symbol::NUM_DIV_CEIL_CHECKED => WrapperIsRequired,
Symbol::NUM_REM => WrapperIsRequired, Symbol::NUM_REM => WrapperIsRequired,
Symbol::NUM_IS_MULTIPLE_OF => CanBeReplacedBy(NumIsMultipleOf), Symbol::NUM_IS_MULTIPLE_OF => CanBeReplacedBy(NumIsMultipleOf),
Symbol::NUM_ABS => CanBeReplacedBy(NumAbs), Symbol::NUM_ABS => CanBeReplacedBy(NumAbs),

View file

@ -945,118 +945,126 @@ define_builtins! {
36 NUM_IS_POSITIVE: "isPositive" 36 NUM_IS_POSITIVE: "isPositive"
37 NUM_IS_NEGATIVE: "isNegative" 37 NUM_IS_NEGATIVE: "isNegative"
38 NUM_REM: "rem" 38 NUM_REM: "rem"
39 NUM_DIV_FLOAT: "div" 39 NUM_REM_CHECKED: "remChecked"
40 NUM_DIV_INT: "divFloor" 40 NUM_DIV_FLOAT: "div"
41 NUM_MOD_INT: "modInt" 41 NUM_DIV_FLOAT_CHECKED: "divChecked"
42 NUM_MOD_FLOAT: "modFloat" 42 NUM_DIV_FLOOR: "divFloor"
43 NUM_SQRT: "sqrt" 43 NUM_DIV_FLOOR_CHECKED: "divFloorChecked"
44 NUM_LOG: "log" 44 NUM_MOD_INT: "modInt"
45 NUM_ROUND: "round" 45 NUM_MOD_INT_CHECKED: "modIntChecked"
46 NUM_COMPARE: "compare" 46 NUM_MOD_FLOAT: "modFloat"
47 NUM_POW: "pow" 47 NUM_MOD_FLOAT_CHECKED: "modFloatChecked"
48 NUM_CEILING: "ceiling" 48 NUM_SQRT: "sqrt"
49 NUM_POW_INT: "powInt" 49 NUM_SQRT_CHECKED: "sqrtChecked"
50 NUM_FLOOR: "floor" 50 NUM_LOG: "log"
51 NUM_ADD_WRAP: "addWrap" 51 NUM_LOG_CHECKED: "logChecked"
52 NUM_ADD_CHECKED: "addChecked" 52 NUM_ROUND: "round"
53 NUM_ADD_SATURATED: "addSaturated" 53 NUM_COMPARE: "compare"
54 NUM_ATAN: "atan" 54 NUM_POW: "pow"
55 NUM_ACOS: "acos" 55 NUM_CEILING: "ceiling"
56 NUM_ASIN: "asin" 56 NUM_POW_INT: "powInt"
57 NUM_AT_SIGNED128: "@Signed128" 57 NUM_FLOOR: "floor"
58 NUM_SIGNED128: "Signed128" imported 58 NUM_ADD_WRAP: "addWrap"
59 NUM_AT_SIGNED64: "@Signed64" 59 NUM_ADD_CHECKED: "addChecked"
60 NUM_SIGNED64: "Signed64" imported 60 NUM_ADD_SATURATED: "addSaturated"
61 NUM_AT_SIGNED32: "@Signed32" 61 NUM_ATAN: "atan"
62 NUM_SIGNED32: "Signed32" imported 62 NUM_ACOS: "acos"
63 NUM_AT_SIGNED16: "@Signed16" 63 NUM_ASIN: "asin"
64 NUM_SIGNED16: "Signed16" imported 64 NUM_AT_SIGNED128: "@Signed128"
65 NUM_AT_SIGNED8: "@Signed8" 65 NUM_SIGNED128: "Signed128" imported
66 NUM_SIGNED8: "Signed8" imported 66 NUM_AT_SIGNED64: "@Signed64"
67 NUM_AT_UNSIGNED128: "@Unsigned128" 67 NUM_SIGNED64: "Signed64" imported
68 NUM_UNSIGNED128: "Unsigned128" imported 68 NUM_AT_SIGNED32: "@Signed32"
69 NUM_AT_UNSIGNED64: "@Unsigned64" 69 NUM_SIGNED32: "Signed32" imported
70 NUM_UNSIGNED64: "Unsigned64" imported 70 NUM_AT_SIGNED16: "@Signed16"
71 NUM_AT_UNSIGNED32: "@Unsigned32" 71 NUM_SIGNED16: "Signed16" imported
72 NUM_UNSIGNED32: "Unsigned32" imported 72 NUM_AT_SIGNED8: "@Signed8"
73 NUM_AT_UNSIGNED16: "@Unsigned16" 73 NUM_SIGNED8: "Signed8" imported
74 NUM_UNSIGNED16: "Unsigned16" imported 74 NUM_AT_UNSIGNED128: "@Unsigned128"
75 NUM_AT_UNSIGNED8: "@Unsigned8" 75 NUM_UNSIGNED128: "Unsigned128" imported
76 NUM_UNSIGNED8: "Unsigned8" imported 76 NUM_AT_UNSIGNED64: "@Unsigned64"
77 NUM_AT_BINARY64: "@Binary64" 77 NUM_UNSIGNED64: "Unsigned64" imported
78 NUM_BINARY64: "Binary64" imported 78 NUM_AT_UNSIGNED32: "@Unsigned32"
79 NUM_AT_BINARY32: "@Binary32" 79 NUM_UNSIGNED32: "Unsigned32" imported
80 NUM_BINARY32: "Binary32" imported 80 NUM_AT_UNSIGNED16: "@Unsigned16"
81 NUM_BITWISE_AND: "bitwiseAnd" 81 NUM_UNSIGNED16: "Unsigned16" imported
82 NUM_BITWISE_XOR: "bitwiseXor" 82 NUM_AT_UNSIGNED8: "@Unsigned8"
83 NUM_BITWISE_OR: "bitwiseOr" 83 NUM_UNSIGNED8: "Unsigned8" imported
84 NUM_SHIFT_LEFT: "shiftLeftBy" 84 NUM_AT_BINARY64: "@Binary64"
85 NUM_SHIFT_RIGHT: "shiftRightBy" 85 NUM_BINARY64: "Binary64" imported
86 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" 86 NUM_AT_BINARY32: "@Binary32"
87 NUM_SUB_WRAP: "subWrap" 87 NUM_BINARY32: "Binary32" imported
88 NUM_SUB_CHECKED: "subChecked" 88 NUM_BITWISE_AND: "bitwiseAnd"
89 NUM_SUB_SATURATED: "subSaturated" 89 NUM_BITWISE_XOR: "bitwiseXor"
90 NUM_MUL_WRAP: "mulWrap" 90 NUM_BITWISE_OR: "bitwiseOr"
91 NUM_MUL_CHECKED: "mulChecked" 91 NUM_SHIFT_LEFT: "shiftLeftBy"
92 NUM_INT: "Int" imported 92 NUM_SHIFT_RIGHT: "shiftRightBy"
93 NUM_FLOAT: "Float" imported 93 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy"
94 NUM_AT_NATURAL: "@Natural" 94 NUM_SUB_WRAP: "subWrap"
95 NUM_NATURAL: "Natural" imported 95 NUM_SUB_CHECKED: "subChecked"
96 NUM_NAT: "Nat" imported 96 NUM_SUB_SATURATED: "subSaturated"
97 NUM_INT_CAST: "intCast" 97 NUM_MUL_WRAP: "mulWrap"
98 NUM_IS_MULTIPLE_OF: "isMultipleOf" 98 NUM_MUL_CHECKED: "mulChecked"
99 NUM_AT_DECIMAL: "@Decimal" 99 NUM_INT: "Int" imported
100 NUM_DECIMAL: "Decimal" imported 100 NUM_FLOAT: "Float" imported
101 NUM_DEC: "Dec" imported // the Num.Dectype alias 101 NUM_AT_NATURAL: "@Natural"
102 NUM_BYTES_TO_U16: "bytesToU16" 102 NUM_NATURAL: "Natural" imported
103 NUM_BYTES_TO_U32: "bytesToU32" 103 NUM_NAT: "Nat" imported
104 NUM_CAST_TO_NAT: "#castToNat" 104 NUM_INT_CAST: "intCast"
105 NUM_DIV_CEIL: "divCeil" 105 NUM_IS_MULTIPLE_OF: "isMultipleOf"
106 NUM_TO_STR: "toStr" 106 NUM_AT_DECIMAL: "@Decimal"
107 NUM_MIN_I8: "minI8" 107 NUM_DECIMAL: "Decimal" imported
108 NUM_MAX_I8: "maxI8" 108 NUM_DEC: "Dec" imported // the Num.Dectype alias
109 NUM_MIN_U8: "minU8" 109 NUM_BYTES_TO_U16: "bytesToU16"
110 NUM_MAX_U8: "maxU8" 110 NUM_BYTES_TO_U32: "bytesToU32"
111 NUM_MIN_I16: "minI16" 111 NUM_CAST_TO_NAT: "#castToNat"
112 NUM_MAX_I16: "maxI16" 112 NUM_DIV_CEIL: "divCeil"
113 NUM_MIN_U16: "minU16" 113 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
114 NUM_MAX_U16: "maxU16" 114 NUM_TO_STR: "toStr"
115 NUM_MIN_I32: "minI32" 115 NUM_MIN_I8: "minI8"
116 NUM_MAX_I32: "maxI32" 116 NUM_MAX_I8: "maxI8"
117 NUM_MIN_U32: "minU32" 117 NUM_MIN_U8: "minU8"
118 NUM_MAX_U32: "maxU32" 118 NUM_MAX_U8: "maxU8"
119 NUM_MIN_I64: "minI64" 119 NUM_MIN_I16: "minI16"
120 NUM_MAX_I64: "maxI64" 120 NUM_MAX_I16: "maxI16"
121 NUM_MIN_U64: "minU64" 121 NUM_MIN_U16: "minU16"
122 NUM_MAX_U64: "maxU64" 122 NUM_MAX_U16: "maxU16"
123 NUM_MIN_I128: "minI128" 123 NUM_MIN_I32: "minI32"
124 NUM_MAX_I128: "maxI128" 124 NUM_MAX_I32: "maxI32"
125 NUM_TO_I8: "toI8" 125 NUM_MIN_U32: "minU32"
126 NUM_TO_I8_CHECKED: "toI8Checked" 126 NUM_MAX_U32: "maxU32"
127 NUM_TO_I16: "toI16" 127 NUM_MIN_I64: "minI64"
128 NUM_TO_I16_CHECKED: "toI16Checked" 128 NUM_MAX_I64: "maxI64"
129 NUM_TO_I32: "toI32" 129 NUM_MIN_U64: "minU64"
130 NUM_TO_I32_CHECKED: "toI32Checked" 130 NUM_MAX_U64: "maxU64"
131 NUM_TO_I64: "toI64" 131 NUM_MIN_I128: "minI128"
132 NUM_TO_I64_CHECKED: "toI64Checked" 132 NUM_MAX_I128: "maxI128"
133 NUM_TO_I128: "toI128" 133 NUM_TO_I8: "toI8"
134 NUM_TO_I128_CHECKED: "toI128Checked" 134 NUM_TO_I8_CHECKED: "toI8Checked"
135 NUM_TO_U8: "toU8" 135 NUM_TO_I16: "toI16"
136 NUM_TO_U8_CHECKED: "toU8Checked" 136 NUM_TO_I16_CHECKED: "toI16Checked"
137 NUM_TO_U16: "toU16" 137 NUM_TO_I32: "toI32"
138 NUM_TO_U16_CHECKED: "toU16Checked" 138 NUM_TO_I32_CHECKED: "toI32Checked"
139 NUM_TO_U32: "toU32" 139 NUM_TO_I64: "toI64"
140 NUM_TO_U32_CHECKED: "toU32Checked" 140 NUM_TO_I64_CHECKED: "toI64Checked"
141 NUM_TO_U64: "toU64" 141 NUM_TO_I128: "toI128"
142 NUM_TO_U64_CHECKED: "toU64Checked" 142 NUM_TO_I128_CHECKED: "toI128Checked"
143 NUM_TO_U128: "toU128" 143 NUM_TO_U8: "toU8"
144 NUM_TO_U128_CHECKED: "toU128Checked" 144 NUM_TO_U8_CHECKED: "toU8Checked"
145 NUM_TO_NAT: "toNat" 145 NUM_TO_U16: "toU16"
146 NUM_TO_NAT_CHECKED: "toNatChecked" 146 NUM_TO_U16_CHECKED: "toU16Checked"
147 NUM_TO_F32: "toF32" 147 NUM_TO_U32: "toU32"
148 NUM_TO_F32_CHECKED: "toF32Checked" 148 NUM_TO_U32_CHECKED: "toU32Checked"
149 NUM_TO_F64: "toF64" 149 NUM_TO_U64: "toU64"
150 NUM_TO_F64_CHECKED: "toF64Checked" 150 NUM_TO_U64_CHECKED: "toU64Checked"
151 NUM_TO_U128: "toU128"
152 NUM_TO_U128_CHECKED: "toU128Checked"
153 NUM_TO_NAT: "toNat"
154 NUM_TO_NAT_CHECKED: "toNatChecked"
155 NUM_TO_F32: "toF32"
156 NUM_TO_F32_CHECKED: "toF32Checked"
157 NUM_TO_F64: "toF64"
158 NUM_TO_F64_CHECKED: "toF64Checked"
} }
2 BOOL: "Bool" => { 2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View file

@ -195,6 +195,7 @@ pub struct PartialProc<'a> {
pub pattern_symbols: &'a [Symbol], pub pattern_symbols: &'a [Symbol],
pub captured_symbols: CapturedSymbols<'a>, pub captured_symbols: CapturedSymbols<'a>,
pub body: roc_can::expr::Expr, pub body: roc_can::expr::Expr,
pub body_var: Variable,
pub is_self_recursive: bool, pub is_self_recursive: bool,
} }
@ -224,6 +225,7 @@ impl<'a> PartialProc<'a> {
pattern_symbols, pattern_symbols,
captured_symbols, captured_symbols,
body: body.value, body: body.value,
body_var: ret_var,
is_self_recursive, is_self_recursive,
} }
} }
@ -240,6 +242,7 @@ impl<'a> PartialProc<'a> {
pattern_symbols: pattern_symbols.into_bump_slice(), pattern_symbols: pattern_symbols.into_bump_slice(),
captured_symbols: CapturedSymbols::None, captured_symbols: CapturedSymbols::None,
body: roc_can::expr::Expr::RuntimeError(error.value), body: roc_can::expr::Expr::RuntimeError(error.value),
body_var: ret_var,
is_self_recursive: false, is_self_recursive: false,
} }
} }
@ -902,6 +905,7 @@ impl<'a> Procs<'a> {
pattern_symbols, pattern_symbols,
captured_symbols, captured_symbols,
body: body.value, body: body.value,
body_var: ret_var,
is_self_recursive, is_self_recursive,
}; };
@ -939,6 +943,7 @@ impl<'a> Procs<'a> {
pattern_symbols, pattern_symbols,
captured_symbols, captured_symbols,
body: body.value, body: body.value,
body_var: ret_var,
is_self_recursive, is_self_recursive,
}; };
@ -2476,7 +2481,7 @@ fn specialize_external<'a>(
}; };
let body = partial_proc.body.clone(); 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 { match specialized {
SpecializedLayout::FunctionPointerBody { SpecializedLayout::FunctionPointerBody {

View file

@ -3,6 +3,7 @@ use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{default_hasher, MutMap}; use roc_collections::all::{default_hasher, MutMap};
use roc_error_macros::todo_abilities;
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_problem::can::RuntimeError; use roc_problem::can::RuntimeError;
@ -10,7 +11,7 @@ use roc_target::{PtrWidth, TargetInfo};
use roc_types::subs::{ use roc_types::subs::{
Content, FlatType, RecordFields, Subs, UnionTags, UnsortedUnionTags, Variable, 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::hash_map::{DefaultHasher, Entry};
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
@ -72,6 +73,7 @@ impl<'a> RawFunctionLayout<'a> {
use roc_types::subs::Content::*; use roc_types::subs::Content::*;
match content { match content {
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
RecursionVar { structure, .. } => { RecursionVar { structure, .. } => {
let structure_content = env.subs.get_content_without_compacting(structure); let structure_content = env.subs.get_content_without_compacting(structure);
Self::new_help(env, structure, *structure_content) Self::new_help(env, structure, *structure_content)
@ -952,6 +954,7 @@ impl<'a> Layout<'a> {
use roc_types::subs::Content::*; use roc_types::subs::Content::*;
match content { match content {
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)), FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
RecursionVar { structure, .. } => { RecursionVar { structure, .. } => {
let structure_content = env.subs.get_content_without_compacting(structure); let structure_content = env.subs.get_content_without_compacting(structure);
Self::new_help(env, structure, *structure_content) Self::new_help(env, structure, *structure_content)
@ -1683,7 +1686,11 @@ fn layout_from_flat_type<'a>(
// extract any values from the ext_var // extract any values from the ext_var
let mut pairs = Vec::with_capacity_in(fields.len(), arena); 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 // drop optional fields
let var = match field { let var = match field {
RecordField::Optional(_) => continue, 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.) // (e.g. for (5 + 5) assume both 5s are 64-bit integers.)
Ok(Layout::default_integer()) Ok(Layout::default_integer())
} }
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
Structure(Apply(symbol, args)) => match *symbol { Structure(Apply(symbol, args)) => match *symbol {
// Ints // Ints
Symbol::NUM_NAT => Ok(Layout::usize(target_info)), Symbol::NUM_NAT => Ok(Layout::usize(target_info)),

View file

@ -137,8 +137,10 @@ impl FunctionLayout {
use LayoutError::*; use LayoutError::*;
match content { match content {
Content::FlexVar(_) => Err(UnresolvedVariable(var)), Content::FlexVar(_)
Content::RigidVar(_) => Err(UnresolvedVariable(var)), | Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)),
Content::RecursionVar { .. } => Err(TypeError(())), Content::RecursionVar { .. } => Err(TypeError(())),
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual), Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual),
@ -243,8 +245,10 @@ impl LambdaSet {
use LayoutError::*; use LayoutError::*;
match content { match content {
Content::FlexVar(_) => Err(UnresolvedVariable(var)), Content::FlexVar(_)
Content::RigidVar(_) => Err(UnresolvedVariable(var)), | Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)),
Content::RecursionVar { .. } => { Content::RecursionVar { .. } => {
unreachable!("lambda sets cannot currently be recursive") unreachable!("lambda sets cannot currently be recursive")
} }
@ -627,8 +631,10 @@ impl Layout {
use LayoutError::*; use LayoutError::*;
match content { match content {
Content::FlexVar(_) => Err(UnresolvedVariable(var)), Content::FlexVar(_)
Content::RigidVar(_) => Err(UnresolvedVariable(var)), | Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)),
Content::RecursionVar { Content::RecursionVar {
structure, structure,
opt_name: _, opt_name: _,

View file

@ -2,7 +2,9 @@ use crate::ast::{
AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Has, Pattern, Spaceable, AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Has, Pattern, Spaceable,
TypeAnnotation, TypeDef, TypeHeader, ValueDef, 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::ident::{lowercase_ident, parse_ident, Ident};
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
@ -2591,14 +2593,15 @@ fn record_help<'a>(
and!( and!(
trailing_sep_by0( trailing_sep_by0(
word1(b',', ERecord::End), word1(b',', ERecord::End),
space0_around_ee( space0_before_optional_after(
loc!(record_field_help(min_indent)), loc!(record_field_help(min_indent)),
min_indent, min_indent,
ERecord::IndentEnd, ERecord::IndentEnd,
ERecord::IndentEnd ERecord::IndentEnd
), ),
), ),
space0_e(min_indent, ERecord::IndentEnd) // Allow outdented closing braces
space0_e(0, ERecord::IndentEnd)
), ),
word1(b'}', ERecord::End) word1(b'}', ERecord::End)
) )

View file

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

View file

@ -0,0 +1,4 @@
x = foo (baz {
bar: blah
})
x

View file

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

View file

@ -0,0 +1,4 @@
a = [
1, 2, 3
]
a

View file

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

View file

@ -0,0 +1,4 @@
x = foo {
bar: blah
}
x

View file

@ -218,6 +218,9 @@ mod test_parse {
pass/opaque_reference_pattern.expr, pass/opaque_reference_pattern.expr,
pass/opaque_reference_pattern_with_arguments.expr, pass/opaque_reference_pattern_with_arguments.expr,
pass/ops_with_newlines.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/packed_singleton_list.expr,
pass/parenthetical_apply.expr, pass/parenthetical_apply.expr,
pass/parenthetical_basic_field.expr, pass/parenthetical_basic_field.expr,

View file

@ -119,6 +119,13 @@ pub enum Problem {
ability: Symbol, ability: Symbol,
region: Region, 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 { AbilityMemberBindsExternalAbility {
member: Symbol, member: Symbol,
ability: Symbol, ability: Symbol,

View file

@ -7,6 +7,7 @@ edition = "2018"
[dependencies] [dependencies]
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
@ -22,6 +23,7 @@ roc_problem = { path = "../problem" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
roc_reporting = { path = "../../reporting" }
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"
indoc = "1.0.3" indoc = "1.0.3"
tempfile = "3.2.0" tempfile = "3.2.0"

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

View file

@ -2,5 +2,6 @@
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
mod ability;
pub mod module; pub mod module;
pub mod solve; pub mod solve;

View file

@ -1,4 +1,5 @@
use crate::solve::{self, Aliases}; use crate::solve::{self, Aliases};
use roc_can::abilities::AbilitiesStore;
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
use roc_can::module::RigidVariables; use roc_can::module::RigidVariables;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
@ -32,13 +33,23 @@ pub fn run_solve(
rigid_variables: RigidVariables, rigid_variables: RigidVariables,
mut subs: Subs, mut subs: Subs,
mut aliases: Aliases, 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(); let env = solve::Env::default();
for (var, name) in rigid_variables.named { for (var, name) in rigid_variables.named {
subs.rigid_var(var, name); 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 { for var in rigid_variables.wildcards {
subs.rigid_var(var, "*".into()); subs.rigid_var(var, "*".into());
} }
@ -55,9 +66,10 @@ pub fn run_solve(
subs, subs,
&mut aliases, &mut aliases,
&constraint, &constraint,
&mut abilities_store,
); );
(solved_subs, solved_env, problems) (solved_subs, solved_env, problems, abilities_store)
} }
pub fn exposed_types_storage_subs( pub fn exposed_types_storage_subs(

View file

@ -1,4 +1,6 @@
use crate::ability::{AbilityImplError, DeferredMustImplementAbility};
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::abilities::{AbilitiesStore, MemberSpecialization};
use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::{Constraints, LetConstraint}; use roc_can::constraint::{Constraints, LetConstraint};
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
@ -14,7 +16,7 @@ use roc_types::subs::{
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
use roc_types::types::{ use roc_types::types::{
gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, PatternCategory, gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, PatternCategory,
TypeExtension, Reason, TypeExtension,
}; };
use roc_unify::unify::{unify, Mode, Unified::*}; 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 // 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. // 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)] #[derive(PartialEq, Debug, Clone)]
pub enum TypeError { pub enum TypeError {
BadExpr(Region, Category, ErrorType, Expected<ErrorType>), BadExpr(Region, Category, ErrorType, Expected<ErrorType>),
@ -75,6 +86,19 @@ pub enum TypeError {
CircularType(Region, Symbol, ErrorType), CircularType(Region, Symbol, ErrorType),
BadType(roc_types::types::Problem), BadType(roc_types::types::Problem),
UnexposedLookup(Symbol), UnexposedLookup(Symbol),
IncompleteAbilityImplementation(IncompleteAbilityImplementation),
BadExprMissingAbility(
Region,
Category,
ErrorType,
Vec<IncompleteAbilityImplementation>,
),
BadPatternMissingAbility(
Region,
PatternCategory,
ErrorType,
Vec<IncompleteAbilityImplementation>,
),
} }
use roc_types::types::Alias; use roc_types::types::Alias;
@ -515,8 +539,17 @@ pub fn run(
mut subs: Subs, mut subs: Subs,
aliases: &mut Aliases, aliases: &mut Aliases,
constraint: &Constraint, constraint: &Constraint,
abilities_store: &mut AbilitiesStore,
) -> (Solved<Subs>, Env) { ) -> (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) (Solved(subs), env)
} }
@ -529,6 +562,7 @@ pub fn run_in_place(
subs: &mut Subs, subs: &mut Subs,
aliases: &mut Aliases, aliases: &mut Aliases,
constraint: &Constraint, constraint: &Constraint,
abilities_store: &mut AbilitiesStore,
) -> Env { ) -> Env {
let mut pools = Pools::default(); let mut pools = Pools::default();
@ -540,6 +574,8 @@ pub fn run_in_place(
let arena = Bump::new(); let arena = Bump::new();
let mut deferred_must_implement_abilities = DeferredMustImplementAbility::default();
let state = solve( let state = solve(
&arena, &arena,
constraints, constraints,
@ -551,8 +587,14 @@ pub fn run_in_place(
aliases, aliases,
subs, subs,
constraint, 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 state.env
} }
@ -604,6 +646,8 @@ fn solve(
aliases: &mut Aliases, aliases: &mut Aliases,
subs: &mut Subs, subs: &mut Subs,
constraint: &Constraint, constraint: &Constraint,
abilities_store: &mut AbilitiesStore,
deferred_must_implement_abilities: &mut DeferredMustImplementAbility,
) -> State { ) -> State {
let initial = Work::Constraint { let initial = Work::Constraint {
env, env,
@ -656,6 +700,19 @@ fn solve(
let mut new_env = env.clone(); let mut new_env = env.clone();
for (symbol, loc_var) in local_def_vars.iter() { 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); new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
} }
@ -752,6 +809,19 @@ fn solve(
let mut new_env = env.clone(); let mut new_env = env.clone();
for (symbol, loc_var) in local_def_vars.iter() { 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); 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()); let expected = type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
match unify(subs, actual, expected, Mode::EQ) { match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => { Success {
vars,
must_implement_ability,
} => {
introduce(subs, rank, pools, &vars); 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 state
} }
Failure(vars, actual_type, expected_type) => { Failure(vars, actual_type, expected_type, _bad_impls) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr( let problem = TypeError::BadExpr(
@ -838,12 +917,16 @@ fn solve(
let target = *target; let target = *target;
match unify(subs, actual, target, Mode::EQ) { match unify(subs, actual, target, Mode::EQ) {
Success(vars) => { Success {
vars,
// ERROR NOT REPORTED
must_implement_ability: _,
} => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
state state
} }
Failure(vars, _actual_type, _expected_type) => { Failure(vars, _actual_type, _expected_type, _bad_impls) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
// ERROR NOT REPORTED // ERROR NOT REPORTED
@ -890,13 +973,26 @@ fn solve(
type_to_var(subs, rank, pools, aliases, expectation.get_type_ref()); type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
match unify(subs, actual, expected, Mode::EQ) { match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => { Success {
vars,
must_implement_ability,
} => {
introduce(subs, rank, pools, &vars); 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 state
} }
Failure(vars, actual_type, expected_type) => { Failure(vars, actual_type, expected_type, _bad_impls) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr( let problem = TypeError::BadExpr(
@ -954,12 +1050,21 @@ fn solve(
}; };
match unify(subs, actual, expected, mode) { match unify(subs, actual, expected, mode) {
Success(vars) => { Success {
vars,
must_implement_ability,
} => {
introduce(subs, rank, pools, &vars); 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 state
} }
Failure(vars, actual_type, expected_type) => { Failure(vars, actual_type, expected_type, _bad_impls) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
let problem = TypeError::BadPattern( let problem = TypeError::BadPattern(
@ -1123,12 +1228,25 @@ fn solve(
let includes = type_to_var(subs, rank, pools, aliases, &tag_ty); let includes = type_to_var(subs, rank, pools, aliases, &tag_ty);
match unify(subs, actual, includes, Mode::PRESENT) { match unify(subs, actual, includes, Mode::PRESENT) {
Success(vars) => { Success {
vars,
must_implement_ability,
} => {
introduce(subs, rank, pools, &vars); 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 state
} }
Failure(vars, actual_type, expected_to_include_type) => { Failure(vars, actual_type, expected_to_include_type, _bad_impls) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
let problem = TypeError::BadPattern( let problem = TypeError::BadPattern(
@ -1156,6 +1274,137 @@ fn solve(
state 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)] #[derive(Debug)]
enum LocalDefVarsVec<T> { enum LocalDefVarsVec<T> {
Stack(arrayvec::ArrayVec<T, 32>), Stack(arrayvec::ArrayVec<T, 32>),
@ -1288,7 +1537,7 @@ impl RegisterVariable {
use RegisterVariable::*; use RegisterVariable::*;
match typ { match typ {
Variable(var) => Direct(*var), Type::Variable(var) => Direct(*var),
EmptyRec => Direct(Variable::EMPTY_RECORD), EmptyRec => Direct(Variable::EMPTY_RECORD),
EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION), EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION),
Type::DelayedAlias(AliasCommon { symbol, .. }) => { Type::DelayedAlias(AliasCommon { symbol, .. }) => {
@ -2180,7 +2429,7 @@ fn adjust_rank_content(
use roc_types::subs::FlatType::*; use roc_types::subs::FlatType::*;
match content { match content {
FlexVar(_) | RigidVar(_) | Error => group_rank, FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) | Error => group_rank,
RecursionVar { .. } => group_rank, RecursionVar { .. } => group_rank,
@ -2396,7 +2645,14 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
desc.mark = Mark::NONE; desc.mark = Mark::NONE;
desc.copy = OptVariable::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, .. } => { RecursionVar { structure, .. } => {
stack.push(*structure); stack.push(*structure);
@ -2684,7 +2940,7 @@ fn deep_copy_var_help(
copy copy
} }
FlexVar(_) | Error => copy, FlexVar(_) | FlexAbleVar(_, _) | Error => copy,
RecursionVar { RecursionVar {
opt_name, opt_name,
@ -2709,6 +2965,12 @@ fn deep_copy_var_help(
copy copy
} }
RigidAbleVar(name, ability) => {
subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability)));
copy
}
Alias(symbol, arguments, real_type_var, kind) => { Alias(symbol, arguments, real_type_var, kind) => {
let new_variables = let new_variables =
SubsSlice::reserve_into_subs(subs, arguments.all_variables_len as _); SubsSlice::reserve_into_subs(subs, arguments.all_variables_len as _);

View file

@ -10,20 +10,12 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod solve_expr { mod solve_expr {
use crate::helpers::with_larger_debug_stack; use crate::helpers::with_larger_debug_stack;
use roc_load::LoadedModule;
use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use roc_types::pretty_print::{content_to_string, name_all_type_vars};
// HELPERS // HELPERS
fn infer_eq_help( fn run_load_and_infer(src: &str) -> Result<LoadedModule, std::io::Error> {
src: &str,
) -> Result<
(
Vec<roc_solve::solve::TypeError>,
Vec<roc_problem::can::Problem>,
String,
),
std::io::Error,
> {
use bumpalo::Bump; use bumpalo::Bump;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
@ -58,6 +50,7 @@ mod solve_expr {
dir.path(), dir.path(),
exposed_types, exposed_types,
roc_target::TargetInfo::default_x86_64(), roc_target::TargetInfo::default_x86_64(),
roc_reporting::report::RenderTarget::Generic,
); );
dir.close()?; dir.close()?;
@ -66,8 +59,19 @@ mod solve_expr {
}; };
let loaded = loaded.expect("failed to load module"); 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 { let LoadedModule {
module_id: home, module_id: home,
mut can_problems, mut can_problems,
@ -76,7 +80,7 @@ mod solve_expr {
mut solved, mut solved,
exposed_to_host, exposed_to_host,
.. ..
} = loaded; } = run_load_and_infer(src)?;
let mut can_problems = can_problems.remove(&home).unwrap_or_default(); let mut can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default(); let type_problems = type_problems.remove(&home).unwrap_or_default();
@ -155,6 +159,59 @@ mod solve_expr {
assert_eq!(actual, expected.to_string()); 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] #[test]
fn int_literal() { fn int_literal() {
infer_eq("5", "Num *"); infer_eq("5", "Num *");
@ -2340,7 +2397,7 @@ mod solve_expr {
{ numIdentity, x : numIdentity 42, y } { 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 * }",
); );
} }
@ -3296,6 +3353,30 @@ mod solve_expr {
); );
} }
#[test]
fn div() {
infer_eq_without_problem(
indoc!(
r#"
Num.div
"#
),
"Float a, Float a -> Float a",
)
}
#[test]
fn div_checked() {
infer_eq_without_problem(
indoc!(
r#"
Num.divChecked
"#
),
"Float a, Float a -> Result (Float a) [ DivByZero ]*",
)
}
#[test] #[test]
fn div_ceil() { fn div_ceil() {
infer_eq_without_problem( infer_eq_without_problem(
@ -3304,22 +3385,46 @@ mod solve_expr {
Num.divCeil Num.divCeil
"# "#
), ),
"Int a, Int a -> Int a",
);
}
#[test]
fn div_ceil_checked() {
infer_eq_without_problem(
indoc!(
r#"
Num.divCeilChecked
"#
),
"Int a, Int a -> Result (Int a) [ DivByZero ]*", "Int a, Int a -> Result (Int a) [ DivByZero ]*",
); );
} }
#[test] #[test]
fn pow_int() { fn div_floor() {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
Num.powInt Num.divFloor
"# "#
), ),
"Int a, Int a -> Int a", "Int a, Int a -> Int a",
); );
} }
#[test]
fn div_floor_checked() {
infer_eq_without_problem(
indoc!(
r#"
Num.divFloorChecked
"#
),
"Int a, Int a -> Result (Int a) [ DivByZero ]*",
);
}
#[test] #[test]
fn atan() { fn atan() {
infer_eq_without_problem( infer_eq_without_problem(
@ -3715,7 +3820,7 @@ mod solve_expr {
negatePoint { x: 1, y: 2.1, z: 0x3 } negatePoint { x: 1, y: 2.1, z: 0x3 }
"# "#
), ),
"{ x : Num a, y : F64, z : Int * }", "{ x : Num a, y : Float *, z : Int * }",
); );
} }
@ -3732,7 +3837,7 @@ mod solve_expr {
{ a, b } { 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 } }",
); );
} }
@ -5662,4 +5767,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 }", "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",
)
}
} }

View file

@ -723,11 +723,24 @@ fn gen_wrap_add_nums() {
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
fn gen_div_f64() { fn gen_div_f64() {
// FIXME this works with normal types, but fails when checking uniqueness types
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
when 48 / 2 is 48 / 2
"#
),
24.0,
f64
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_checked_f64() {
assert_evals_to!(
indoc!(
r#"
when Num.divChecked 48 2 is
Ok val -> val Ok val -> val
Err _ -> -1 Err _ -> -1
"# "#
@ -736,6 +749,23 @@ fn gen_div_f64() {
f64 f64
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_checked_by_zero_f64() {
assert_evals_to!(
indoc!(
r#"
when Num.divChecked 47 0 is
Ok val -> val
Err _ -> -1
"#
),
-1.0,
f64
);
}
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
fn gen_div_dec() { fn gen_div_dec() {
@ -748,7 +778,27 @@ fn gen_div_dec() {
y : Dec y : Dec
y = 3 y = 3
when x / y is x / y
"#
),
RocDec::from_str_to_i128_unsafe("3.333333333333333333"),
i128
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_checked_dec() {
assert_evals_to!(
indoc!(
r#"
x : Dec
x = 10
y : Dec
y = 3
when Num.divChecked x y is
Ok val -> val Ok val -> val
Err _ -> -1 Err _ -> -1
"# "#
@ -757,6 +807,27 @@ fn gen_div_dec() {
i128 i128
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_checked_by_zero_dec() {
assert_evals_to!(
indoc!(
r#"
x : Dec
x = 10
y : Dec
y = 0
when Num.divChecked x y is
Ok val -> val
Err _ -> -1
"#
),
RocDec::from_str_to_i128_unsafe("-1"),
i128
);
}
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
@ -965,7 +1036,21 @@ fn gen_div_i64() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
when 1000 // 10 is 1000 // 10
"#
),
100,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_checked_i64() {
assert_evals_to!(
indoc!(
r#"
when Num.divFloorChecked 1000 10 is
Ok val -> val Ok val -> val
Err _ -> -1 Err _ -> -1
"# "#
@ -977,11 +1062,11 @@ fn gen_div_i64() {
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
fn gen_div_by_zero_i64() { fn gen_div_checked_by_zero_i64() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
when 1000 // 0 is when Num.divFloorChecked 1000 0 is
Err DivByZero -> 99 Err DivByZero -> 99
_ -> -24 _ -> -24
"# "#
@ -2660,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] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn u8_addition_greater_than_i8() { fn u8_addition_greater_than_i8() {

View file

@ -54,6 +54,7 @@ pub fn helper(
src_dir, src_dir,
Default::default(), Default::default(),
roc_target::TargetInfo::default_x86_64(), roc_target::TargetInfo::default_x86_64(),
roc_reporting::report::RenderTarget::ColorTerminal,
); );
let mut loaded = loaded.expect("failed to load module"); let mut loaded = loaded.expect("failed to load module");

View file

@ -7,6 +7,7 @@ use roc_collections::all::MutSet;
use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use roc_region::all::LineInfo; use roc_region::all::LineInfo;
use roc_reporting::report::RenderTarget;
use target_lexicon::Triple; use target_lexicon::Triple;
fn promote_expr_to_module(src: &str) -> String { fn promote_expr_to_module(src: &str) -> String {
@ -57,6 +58,7 @@ fn create_llvm_module<'a>(
src_dir, src_dir,
Default::default(), Default::default(),
target_info, target_info,
RenderTarget::ColorTerminal,
); );
let mut loaded = match loaded { let mut loaded = match loaded {

View file

@ -91,6 +91,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>(
src_dir, src_dir,
Default::default(), Default::default(),
roc_target::TargetInfo::default_wasm32(), roc_target::TargetInfo::default_wasm32(),
roc_reporting::report::RenderTarget::ColorTerminal,
); );
let loaded = loaded.expect("failed to load module"); let loaded = loaded.expect("failed to load module");

View file

@ -17,6 +17,7 @@ roc_load = { path = "../load" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
roc_reporting = { path = "../../reporting" }
test_mono_macros = { path = "../test_mono_macros" } test_mono_macros = { path = "../test_mono_macros" }
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }

View file

@ -101,6 +101,7 @@ fn compiles_to_ir(test_name: &str, src: &str) {
src_dir, src_dir,
Default::default(), Default::default(),
TARGET_INFO, TARGET_INFO,
roc_reporting::report::RenderTarget::Generic,
); );
let mut loaded = match loaded { let mut loaded = match loaded {
@ -274,7 +275,7 @@ fn ir_round() {
#[mono_test] #[mono_test]
fn ir_when_idiv() { fn ir_when_idiv() {
r#" r#"
when 1000 // 10 is when Num.divFloorChecked 1000 10 is
Ok val -> val Ok val -> val
Err _ -> -1 Err _ -> -1
"# "#

View file

@ -116,7 +116,7 @@ fn find_names_needed(
} }
match &subs.get_content_without_compacting(variable).clone() { 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); let root = subs.get_root_key_without_compacting(variable);
// If this var is *not* its own root, then the // If this var is *not* its own root, then the
@ -139,7 +139,8 @@ fn find_names_needed(
opt_name: Some(name_index), 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! // This root already has a name. Nothing more to do here!
// User-defined names are already taken. // 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(); let name = subs.field_names[name_index.index as usize].clone();
names_taken.insert(name); names_taken.insert(name);
} }
RigidVar(name_index) => { RigidVar(name_index) | RigidAbleVar(name_index, _) => {
// User-defined names are already taken. // User-defined names are already taken.
// We must not accidentally generate names that collide with them! // We must not accidentally generate names that collide with them!
let name = subs.field_names[name_index.index as usize].clone(); 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( pub fn content_to_string(
content: &Content, content: &Content,
subs: &Subs, subs: &Subs,
@ -297,8 +303,16 @@ pub fn content_to_string(
) -> String { ) -> String {
let mut buf = String::new(); let mut buf = String::new();
let env = Env { home, interns }; 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 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) 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::*; use crate::subs::Content::*;
match 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]; let name = &subs.field_names[name_index.index as usize];
buf.push_str(name.as_str()) 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 { RecursionVar { opt_name, .. } => match opt_name {
Some(name_index) => { Some(name_index) => {
let name = &subs.field_names[name_index.index as usize]; 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), 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) => { Alias(symbol, args, _actual, _kind) => {
let write_parens = parens == Parens::InTypeParam && !args.is_empty(); 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 => { Symbol::NUM_INTEGER => {
write_integer( write_integer(
env, env,
ctx,
get_single_arg(subs, &args), get_single_arg(subs, &args),
subs, subs,
buf, buf,
@ -353,17 +387,25 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
false, 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, { _ => write_parens!(write_parens, buf, {
buf.push_str("Num "); buf.push_str("Num ");
write_content(env, content, subs, buf, parens); write_content(env, ctx, content, subs, buf, parens);
}), }),
}, },
_ => write_parens!(write_parens, buf, { _ => write_parens!(write_parens, buf, {
buf.push_str("Num "); 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 => { Symbol::NUM_INT => {
let content = get_single_arg(subs, args); 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 => { Symbol::NUM_FLOAT => write_float(
debug_assert_eq!(args.len(), 1); env,
ctx,
let arg_var_index = args get_single_arg(subs, args),
.into_iter() subs,
.next() buf,
.expect("Num was not applied to a type argument!"); parens,
let arg_var = subs[arg_var_index]; write_parens,
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);
}),
}
}
_ => write_parens!(write_parens, buf, { _ => write_parens!(write_parens, buf, {
write_symbol(env, *symbol, 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(' '); buf.push(' ');
write_content( write_content(
env, env,
ctx,
subs.get_content_without_compacting(var), subs.get_content_without_compacting(var),
subs, subs,
buf, buf,
@ -414,7 +446,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
if false { if false {
buf.push_str("[[ but really "); buf.push_str("[[ but really ");
let content = subs.get_content_without_compacting(*_actual); 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("]]"); 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( RangedNumber(typ, _range_vars) => write_content(
env, env,
ctx,
subs.get_content_without_compacting(*typ), subs.get_content_without_compacting(*typ),
subs, subs,
buf, 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, env: &Env,
ctx: &mut Context<'a>,
content: &Content, 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, buf: &mut String,
parens: Parens, parens: Parens,
write_parens: bool, write_parens: bool,
@ -454,7 +509,7 @@ fn write_integer(
)* )*
actual => { actual => {
buf.push_str("Int "); 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>( fn write_ext_content<'a>(
env: &Env, env: &Env,
ctx: &mut Context<'a>,
subs: &'a Subs, subs: &'a Subs,
buf: &mut String, buf: &mut String,
ext_content: ExtContent<'a>, ext_content: ExtContent<'a>,
@ -508,12 +564,13 @@ fn write_ext_content<'a>(
// //
// e.g. the "*" at the end of `{ x: I64 }*` // e.g. the "*" at the end of `{ x: I64 }*`
// or the "r" at the end of `{ x: I64 }r` // 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>( fn write_sorted_tags2<'a>(
env: &Env, env: &Env,
ctx: &mut Context<'a>,
subs: &'a Subs, subs: &'a Subs,
buf: &mut String, buf: &mut String,
tags: &UnionTags, tags: &UnionTags,
@ -546,6 +603,7 @@ fn write_sorted_tags2<'a>(
buf.push(' '); buf.push(' ');
write_content( write_content(
env, env,
ctx,
subs.get_content_without_compacting(*var), subs.get_content_without_compacting(*var),
subs, subs,
buf, buf,
@ -559,6 +617,7 @@ fn write_sorted_tags2<'a>(
fn write_sorted_tags<'a>( fn write_sorted_tags<'a>(
env: &Env, env: &Env,
ctx: &mut Context<'a>,
subs: &'a Subs, subs: &'a Subs,
buf: &mut String, buf: &mut String,
tags: &MutMap<TagName, Vec<Variable>>, tags: &MutMap<TagName, Vec<Variable>>,
@ -603,6 +662,7 @@ fn write_sorted_tags<'a>(
buf.push(' '); buf.push(' ');
write_content( write_content(
env, env,
ctx,
subs.get_content_without_compacting(*var), subs.get_content_without_compacting(*var),
subs, subs,
buf, buf,
@ -614,18 +674,37 @@ fn write_sorted_tags<'a>(
ExtContent::from_var(subs, ext_var) 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::*; use crate::subs::FlatType::*;
match flat_type { match flat_type {
Apply(symbol, args) => { Apply(symbol, args) => write_apply(
write_apply(env, *symbol, subs.get_subs_slice(*args), subs, buf, parens) env,
} ctx,
*symbol,
subs.get_subs_slice(*args),
subs,
buf,
parens,
),
EmptyRecord => buf.push_str(EMPTY_RECORD), EmptyRecord => buf.push_str(EMPTY_RECORD),
EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION), EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION),
Func(args, _closure, ret) => { Func(args, _closure, ret) => write_fn(
write_fn(env, subs.get_subs_slice(*args), *ret, subs, buf, parens) env,
} ctx,
subs.get_subs_slice(*args),
*ret,
subs,
buf,
parens,
),
Record(fields, ext_var) => { Record(fields, ext_var) => {
use crate::types::{gather_fields, RecordStructure}; 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( write_content(
env, env,
ctx,
subs.get_content_without_compacting(var), subs.get_content_without_compacting(var),
subs, subs,
buf, 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 }*` // e.g. the "*" at the end of `{ x: I64 }*`
// or the "r" at the end of `{ x: I64 }r` // 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) => { TagUnion(tags, ext_var) => {
buf.push_str("[ "); 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(" ]"); 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) => { 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(); let mut tags: MutMap<TagName, _> = MutMap::default();
tags.insert(subs[*tag_name].clone(), vec![]); 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(" ]"); 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) => { RecursiveTagUnion(rec_var, tags, ext_var) => {
buf.push_str("[ "); 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(" ]"); 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 "); buf.push_str(" as ");
write_content( write_content(
env, env,
ctx,
subs.get_content_without_compacting(*rec_var), subs.get_content_without_compacting(*rec_var),
subs, subs,
buf, buf,
@ -777,11 +858,12 @@ pub fn chase_ext_tag_union<'a>(
} }
} }
fn write_apply( fn write_apply<'a>(
env: &Env, env: &Env,
ctx: &mut Context<'a>,
symbol: Symbol, symbol: Symbol,
args: &[Variable], args: &[Variable],
subs: &Subs, subs: &'a Subs,
buf: &mut String, buf: &mut String,
parens: Parens, parens: Parens,
) { ) {
@ -805,7 +887,7 @@ fn write_apply(
buf.push('('); 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("Num ");
buf.push_str(&arg_param); buf.push_str(&arg_param);
@ -838,6 +920,7 @@ fn write_apply(
buf.push(' '); buf.push(' ');
write_content( write_content(
env, env,
ctx,
subs.get_content_without_compacting(*arg), subs.get_content_without_compacting(*arg),
subs, subs,
buf, buf,
@ -852,11 +935,12 @@ fn write_apply(
} }
} }
fn write_fn( fn write_fn<'a>(
env: &Env, env: &Env,
ctx: &mut Context<'a>,
args: &[Variable], args: &[Variable],
ret: Variable, ret: Variable,
subs: &Subs, subs: &'a Subs,
buf: &mut String, buf: &mut String,
parens: Parens, parens: Parens,
) { ) {
@ -876,6 +960,7 @@ fn write_fn(
write_content( write_content(
env, env,
ctx,
subs.get_content_without_compacting(*arg), subs.get_content_without_compacting(*arg),
subs, subs,
buf, buf,
@ -886,6 +971,7 @@ fn write_fn(
buf.push_str(" -> "); buf.push_str(" -> ");
write_content( write_content(
env, env,
ctx,
subs.get_content_without_compacting(ret), subs.get_content_without_compacting(ret),
subs, subs,
buf, buf,

View file

@ -1,6 +1,9 @@
#![deny(unsafe_op_in_unsafe_fn)] #![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_collections::all::{ImMap, ImSet, MutSet, SendMap};
use roc_error_macros::internal_error;
use roc_module::ident::{Lowercase, TagName, Uppercase}; use roc_module::ident::{Lowercase, TagName, Uppercase};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use std::fmt; 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 { fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt::Result {
match this { match this {
Content::FlexVar(name) => write!(f, "Flex({:?})", name), Content::FlexVar(name) => write!(f, "Flex({:?})", name),
Content::FlexAbleVar(name, symbol) => write!(f, "FlexAble({:?}, {:?})", name, symbol),
Content::RigidVar(name) => write!(f, "Rigid({:?})", name), Content::RigidVar(name) => write!(f, "Rigid({:?})", name),
Content::RigidAbleVar(name, symbol) => write!(f, "RigidAble({:?}, {:?})", name, symbol),
Content::RecursionVar { Content::RecursionVar {
structure, structure,
opt_name, 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) => { FlatType::Func(arguments, lambda_set, result) => {
let slice = subs.get_subs_slice(*arguments); 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) => { FlatType::Record(fields, ext) => {
write!(f, "{{ ")?; write!(f, "{{ ")?;
@ -1737,6 +1754,14 @@ impl Subs {
self.set(var, desc); 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. /// Unions two keys without the possibility of failure.
pub fn union(&mut self, left: Variable, right: Variable, desc: Descriptor) { pub fn union(&mut self, left: Variable, right: Variable, desc: Descriptor) {
let l_root = self.utable.inlined_get_root_key(left); let l_root = self.utable.inlined_get_root_key(left);
@ -2118,6 +2143,12 @@ pub enum Content {
FlexVar(Option<SubsIndex<Lowercase>>), FlexVar(Option<SubsIndex<Lowercase>>),
/// name given in a user-written annotation /// name given in a user-written annotation
RigidVar(SubsIndex<Lowercase>), 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 /// name given to a recursion variable
RecursionVar { RecursionVar {
structure: Variable, structure: Variable,
@ -2713,11 +2744,11 @@ impl RecordFields {
&'a self, &'a self,
subs: &'a Subs, subs: &'a Subs,
ext: Variable, ext: Variable,
) -> impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + 'a { ) -> Result<impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + 'a, RecordFieldsError>
let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext) {
.expect("Something weird ended up in a record type"); let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext)?;
it Ok(it)
} }
#[inline(always)] #[inline(always)]
@ -2838,7 +2869,12 @@ fn occurs(
Err((root_var, vec![])) Err((root_var, vec![]))
} else { } else {
match subs.get_content_without_compacting(root_var) { match subs.get_content_without_compacting(root_var) {
FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => Ok(()), FlexVar(_)
| RigidVar(_)
| FlexAbleVar(_, _)
| RigidAbleVar(_, _)
| RecursionVar { .. }
| Error => Ok(()),
Structure(flat_type) => { Structure(flat_type) => {
let mut new_seen = seen.to_owned(); let mut new_seen = seen.to_owned();
@ -2966,7 +3002,12 @@ fn explicit_substitute(
to to
} else { } else {
match subs.get(in_var).content { match subs.get(in_var).content {
FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => in_var, FlexVar(_)
| RigidVar(_)
| FlexAbleVar(_, _)
| RigidAbleVar(_, _)
| RecursionVar { .. }
| Error => in_var,
Structure(flat_type) => { Structure(flat_type) => {
match flat_type { match flat_type {
@ -3134,9 +3175,9 @@ fn get_var_names(
subs.set_mark(var, Mark::GET_VAR_NAMES); subs.set_mark(var, Mark::GET_VAR_NAMES);
match desc.content { 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, subs,
0, 0,
name_index, name_index,
@ -3163,7 +3204,9 @@ fn get_var_names(
None => taken_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| { Alias(_, args, _, _) => args.into_iter().fold(taken_names, |answer, arg_var| {
get_var_names(subs, subs[arg_var], answer) get_var_names(subs, subs[arg_var], answer)
@ -3329,11 +3372,6 @@ fn content_to_err_type(
match content { match content {
Structure(flat_type) => flat_type_to_err_type(subs, state, flat_type), 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) => { FlexVar(opt_name) => {
let name = match opt_name { let name = match opt_name {
Some(name_index) => subs.field_names[name_index.index as usize].clone(), Some(name_index) => subs.field_names[name_index.index as usize].clone(),
@ -3356,6 +3394,28 @@ fn content_to_err_type(
ErrorType::RigidVar(name) 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, .. } => { RecursionVar { opt_name, .. } => {
let name = match opt_name { let name = match opt_name {
Some(name_index) => subs.field_names[name_index.index as usize].clone(), 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::*; use FlatType::*;
match &desc.content { match &desc.content {
FlexVar(_) | RigidVar(_) | Error => (), FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) | Error => (),
RecursionVar { structure, .. } => { RecursionVar { structure, .. } => {
stack.push(*structure); stack.push(*structure);
@ -3857,6 +3917,8 @@ impl StorageSubs {
match content { match content {
FlexVar(opt_name) => FlexVar(*opt_name), FlexVar(opt_name) => FlexVar(*opt_name),
RigidVar(name) => RigidVar(*name), RigidVar(name) => RigidVar(*name),
FlexAbleVar(opt_name, ability) => FlexAbleVar(*opt_name, *ability),
RigidAbleVar(name, ability) => RigidAbleVar(*name, *ability),
RecursionVar { RecursionVar {
structure, structure,
opt_name, opt_name,
@ -4253,6 +4315,29 @@ fn deep_copy_var_to_help(env: &mut DeepCopyVarToEnv<'_>, var: Variable) -> Varia
copy 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) => { Alias(symbol, arguments, real_type_var, kind) => {
let new_variables = let new_variables =
SubsSlice::reserve_into_subs(env.target, arguments.all_variables_len as _); SubsSlice::reserve_into_subs(env.target, arguments.all_variables_len as _);
@ -4312,6 +4397,8 @@ pub struct CopiedImport {
pub variable: Variable, pub variable: Variable,
pub flex: Vec<Variable>, pub flex: Vec<Variable>,
pub rigid: Vec<Variable>, pub rigid: Vec<Variable>,
pub flex_able: Vec<Variable>,
pub rigid_able: Vec<Variable>,
pub translations: Vec<(Variable, Variable)>, pub translations: Vec<(Variable, Variable)>,
pub registered: Vec<Variable>, pub registered: Vec<Variable>,
} }
@ -4322,6 +4409,8 @@ struct CopyImportEnv<'a> {
target: &'a mut Subs, target: &'a mut Subs,
flex: Vec<Variable>, flex: Vec<Variable>,
rigid: Vec<Variable>, rigid: Vec<Variable>,
flex_able: Vec<Variable>,
rigid_able: Vec<Variable>,
translations: Vec<(Variable, Variable)>, translations: Vec<(Variable, Variable)>,
registered: Vec<Variable>, registered: Vec<Variable>,
} }
@ -4343,6 +4432,8 @@ pub fn copy_import_to(
target, target,
flex: Vec::new(), flex: Vec::new(),
rigid: Vec::new(), rigid: Vec::new(),
flex_able: Vec::new(),
rigid_able: Vec::new(),
translations: Vec::new(), translations: Vec::new(),
registered: Vec::new(), registered: Vec::new(),
}; };
@ -4354,6 +4445,8 @@ pub fn copy_import_to(
source, source,
flex, flex,
rigid, rigid,
flex_able,
rigid_able,
translations, translations,
registered, registered,
target: _, target: _,
@ -4376,6 +4469,8 @@ pub fn copy_import_to(
variable: copy, variable: copy,
flex, flex,
rigid, rigid,
flex_able,
rigid_able,
translations, translations,
registered, registered,
} }
@ -4393,7 +4488,10 @@ pub fn copy_import_to(
/// standard variables /// standard variables
fn is_registered(content: &Content) -> bool { fn is_registered(content: &Content) -> bool {
match content { 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(FlatType::EmptyRecord | FlatType::EmptyTagUnion) => false,
Content::Structure(_) 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 // We have already marked the variable as copied, so we
// will not repeat this work or crawl this variable again. // will not repeat this work or crawl this variable again.
match desc.content { 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) => { Structure(flat_type) => {
let new_flat_type = match flat_type { let new_flat_type = match flat_type {
Apply(symbol, arguments) => { 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) 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) => { Record(fields, ext_var) => {
let record_fields = { let record_fields = {
@ -4631,6 +4738,20 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
copy 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 => { Error => {
// Open question: should this return Error, or a Flex var? // 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 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 { RecursionVar {
opt_name, opt_name,
structure, structure,
@ -4746,7 +4881,7 @@ where
use Content::*; use Content::*;
use FlatType::*; use FlatType::*;
match content { match content {
FlexVar(_) | RigidVar(_) => {} FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) => {}
RecursionVar { RecursionVar {
structure, structure,
opt_name: _, opt_name: _,

View file

@ -1744,6 +1744,15 @@ pub enum Reason {
RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>), RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>),
RecordDefaultField(Lowercase), RecordDefaultField(Lowercase),
NumericLiteralSuffix, NumericLiteralSuffix,
InvalidAbilityMemberSpecialization {
member_name: Symbol,
def_region: Region,
unimplemented_abilities: DoesNotImplementAbility,
},
GeneralizedAbilityMemberSpecialization {
member_name: Symbol,
def_region: Region,
},
} }
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
@ -1783,6 +1792,8 @@ pub enum Category {
Accessor(Lowercase), Accessor(Lowercase),
Access(Lowercase), Access(Lowercase),
DefaultValue(Lowercase), // for setting optional fields DefaultValue(Lowercase), // for setting optional fields
AbilityMemberSpecialization(Symbol),
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@ -1867,14 +1878,19 @@ pub enum Mismatch {
InconsistentWhenBranches, InconsistentWhenBranches,
CanonicalizationProblem, CanonicalizationProblem,
TypeNotInRange, TypeNotInRange,
DoesNotImplementAbiity(Variable, Symbol),
} }
pub type DoesNotImplementAbility = Vec<(ErrorType, Symbol)>;
#[derive(PartialEq, Eq, Clone, Hash)] #[derive(PartialEq, Eq, Clone, Hash)]
pub enum ErrorType { pub enum ErrorType {
Infinite, Infinite,
Type(Symbol, Vec<ErrorType>), Type(Symbol, Vec<ErrorType>),
FlexVar(Lowercase), FlexVar(Lowercase),
RigidVar(Lowercase), RigidVar(Lowercase),
FlexAbleVar(Lowercase, Symbol),
RigidAbleVar(Lowercase, Symbol),
Record(SendMap<Lowercase, RecordField<ErrorType>>, TypeExt), Record(SendMap<Lowercase, RecordField<ErrorType>>, TypeExt),
TagUnion(SendMap<TagName, Vec<ErrorType>>, TypeExt), TagUnion(SendMap<TagName, Vec<ErrorType>>, TypeExt),
RecursiveTagUnion(Box<ErrorType>, SendMap<TagName, Vec<ErrorType>>, TypeExt), RecursiveTagUnion(Box<ErrorType>, SendMap<TagName, Vec<ErrorType>>, TypeExt),
@ -1905,10 +1921,7 @@ impl ErrorType {
match self { match self {
Infinite => {} Infinite => {}
Type(_, ts) => ts.iter().for_each(|t| t.add_names(taken)), Type(_, ts) => ts.iter().for_each(|t| t.add_names(taken)),
FlexVar(v) => { FlexVar(v) | RigidVar(v) | FlexAbleVar(v, _) | RigidAbleVar(v, _) => {
taken.insert(v.clone());
}
RigidVar(v) => {
taken.insert(v.clone()); taken.insert(v.clone());
} }
Record(fields, ext) => { Record(fields, ext) => {
@ -2087,8 +2100,18 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
match error_type { match error_type {
Infinite => buf.push('∞'), Infinite => buf.push('∞'),
Error => buf.push('?'), Error => buf.push('?'),
FlexVar(name) => buf.push_str(name.as_str()), FlexVar(name) | RigidVar(name) => buf.push_str(name.as_str()),
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) => { Type(symbol, arguments) => {
let write_parens = parens == Parens::InTypeParam && !arguments.is_empty(); let write_parens = parens == Parens::InTypeParam && !arguments.is_empty();

View file

@ -11,6 +11,9 @@ bitflags = "1.3.2"
[dependencies.roc_collections] [dependencies.roc_collections]
path = "../collections" path = "../collections"
[dependencies.roc_error_macros]
path = "../../error_macros"
[dependencies.roc_module] [dependencies.roc_module]
path = "../module" path = "../module"

View file

@ -1,4 +1,5 @@
use bitflags::bitflags; use bitflags::bitflags;
use roc_error_macros::todo_abilities;
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_types::subs::Content::{self, *}; use roc_types::subs::Content::{self, *};
@ -6,7 +7,7 @@ use roc_types::subs::{
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable, AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable,
RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, 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 { macro_rules! mismatch {
() => {{ () => {{
@ -19,7 +20,10 @@ macro_rules! mismatch {
); );
} }
vec![Mismatch::TypeMismatch] Outcome {
mismatches: vec![Mismatch::TypeMismatch],
..Outcome::default()
}
}}; }};
($msg:expr) => {{ ($msg:expr) => {{
if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() { 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,) => {{ ($msg:expr,) => {{
mismatch!($msg) mismatch!($msg)
@ -51,8 +58,28 @@ macro_rules! mismatch {
println!(""); 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>; type Pool = Vec<Variable>;
@ -105,20 +132,52 @@ pub struct Context {
#[derive(Debug)] #[derive(Debug)]
pub enum Unified { pub enum Unified {
Success(Pool), Success {
Failure(Pool, ErrorType, ErrorType), vars: Pool,
must_implement_ability: Vec<MustImplementAbility>,
},
Failure(Pool, ErrorType, ErrorType, DoesNotImplementAbility),
BadType(Pool, roc_types::types::Problem), 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)] #[inline(always)]
pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Unified { pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Unified {
let mut vars = Vec::new(); 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() { if mismatches.is_empty() {
Unified::Success(vars) Unified::Success {
vars,
must_implement_ability,
}
} else { } else {
let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) { let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) {
ErrorTypeContext::ExpandRanges ErrorTypeContext::ExpandRanges
@ -136,7 +195,19 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Uni
if !problems.is_empty() { if !problems.is_empty() {
Unified::BadType(vars, problems.remove(0)) Unified::BadType(vars, problems.remove(0))
} else { } 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, mode: Mode,
) -> Outcome { ) -> Outcome {
if subs.equivalent(var1, var2) { if subs.equivalent(var1, var2) {
Vec::new() Outcome::default()
} else { } else {
let ctx = Context { let ctx = Context {
first: var1, first: var1,
@ -164,8 +235,10 @@ pub fn unify_pool(
} }
} }
fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { #[cfg(debug_assertions)]
if false { 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. // if true, print the types that are unified.
// //
// NOTE: names are generated here (when creating an error type) and that modifies names // 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_1 = subs.get(ctx.first).content;
let content_2 = subs.get(ctx.second).content; let content_2 = subs.get(ctx.second).content;
let mode = if ctx.mode.is_eq() { "~" } else { "+=" }; let mode = if ctx.mode.is_eq() { "~" } else { "+=" };
println!( eprintln!(
"{:?} {:?} {} {:?} {:?}", "{}({:?}-{:?}): {:?} {:?} {} {:?} {:?}",
time,
ctx.first,
ctx.second,
ctx.first, ctx.first,
roc_types::subs::SubsFmtContent(&content_1, subs), roc_types::subs::SubsFmtContent(&content_1, subs),
mode, mode,
@ -190,8 +266,21 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
roc_types::subs::SubsFmtContent(&content_2, subs), 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 { RecursionVar {
opt_name, opt_name,
structure, structure,
@ -203,7 +292,10 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
*structure, *structure,
&ctx.second_desc.content, &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) => { Structure(flat_type) => {
unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content) 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! // Error propagates. Whatever we're comparing it to doesn't matter!
merge(subs, &ctx, Error) merge(subs, &ctx, Error)
} }
} };
#[cfg(debug_assertions)]
debug_print_unified_types(subs, &ctx, true);
result
} }
#[inline(always)] #[inline(always)]
@ -238,7 +335,7 @@ fn unify_ranged_number(
} }
&RangedNumber(other_real_var, other_range_vars) => { &RangedNumber(other_real_var, other_range_vars) => {
let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode); 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) check_valid_range(subs, pool, ctx.first, other_range_vars, ctx.mode)
} else { } else {
outcome outcome
@ -246,9 +343,12 @@ fn unify_ranged_number(
// TODO: We should probably check that "range_vars" and "other_range_vars" intersect // TODO: We should probably check that "range_vars" and "other_range_vars" intersect
} }
Error => merge(subs, ctx, Error), 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; return outcome;
} }
@ -269,11 +369,11 @@ fn check_valid_range(
let snapshot = subs.snapshot(); let snapshot = subs.snapshot();
let old_pool = pool.clone(); let old_pool = pool.clone();
let outcome = unify_pool(subs, pool, var, possible_var, mode | Mode::RIGID_AS_FLEX); 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. // Okay, we matched some type in the range.
subs.rollback_to(snapshot); subs.rollback_to(snapshot);
*pool = old_pool; *pool = old_pool;
return vec![]; return Outcome::default();
} else if it.peek().is_some() { } else if it.peek().is_some() {
// We failed to match something in the range, but there are still things we can try. // We failed to match something in the range, but there are still things we can try.
subs.rollback_to(snapshot); 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)] #[inline(always)]
@ -310,13 +413,19 @@ fn unify_alias(
unify_pool(subs, pool, real_var, *structure, ctx.mode) unify_pool(subs, pool, real_var, *structure, ctx.mode)
} }
RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second, 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, _) Alias(other_symbol, other_args, other_real_var, _)
// Opaques types are only equal if the opaque symbols are equal! // Opaques types are only equal if the opaque symbols are equal!
if !either_is_opaque || symbol == *other_symbol => if !either_is_opaque || symbol == *other_symbol =>
{ {
if symbol == *other_symbol { if symbol == *other_symbol {
if args.len() == other_args.len() { if args.len() == other_args.len() {
let mut problems = Vec::new(); let mut outcome = Outcome::default();
let it = args let it = args
.all_variables() .all_variables()
.into_iter() .into_iter()
@ -327,23 +436,23 @@ fn unify_alias(
for (l, r) in it { for (l, r) in it {
let l_var = subs[l]; let l_var = subs[l];
let r_var = subs[r]; 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() { if outcome.mismatches.is_empty() {
problems.extend(merge(subs, ctx, *other_content)); outcome.union(merge(subs, ctx, *other_content));
} }
let args_unification_had_changes = !subs.vars_since_snapshot(&args_unification_snapshot).is_empty(); let args_unification_had_changes = !subs.vars_since_snapshot(&args_unification_snapshot).is_empty();
subs.commit_snapshot(args_unification_snapshot); 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 // 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`. // 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 { } else {
dbg!(args.len(), other_args.len()); dbg!(args.len(), other_args.len());
mismatch!("{:?}", symbol) mismatch!("{:?}", symbol)
@ -355,7 +464,7 @@ fn unify_alias(
Structure(_) if !either_is_opaque => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), 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 => { RangedNumber(other_real_var, other_range_vars) if !either_is_opaque => {
let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode); 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) check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode)
} else { } else {
outcome outcome
@ -448,13 +557,31 @@ fn unify_structure(
}, },
RangedNumber(other_real_var, other_range_vars) => { RangedNumber(other_real_var, other_range_vars) => {
let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); 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) check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode)
} else { } else {
outcome outcome
} }
} }
Error => merge(subs, ctx, Error), 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_1.is_empty() {
if separate.only_in_2.is_empty() { if separate.only_in_2.is_empty() {
// these variable will be the empty record, but we must still unify them // 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() { if !ext_outcome.mismatches.is_empty() {
return ext_problems; return ext_outcome;
} }
let mut field_problems = let mut field_outcome =
unify_shared_fields(subs, pool, ctx, shared_fields, OtherFields::None, ext1); 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 { } else {
let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2); let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2);
let flat_type = FlatType::Record(only_in_2, ext2); let flat_type = FlatType::Record(only_in_2, ext2);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); 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() { if !ext_outcome.mismatches.is_empty() {
return ext_problems; return ext_outcome;
} }
let mut field_problems = unify_shared_fields( let mut field_outcome = unify_shared_fields(
subs, subs,
pool, pool,
ctx, ctx,
@ -505,21 +632,21 @@ fn unify_record(
sub_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() { } else if separate.only_in_2.is_empty() {
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1); let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1);
let flat_type = FlatType::Record(only_in_1, ext1); let flat_type = FlatType::Record(only_in_1, ext1);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); 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() { if !ext_outcome.mismatches.is_empty() {
return ext_problems; return ext_outcome;
} }
let mut field_problems = unify_shared_fields( let mut field_outcome = unify_shared_fields(
subs, subs,
pool, pool,
ctx, ctx,
@ -528,9 +655,9 @@ fn unify_record(
sub_record, sub_record,
); );
field_problems.extend(ext_problems); field_outcome.union(ext_outcome);
field_problems field_outcome
} else { } else {
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1); 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); 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 sub1 = fresh(subs, pool, ctx, Structure(flat_type1));
let sub2 = fresh(subs, pool, ctx, Structure(flat_type2)); let sub2 = fresh(subs, pool, ctx, Structure(flat_type2));
let rec1_problems = unify_pool(subs, pool, ext1, sub2, ctx.mode); let rec1_outcome = unify_pool(subs, pool, ext1, sub2, ctx.mode);
if !rec1_problems.is_empty() { if !rec1_outcome.mismatches.is_empty() {
return rec1_problems; return rec1_outcome;
} }
let rec2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode); let rec2_outcome = unify_pool(subs, pool, sub1, ext2, ctx.mode);
if !rec2_problems.is_empty() { if !rec2_outcome.mismatches.is_empty() {
return rec2_problems; return rec2_outcome;
} }
let mut field_problems = let mut field_outcome =
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, ext); unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, ext);
field_problems.reserve(rec1_problems.len() + rec2_problems.len()); field_outcome
field_problems.extend(rec1_problems); .mismatches
field_problems.extend(rec2_problems); .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(); let num_shared_fields = shared_fields.len();
for (name, (actual, expected)) in shared_fields { for (name, (actual, expected)) in shared_fields {
let local_problems = unify_pool( let local_outcome = unify_pool(
subs, subs,
pool, pool,
actual.into_inner(), actual.into_inner(),
@ -592,7 +721,7 @@ fn unify_shared_fields(
ctx.mode, ctx.mode,
); );
if local_problems.is_empty() { if local_outcome.mismatches.is_empty() {
use RecordField::*; use RecordField::*;
// Unification of optional fields // Unification of optional fields
@ -856,18 +985,18 @@ fn unify_tag_union_new(
if separate.only_in_1.is_empty() { if separate.only_in_1.is_empty() {
if separate.only_in_2.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) unify_pool(subs, pool, ext1, ext2, ctx.mode)
} else { } else {
// In a presence context, we don't care about ext2 being equal to ext1 // In a presence context, we don't care about ext2 being equal to ext1
vec![] Outcome::default()
}; };
if !ext_problems.is_empty() { if !ext_outcome.mismatches.is_empty() {
return ext_problems; return ext_outcome;
} }
let mut tag_problems = unify_shared_tags_new( let mut shared_tags_outcome = unify_shared_tags_new(
subs, subs,
pool, pool,
ctx, ctx,
@ -877,20 +1006,20 @@ fn unify_tag_union_new(
recursion_var, recursion_var,
); );
tag_problems.extend(ext_problems); shared_tags_outcome.union(ext_outcome);
tag_problems shared_tags_outcome
} else { } else {
let unique_tags2 = UnionTags::insert_slices_into_subs(subs, separate.only_in_2); let unique_tags2 = UnionTags::insert_slices_into_subs(subs, separate.only_in_2);
let flat_type = FlatType::TagUnion(unique_tags2, ext2); let flat_type = FlatType::TagUnion(unique_tags2, ext2);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type)); 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() { if !ext_outcome.mismatches.is_empty() {
return ext_problems; return ext_outcome;
} }
let mut tag_problems = unify_shared_tags_new( let mut shared_tags_outcome = unify_shared_tags_new(
subs, subs,
pool, pool,
ctx, ctx,
@ -900,9 +1029,9 @@ fn unify_tag_union_new(
recursion_var, 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() { } else if separate.only_in_2.is_empty() {
let unique_tags1 = UnionTags::insert_slices_into_subs(subs, separate.only_in_1); 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 // In a presence context, we don't care about ext2 being equal to tags1
if ctx.mode.is_eq() { 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() { if !ext_outcome.mismatches.is_empty() {
return ext_problems; return ext_outcome;
} }
} }
@ -961,17 +1090,17 @@ fn unify_tag_union_new(
let snapshot = subs.snapshot(); let snapshot = subs.snapshot();
let ext1_problems = unify_pool(subs, pool, ext1, sub2, ctx.mode); let ext1_outcome = unify_pool(subs, pool, ext1, sub2, ctx.mode);
if !ext1_problems.is_empty() { if !ext1_outcome.mismatches.is_empty() {
subs.rollback_to(snapshot); subs.rollback_to(snapshot);
return ext1_problems; return ext1_outcome;
} }
if ctx.mode.is_eq() { if ctx.mode.is_eq() {
let ext2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode); let ext2_outcome = unify_pool(subs, pool, sub1, ext2, ctx.mode);
if !ext2_problems.is_empty() { if !ext2_outcome.mismatches.is_empty() {
subs.rollback_to(snapshot); 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, actual);
maybe_mark_tag_union_recursive(subs, expected); 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, // clearly, this is very suspicious: these variables have just been unified. And yet,
// not doing this leads to stack overflows // not doing this leads to stack overflows
if let Rec::Right(_) = recursion_var { if let Rec::Right(_) = recursion_var {
if problems.is_empty() { if outcome.mismatches.is_empty() {
matching_vars.push(expected); matching_vars.push(expected);
} }
} else if problems.is_empty() { } else if outcome.mismatches.is_empty() {
matching_vars.push(actual); matching_vars.push(actual);
} }
} }
@ -1215,39 +1344,43 @@ fn unify_flat_type(
debug_assert!(is_recursion_var(subs, *rec2)); debug_assert!(is_recursion_var(subs, *rec2));
let rec = Rec::Both(*rec1, *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); 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 => { (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() { if outcome.mismatches.is_empty() {
merge(subs, ctx, Structure(Apply(*r_symbol, *r_args))) outcome.union(merge(subs, ctx, Structure(Apply(*r_symbol, *r_args))));
} else {
problems
} }
outcome
} }
(Func(l_args, l_closure, l_ret), Func(r_args, r_closure, r_ret)) (Func(l_args, l_closure, l_ret), Func(r_args, r_closure, r_ret))
if l_args.len() == r_args.len() => if l_args.len() == r_args.len() =>
{ {
let arg_problems = unify_zip_slices(subs, pool, *l_args, *r_args); let arg_outcome = unify_zip_slices(subs, pool, *l_args, *r_args);
let ret_problems = unify_pool(subs, pool, *l_ret, *r_ret, ctx.mode); let ret_outcome = unify_pool(subs, pool, *l_ret, *r_ret, ctx.mode);
let closure_problems = unify_pool(subs, pool, *l_closure, *r_closure, 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() { let mut outcome = ret_outcome;
merge(subs, ctx, Structure(Func(*r_args, *r_closure, *r_ret)))
} else {
let mut problems = ret_problems;
problems.extend(closure_problems); outcome.union(closure_outcome);
problems.extend(arg_problems); 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)) => { (FunctionOrTagUnion(tag_name, tag_symbol, ext), Func(args, closure, ret)) => {
unify_function_or_tag_union_and_func( unify_function_or_tag_union_and_func(
@ -1282,12 +1415,12 @@ fn unify_flat_type(
let tag_name_2_ref = &subs[*tag_name_2]; let tag_name_2_ref = &subs[*tag_name_2];
if tag_name_1_ref == tag_name_2_ref { if tag_name_1_ref == tag_name_2_ref {
let problems = unify_pool(subs, pool, *ext1, *ext2, ctx.mode); let outcome = unify_pool(subs, pool, *ext1, *ext2, ctx.mode);
if problems.is_empty() { if outcome.mismatches.is_empty() {
let content = *subs.get_content_without_compacting(ctx.second); let content = *subs.get_content_without_compacting(ctx.second);
merge(subs, ctx, content) merge(subs, ctx, content)
} else { } else {
problems outcome
} }
} else { } else {
let tags1 = UnionTags::from_tag_name_index(*tag_name_1); let tags1 = UnionTags::from_tag_name_index(*tag_name_1);
@ -1343,7 +1476,7 @@ fn unify_zip_slices(
left: SubsSlice<Variable>, left: SubsSlice<Variable>,
right: SubsSlice<Variable>, right: SubsSlice<Variable>,
) -> Outcome { ) -> Outcome {
let mut problems = Vec::new(); let mut outcome = Outcome::default();
let it = left.into_iter().zip(right.into_iter()); let it = left.into_iter().zip(right.into_iter());
@ -1351,10 +1484,10 @@ fn unify_zip_slices(
let l_var = subs[l_index]; let l_var = subs[l_index];
let r_var = subs[r_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)] #[inline(always)]
@ -1362,6 +1495,7 @@ fn unify_rigid(
subs: &mut Subs, subs: &mut Subs,
ctx: &Context, ctx: &Context,
name: &SubsIndex<Lowercase>, name: &SubsIndex<Lowercase>,
opt_able_bound: Option<Symbol>,
other: &Content, other: &Content,
) -> Outcome { ) -> Outcome {
match other { match other {
@ -1369,16 +1503,76 @@ fn unify_rigid(
// If the other is flex, rigid wins! // If the other is flex, rigid wins!
merge(subs, ctx, RigidVar(*name)) merge(subs, ctx, RigidVar(*name))
} }
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) => { FlexAbleVar(_, other_ability) => {
if !ctx.mode.contains(Mode::RIGID_AS_FLEX) { 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 // Type mismatch! Rigid can only unify with flex, even if the
// rigid names are the same. // rigid names are the same.
mismatch!("Rigid {:?} with {:?}", ctx.first, &other) mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
} else {
// We are treating rigid vars as flex vars; admit this
merge(subs, ctx, *other)
}
} }
Error => { Error => {
// Error propagates. // Error propagates.
merge(subs, ctx, Error) merge(subs, ctx, Error)
@ -1391,16 +1585,49 @@ fn unify_flex(
subs: &mut Subs, subs: &mut Subs,
ctx: &Context, ctx: &Context,
opt_name: &Option<SubsIndex<Lowercase>>, opt_name: &Option<SubsIndex<Lowercase>>,
opt_able_bound: Option<Symbol>,
other: &Content, other: &Content,
) -> Outcome { ) -> Outcome {
match other { match other {
FlexVar(None) => { FlexVar(None) => {
// If both are flex, and only left has a name, keep the name around. // 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(_)) FlexVar(Some(_))
| RigidVar(_) | RigidVar(_)
| RigidAbleVar(_, _)
| RecursionVar { .. } | RecursionVar { .. }
| Structure(_) | Structure(_)
| Alias(_, _, _, _) | Alias(_, _, _, _)
@ -1446,7 +1673,13 @@ fn unify_recursion(
// unify the structure variable with this Structure // unify the structure variable with this Structure
unify_pool(subs, pool, structure, ctx.second, ctx.mode) 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( FlexVar(_) => merge(
subs, subs,
@ -1492,7 +1725,7 @@ pub fn merge(subs: &mut Subs, ctx: &Context, content: Content) -> Outcome {
subs.union(ctx.first, ctx.second, desc); subs.union(ctx.first, ctx.second, desc);
Vec::new() Outcome::default()
} }
fn register(subs: &mut Subs, desc: Descriptor, pool: &mut Pool) -> Variable { 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 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) unify_pool(subs, pool, new_tag_union_var, function_return, ctx.mode)
} else { } else {
unify_pool(subs, pool, function_return, new_tag_union_var, ctx.mode) 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, pool,
); );
let closure_problems = if left { let closure_outcome = if left {
unify_pool(subs, pool, tag_lambda_set, function_lambda_set, ctx.mode) unify_pool(subs, pool, tag_lambda_set, function_lambda_set, ctx.mode)
} else { } else {
unify_pool(subs, pool, function_lambda_set, tag_lambda_set, ctx.mode) 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 { let desc = if left {
subs.get(ctx.second) subs.get(ctx.second)
} else { } else {
@ -1586,5 +1819,5 @@ fn unify_function_or_tag_union_and_func(
subs.union(ctx.first, ctx.second, desc); subs.union(ctx.first, ctx.second, desc);
} }
problems outcome
} }

View file

@ -21,6 +21,7 @@ roc_parse = { path = "../compiler/parse" }
roc_target = { path = "../compiler/roc_target" } roc_target = { path = "../compiler/roc_target" }
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
roc_highlight = { path = "../highlight"} roc_highlight = { path = "../highlight"}
roc_reporting = { path = "../reporting"}
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
snafu = { version = "0.6.10", features = ["backtraces"] } snafu = { version = "0.6.10", features = ["backtraces"] }
peg = "0.8.0" peg = "0.8.0"

View file

@ -424,6 +424,7 @@ pub fn load_modules_for_files(filenames: Vec<PathBuf>) -> Vec<LoadedModule> {
src_dir.as_path(), src_dir.as_path(),
Default::default(), Default::default(),
roc_target::TargetInfo::default_x86_64(), // This is just type-checking for docs, so "target" doesn't matter 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), Ok(loaded) => modules.push(loaded),
Err(LoadingProblem::FormattedReport(report)) => { Err(LoadingProblem::FormattedReport(report)) => {

View file

@ -236,6 +236,8 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
print_err(&e) print_err(&e)
} else if let Ok(InputOutcome::Ignored) = input_outcome_res { } else if let Ok(InputOutcome::Ignored) = input_outcome_res {
println!("Input '{}' ignored!", ch); println!("Input '{}' ignored!", ch);
} else {
window.request_redraw()
} }
} }
//Keyboard Input //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 { if let Err(e) = keydown_res {
print_err(&e) print_err(&e)
} }
window.request_redraw()
} }
} }
} }

View file

@ -42,7 +42,7 @@ Expr : [ Val I64, Var Str, Add Expr Expr, Mul Expr Expr, Pow Expr Expr, Ln Expr
divmod : I64, I64 -> Result { div : I64, mod : I64 } [ DivByZero ]* divmod : I64, I64 -> Result { div : I64, mod : I64 } [ DivByZero ]*
divmod = \l, r -> divmod = \l, r ->
when Pair (l // r) (l % r) is when Pair (l // r) (l % r) is
Pair (Ok div) (Ok mod) -> Pair div (Ok mod) ->
Ok { div, mod } Ok { div, mod }
_ -> _ ->

View file

@ -47,7 +47,7 @@ makeMapHelp = \total, n, m ->
isFrequency = isFrequency =
n |> Num.isMultipleOf 4 n |> Num.isMultipleOf 4
key = n1 + ((total - n1) // 5 |> resultWithDefault 0) key = n1 + ((total - n1) // 5)
t2 = if isFrequency then delete t1 key else t1 t2 = if isFrequency then delete t1 key else t1
makeMapHelp total n1 t2 makeMapHelp total n1 t2

View file

@ -434,7 +434,7 @@ stepExecCtx = \ctx, char ->
( (
(T popCtx1 numR) <- Result.after (popNumber ctx) (T popCtx1 numR) <- Result.after (popNumber ctx)
(T popCtx2 numL) <- Result.after (popNumber popCtx1) (T popCtx2 numL) <- Result.after (popNumber popCtx1)
res <- Result.after (Num.divFloor numL numR) res <- Result.after (Num.divFloorChecked numL numR)
Ok (Context.pushStack popCtx2 (Number res)) Ok (Context.pushStack popCtx2 (Number res))
) )

View file

@ -4,9 +4,7 @@ app "hello-gui"
provides [ render ] to pf provides [ render ] to pf
render = render =
div0 = \numerator, denominator -> (numerator / denominator) |> Result.withDefault 0 rgba = \r, g, b, a -> { r: r / 255, g: g / 255, b: b / 255, a }
rgba = \r, g, b, a -> { r: div0 r 255, g: div0 g 255, b: div0 b 255, a }
styles = { bgColor: rgba 100 50 50 1, borderColor: rgba 10 20 30 1, borderWidth: 10, textColor: rgba 220 220 250 1 } styles = { bgColor: rgba 100 50 50 1, borderColor: rgba 10 20 30 1, borderWidth: 10, textColor: rgba 220 220 250 1 }

View file

@ -60,6 +60,7 @@ pub fn compile_to_mono<'a>(
src_dir, src_dir,
exposed_types, exposed_types,
target_info, target_info,
roc_reporting::report::RenderTarget::ColorTerminal,
); );
let mut loaded = match loaded { let mut loaded = match loaded {

View file

@ -50,7 +50,7 @@ fn int_addition() {
#[test] #[test]
fn float_addition() { fn float_addition() {
expect_success("1.1 + 2", "3.1 : F64"); expect_success("1.1 + 2", "3.1 : Float *");
} }
#[cfg(not(feature = "wasm"))] #[cfg(not(feature = "wasm"))]
@ -61,23 +61,41 @@ fn num_rem() {
#[cfg(not(feature = "wasm"))] #[cfg(not(feature = "wasm"))]
#[test] #[test]
fn num_floor_division_success() { fn num_floor_division() {
expect_success("Num.divFloor 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*"); expect_success("Num.divFloor 4 3", "1 : Int *");
} }
#[cfg(not(feature = "wasm"))] #[cfg(not(feature = "wasm"))]
#[test] #[test]
fn num_floor_division_divby_zero() { fn num_floor_checked_division_success() {
expect_success( expect_success(
"Num.divFloor 4 0", "Num.divFloorChecked 4 3",
"Ok 1 : Result (Int *) [ DivByZero ]*",
);
}
#[cfg(not(feature = "wasm"))]
#[test]
fn num_floor_checked_division_divby_zero() {
expect_success(
"Num.divFloorChecked 4 0",
"Err DivByZero : Result (Int *) [ DivByZero ]*", "Err DivByZero : Result (Int *) [ DivByZero ]*",
); );
} }
#[cfg(not(feature = "wasm"))] #[cfg(not(feature = "wasm"))]
#[test] #[test]
fn num_ceil_division_success() { fn num_ceil_division() {
expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*") expect_success("Num.divCeil 4 3", "2 : Int *")
}
#[cfg(not(feature = "wasm"))]
#[test]
fn num_ceil_checked_division_success() {
expect_success(
"Num.divCeilChecked 4 3",
"Ok 2 : Result (Int *) [ DivByZero ]*",
)
} }
#[test] #[test]
@ -291,7 +309,7 @@ fn nested_int_list() {
fn nested_float_list() { fn nested_float_list() {
expect_success( expect_success(
r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#, 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 *)))"#,
); );
} }
@ -385,7 +403,7 @@ fn list_contains() {
fn list_sum() { fn list_sum() {
expect_success("List.sum []", "0 : Num *"); expect_success("List.sum []", "0 : Num *");
expect_success("List.sum [ 1, 2, 3 ]", "6 : 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"))] #[cfg(not(feature = "wasm"))]
@ -1103,3 +1121,20 @@ fn issue_2582_specialize_result_value() {
r"<function> : Num *, List Str -> Result Str [ ListWasEmpty ]*", 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",
)
}

View file

@ -23,9 +23,11 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
[dev-dependencies] [dev-dependencies]
roc_constrain = { path = "../compiler/constrain" } roc_constrain = { path = "../compiler/constrain" }
roc_builtins = { path = "../compiler/builtins" } roc_builtins = { path = "../compiler/builtins" }
roc_load = { path = "../compiler/load" }
roc_problem = { path = "../compiler/problem" } roc_problem = { path = "../compiler/problem" }
roc_parse = { path = "../compiler/parse" } roc_parse = { path = "../compiler/parse" }
roc_target = { path = "../compiler/roc_target" } roc_target = { path = "../compiler/roc_target" }
roc_test_utils = { path = "../test_utils" } roc_test_utils = { path = "../test_utils" }
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"
indoc = "1.0.3" indoc = "1.0.3"
tempfile = "3.2.0"

View file

@ -44,6 +44,7 @@ const ALIAS_USES_ABILITY: &str = "ALIAS USES ABILITY";
const ILLEGAL_HAS_CLAUSE: &str = "ILLEGAL HAS CLAUSE"; const ILLEGAL_HAS_CLAUSE: &str = "ILLEGAL HAS CLAUSE";
const ABILITY_MEMBER_MISSING_HAS_CLAUSE: &str = "ABILITY MEMBER MISSING 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_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>( pub fn can_problem<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
@ -684,6 +685,34 @@ pub fn can_problem<'b>(
severity = Severity::RuntimeError; 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 { Problem::AbilityMemberBindsExternalAbility {
member, member,
ability, ability,

View file

@ -4,7 +4,7 @@ use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{LineInfo, Loc, Region}; 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::pretty_print::{Parens, WILDCARD};
use roc_types::types::{ use roc_types::types::{
AliasKind, Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt, AliasKind, Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt,
@ -118,7 +118,134 @@ pub fn type_problem<'b>(
other => panic!("unhandled bad type: {:?}", other), 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>( fn report_shadowing<'b>(
@ -950,6 +1077,98 @@ fn to_expr_report<'b>(
None, None,
), ),
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 } => { Reason::LowLevelOpArg { op, arg_index } => {
panic!( panic!(
"Compiler bug: argument #{} to low-level operation {:?} was the wrong type!", "Compiler bug: argument #{} to low-level operation {:?} was the wrong type!",
@ -983,6 +1202,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 { fn count_arguments(tipe: &ErrorType) -> usize {
use ErrorType::*; use ErrorType::*;
@ -1281,6 +1524,10 @@ fn format_category<'b>(
alloc.concat(vec![this_is, alloc.text(" a default field")]), alloc.concat(vec![this_is, alloc.text(" a default field")]),
alloc.text(" of type:"), alloc.text(" of type:"),
), ),
AbilityMemberSpecialization(_ability_member) => (
alloc.concat(vec![this_is, alloc.text(" a declared specialization")]),
alloc.text(" of type:"),
),
} }
} }
@ -1526,7 +1773,7 @@ fn to_circular_report<'b>(
You will see for parts of the type that repeat \ You will see for parts of the type that repeat \
something already printed out infinitely.", 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),
]), ]),
]) ])
}, },
@ -1627,10 +1874,12 @@ fn to_comparison<'b>(
expected: ErrorType, expected: ErrorType,
) -> Comparison<'b> { ) -> Comparison<'b> {
let diff = to_diff(alloc, Parens::Unnecessary, actual, expected); 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 { Comparison {
actual: alloc.type_block(diff.left), actual: alloc.type_block(actual),
expected: alloc.type_block(diff.right), expected: alloc.type_block(expected),
problems: match diff.status { problems: match diff.status {
Status::Similar => vec![], Status::Similar => vec![],
Status::Different(problems) => problems, Status::Different(problems) => problems,
@ -1683,6 +1932,9 @@ pub struct Diff<T> {
left: T, left: T,
right: T, right: T,
status: Status, 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>> { fn ext_to_doc<'b>(alloc: &'b RocDocAllocator<'b>, ext: TypeExt) -> Option<RocDocBuilder<'b>> {
@ -1694,10 +1946,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>( pub fn to_doc<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
parens: Parens, parens: Parens,
tipe: ErrorType, 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> { ) -> RocDocBuilder<'b> {
use ErrorType::*; use ErrorType::*;
@ -1706,22 +1978,26 @@ pub fn to_doc<'b>(
alloc, alloc,
parens, parens,
args.into_iter() args.into_iter()
.map(|arg| to_doc(alloc, Parens::InFn, arg)) .map(|arg| to_doc_help(ctx, alloc, Parens::InFn, arg))
.collect(), .collect(),
to_doc(alloc, Parens::InFn, *ret), to_doc_help(ctx, alloc, Parens::InFn, *ret),
), ),
Infinite => alloc.text(""), Infinite => alloc.text(""),
Error => alloc.text("?"), Error => alloc.text("?"),
FlexVar(lowercase) => alloc.type_variable(lowercase), FlexVar(lowercase) | RigidVar(lowercase) => alloc.type_variable(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( Type(symbol, args) => report_text::apply(
alloc, alloc,
parens, parens,
alloc.symbol_foreign_qualified(symbol), alloc.symbol_foreign_qualified(symbol),
args.into_iter() args.into_iter()
.map(|arg| to_doc(alloc, Parens::InTypeParam, arg)) .map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg))
.collect(), .collect(),
), ),
@ -1730,7 +2006,7 @@ pub fn to_doc<'b>(
parens, parens,
alloc.symbol_foreign_qualified(symbol), alloc.symbol_foreign_qualified(symbol),
args.into_iter() args.into_iter()
.map(|arg| to_doc(alloc, Parens::InTypeParam, arg)) .map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg))
.collect(), .collect(),
), ),
@ -1746,15 +2022,24 @@ pub fn to_doc<'b>(
( (
alloc.string(k.as_str().to_string()), alloc.string(k.as_str().to_string()),
match value { match value {
RecordField::Optional(v) => { RecordField::Optional(v) => RecordField::Optional(to_doc_help(
RecordField::Optional(to_doc(alloc, Parens::Unnecessary, v)) ctx,
} alloc,
RecordField::Required(v) => { Parens::Unnecessary,
RecordField::Required(to_doc(alloc, Parens::Unnecessary, v)) v,
} )),
RecordField::Demanded(v) => { RecordField::Required(v) => RecordField::Required(to_doc_help(
RecordField::Demanded(to_doc(alloc, Parens::Unnecessary, v)) ctx,
} alloc,
Parens::Unnecessary,
v,
)),
RecordField::Demanded(v) => RecordField::Demanded(to_doc_help(
ctx,
alloc,
Parens::Unnecessary,
v,
)),
}, },
) )
}) })
@ -1770,7 +2055,7 @@ pub fn to_doc<'b>(
( (
name, name,
args.into_iter() args.into_iter()
.map(|arg| to_doc(alloc, Parens::InTypeParam, arg)) .map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
) )
}) })
@ -1793,7 +2078,7 @@ pub fn to_doc<'b>(
( (
name, name,
args.into_iter() args.into_iter()
.map(|arg| to_doc(alloc, Parens::InTypeParam, arg)) .map(|arg| to_doc_help(ctx, alloc, Parens::InTypeParam, arg))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
) )
}) })
@ -1802,7 +2087,7 @@ pub fn to_doc<'b>(
report_text::recursive_tag_union( report_text::recursive_tag_union(
alloc, alloc,
to_doc(alloc, Parens::Unnecessary, *rec_var), to_doc_help(ctx, alloc, Parens::Unnecessary, *rec_var),
tags.into_iter() tags.into_iter()
.map(|(k, v)| (alloc.tag_name(k), v)) .map(|(k, v)| (alloc.tag_name(k), v))
.collect(), .collect(),
@ -1811,10 +2096,10 @@ pub fn to_doc<'b>(
} }
Range(typ, range_types) => { 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 let range_types = range_types
.into_iter() .into_iter()
.map(|arg| to_doc(alloc, Parens::Unnecessary, arg)) .map(|arg| to_doc_help(ctx, alloc, Parens::Unnecessary, arg))
.collect(); .collect();
report_text::range(alloc, typ, range_types) report_text::range(alloc, typ, range_types)
} }
@ -1826,15 +2111,42 @@ fn same<'b>(
parens: Parens, parens: Parens,
tipe: ErrorType, tipe: ErrorType,
) -> Diff<RocDocBuilder<'b>> { ) -> Diff<RocDocBuilder<'b>> {
let doc = to_doc(alloc, parens, tipe); let (doc, able) = to_doc(alloc, parens, tipe);
Diff { Diff {
left: doc.clone(), left: doc.clone(),
right: doc, right: doc,
status: Status::Similar, 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>( fn to_diff<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
parens: Parens, parens: Parens,
@ -1863,15 +2175,21 @@ fn to_diff<'b>(
let left = report_text::function(alloc, parens, arg_diff.left, ret_diff.left); 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 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 { Diff {
left, left,
right, right,
status, status,
left_able,
right_able,
} }
} else { } else {
let left = to_doc(alloc, Parens::InFn, type1); let (left, left_able) = to_doc(alloc, Parens::InFn, type1);
let right = to_doc(alloc, Parens::InFn, type2); let (right, right_able) = to_doc(alloc, Parens::InFn, type2);
Diff { Diff {
left, left,
@ -1880,6 +2198,8 @@ fn to_diff<'b>(
args1.len(), args1.len(),
args2.len(), args2.len(),
)]), )]),
left_able,
right_able,
} }
} }
} }
@ -1902,6 +2222,8 @@ fn to_diff<'b>(
left, left,
right, right,
status: args_diff.status, status: args_diff.status,
left_able: args_diff.left_able,
right_able: args_diff.right_able,
} }
} }
@ -1924,17 +2246,21 @@ fn to_diff<'b>(
left, left,
right, right,
status: args_diff.status, status: args_diff.status,
left_able: args_diff.left_able,
right_able: args_diff.right_able,
} }
} }
(Alias(_, _, _, AliasKind::Opaque), _) | (_, Alias(_, _, _, AliasKind::Opaque)) => { (Alias(_, _, _, AliasKind::Opaque), _) | (_, Alias(_, _, _, AliasKind::Opaque)) => {
let left = to_doc(alloc, Parens::InFn, type1); let (left, left_able) = to_doc(alloc, Parens::InFn, type1);
let right = to_doc(alloc, Parens::InFn, type2); let (right, right_able) = to_doc(alloc, Parens::InFn, type2);
Diff { Diff {
left, left,
right, right,
status: Status::Different(vec![Problem::OpaqueComparedToNonOpaque]), status: Status::Different(vec![Problem::OpaqueComparedToNonOpaque]),
left_able,
right_able,
} }
} }
@ -1961,20 +2287,22 @@ fn to_diff<'b>(
(RecursiveTagUnion(_rec1, _tags1, _ext1), RecursiveTagUnion(_rec2, _tags2, _ext2)) => { (RecursiveTagUnion(_rec1, _tags1, _ext1), RecursiveTagUnion(_rec2, _tags2, _ext2)) => {
// TODO do a better job here // TODO do a better job here
let left = to_doc(alloc, Parens::Unnecessary, type1); let (left, left_able) = to_doc(alloc, Parens::Unnecessary, type1);
let right = to_doc(alloc, Parens::Unnecessary, type2); let (right, right_able) = to_doc(alloc, Parens::Unnecessary, type2);
Diff { Diff {
left, left,
right, right,
status: Status::Similar, status: Status::Similar,
left_able,
right_able,
} }
} }
pair => { pair => {
// We hit none of the specific cases where we give more detailed information // We hit none of the specific cases where we give more detailed information
let left = to_doc(alloc, parens, type1); let (left, left_able) = to_doc(alloc, parens, type1);
let right = to_doc(alloc, parens, type2); let (right, right_able) = to_doc(alloc, parens, type2);
let is_int = |t: &ErrorType| match t { let is_int = |t: &ErrorType| match t {
ErrorType::Type(Symbol::NUM_INT, _) => true, ErrorType::Type(Symbol::NUM_INT, _) => true,
@ -2030,6 +2358,8 @@ fn to_diff<'b>(
left, left,
right, right,
status: Status::Different(problems), status: Status::Different(problems),
left_able,
right_able,
} }
} }
} }
@ -2049,6 +2379,8 @@ where
// TODO use ExactSizeIterator to pre-allocate here // TODO use ExactSizeIterator to pre-allocate here
let mut left = Vec::new(); let mut left = Vec::new();
let mut right = 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()) { for (arg1, arg2) in args1.into_iter().zip(args2.into_iter()) {
let diff = to_diff(alloc, parens, arg1, arg2); let diff = to_diff(alloc, parens, arg1, arg2);
@ -2056,12 +2388,16 @@ where
left.push(diff.left); left.push(diff.left);
right.push(diff.right); right.push(diff.right);
status.merge(diff.status); status.merge(diff.status);
left_able.extend(diff.left_able);
right_able.extend(diff.right_able);
} }
Diff { Diff {
left, left,
right, right,
status, status,
left_able,
right_able,
} }
} }
@ -2128,6 +2464,8 @@ fn diff_record<'b>(
_ => diff.status, _ => diff.status,
} }
}, },
left_able: diff.left_able,
right_able: diff.right_able,
} }
}; };
@ -2135,7 +2473,7 @@ fn diff_record<'b>(
( (
field.clone(), field.clone(),
alloc.string(field.as_str().to_string()), 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 let shared_keys = fields1
@ -2193,12 +2531,16 @@ fn diff_record<'b>(
left: vec![], left: vec![],
right: vec![], right: vec![],
status: Status::Similar, status: Status::Similar,
left_able: vec![],
right_able: vec![],
}; };
for diff in both { for diff in both {
fields_diff.left.push(diff.left); fields_diff.left.push(diff.left);
fields_diff.right.push(diff.right); fields_diff.right.push(diff.right);
fields_diff.status.merge(diff.status); 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 { if !all_fields_shared {
@ -2236,6 +2578,8 @@ fn diff_record<'b>(
left: doc1, left: doc1,
right: doc2, right: doc2,
status: fields_diff.status, status: fields_diff.status,
left_able: fields_diff.left_able,
right_able: fields_diff.right_able,
} }
} }
@ -2253,16 +2597,26 @@ fn diff_tag_union<'b>(
left: (field.clone(), alloc.tag_name(field.clone()), diff.left), left: (field.clone(), alloc.tag_name(field.clone()), diff.left),
right: (field.clone(), alloc.tag_name(field), diff.right), right: (field.clone(), alloc.tag_name(field), diff.right),
status: diff.status, status: diff.status,
left_able: diff.left_able,
right_able: diff.right_able,
} }
}; };
let to_unknown_docs = |(field, args): (&TagName, &Vec<ErrorType>)| { let to_unknown_docs = |(field, args): (&TagName, &Vec<ErrorType>)| -> (
( TagName,
field.clone(), RocDocBuilder<'b>,
alloc.tag_name(field.clone()), Vec<RocDocBuilder<'b>>,
AbleVariables,
) {
let (args, able): (_, Vec<AbleVariables>) =
// TODO add spaces between args // TODO add spaces between args
args.iter() args.iter()
.map(|arg| to_doc(alloc, Parens::InTypeParam, arg.clone())) .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 let shared_keys = fields1
@ -2280,7 +2634,7 @@ fn diff_tag_union<'b>(
let status = match (ext_has_fixed_fields(&ext1), ext_has_fixed_fields(&ext2)) { let status = match (ext_has_fixed_fields(&ext1), ext_has_fixed_fields(&ext2)) {
(true, true) => match left.peek() { (true, true) => match left.peek() {
Some((f, _, _)) => Status::Different(vec![Problem::TagTypo( Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo(
f.clone(), f.clone(),
fields2.keys().cloned().collect(), fields2.keys().cloned().collect(),
)]), )]),
@ -2297,14 +2651,14 @@ fn diff_tag_union<'b>(
} }
}, },
(false, true) => match left.peek() { (false, true) => match left.peek() {
Some((f, _, _)) => Status::Different(vec![Problem::TagTypo( Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo(
f.clone(), f.clone(),
fields2.keys().cloned().collect(), fields2.keys().cloned().collect(),
)]), )]),
None => Status::Similar, None => Status::Similar,
}, },
(true, false) => match right.peek() { (true, false) => match right.peek() {
Some((f, _, _)) => Status::Different(vec![Problem::TagTypo( Some((f, _, _, _)) => Status::Different(vec![Problem::TagTypo(
f.clone(), f.clone(),
fields1.keys().cloned().collect(), fields1.keys().cloned().collect(),
)]), )]),
@ -2319,17 +2673,27 @@ fn diff_tag_union<'b>(
left: vec![], left: vec![],
right: vec![], right: vec![],
status: Status::Similar, status: Status::Similar,
left_able: vec![],
right_able: vec![],
}; };
for diff in both { for diff in both {
fields_diff.left.push(diff.left); fields_diff.left.push(diff.left);
fields_diff.right.push(diff.right); fields_diff.right.push(diff.right);
fields_diff.status.merge(diff.status); 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 { if !all_fields_shared {
fields_diff.left.extend(left); for (tag, tag_doc, args, able) in left {
fields_diff.right.extend(right); 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![])); fields_diff.status.merge(Status::Different(vec![]));
} }
@ -2356,6 +2720,8 @@ fn diff_tag_union<'b>(
left: doc1, left: doc1,
right: doc2, right: doc2,
status: fields_diff.status, status: fields_diff.status,
left_able: fields_diff.left_able,
right_able: fields_diff.right_able,
} }
} }
@ -2373,12 +2739,16 @@ fn ext_to_diff<'b>(
left: ext_doc_1, left: ext_doc_1,
right: ext_doc_2, right: ext_doc_2,
status, status,
left_able: vec![],
right_able: vec![],
}, },
Status::Different(_) => Diff { Status::Different(_) => Diff {
// NOTE elm colors these differently at this point // NOTE elm colors these differently at this point
left: ext_doc_1, left: ext_doc_1,
right: ext_doc_2, right: ext_doc_2,
status, status,
left_able: vec![],
right_able: vec![],
}, },
} }
} }
@ -2518,7 +2888,7 @@ mod report_text {
let entry_to_doc = |(name, tipe): (Lowercase, RecordField<ErrorType>)| { let entry_to_doc = |(name, tipe): (Lowercase, RecordField<ErrorType>)| {
( (
alloc.string(name.as_str().to_string()), alloc.string(name.as_str().to_string()),
to_doc(alloc, Parens::Unnecessary, tipe.into_inner()), to_doc(alloc, Parens::Unnecessary, tipe.into_inner()).0,
) )
}; };
@ -2863,7 +3233,14 @@ fn type_problem_to_pretty<'b>(
match tipe { match tipe {
Infinite | Error | FlexVar(_) => alloc.nil(), 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")), Function(_, _, _) => bad_rigid_var(x, alloc.reflow("a function value")),
Record(_, _) => bad_rigid_var(x, alloc.reflow("a record value")), Record(_, _) => bad_rigid_var(x, alloc.reflow("a record value")),
TagUnion(_, _) | RecursiveTagUnion(_, _, _) => { TagUnion(_, _) | RecursiveTagUnion(_, _, _) => {

View file

@ -72,6 +72,12 @@ pub enum Severity {
Warning, Warning,
} }
#[derive(Clone, Copy, Debug)]
pub enum RenderTarget {
ColorTerminal,
Generic,
}
/// A textual report. /// A textual report.
pub struct Report<'b> { pub struct Report<'b> {
pub title: String, pub title: String,
@ -81,6 +87,19 @@ pub struct Report<'b> {
} }
impl<'b> 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. /// Render to CI console output, where no colors are available.
pub fn render_ci(self, buf: &'b mut String, alloc: &'b RocDocAllocator<'b>) { pub fn render_ci(self, buf: &'b mut String, alloc: &'b RocDocAllocator<'b>) {
let err_msg = "<buffer is not a utf-8 encoded string>"; let err_msg = "<buffer is not a utf-8 encoded string>";

View file

@ -1,6 +1,7 @@
extern crate bumpalo; extern crate bumpalo;
use self::bumpalo::Bump; use self::bumpalo::Bump;
use roc_can::abilities::AbilitiesStore;
use roc_can::constraint::{Constraint, Constraints}; use roc_can::constraint::{Constraint, Constraints};
use roc_can::env::Env; use roc_can::env::Env;
use roc_can::expected::Expected; use roc_can::expected::Expected;
@ -31,10 +32,19 @@ pub fn infer_expr(
constraints: &Constraints, constraints: &Constraints,
constraint: &Constraint, constraint: &Constraint,
aliases: &mut Aliases, aliases: &mut Aliases,
abilities_store: &mut AbilitiesStore,
expr_var: Variable, expr_var: Variable,
) -> (Content, Subs) { ) -> (Content, Subs) {
let env = solve::Env::default(); 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); let content = *solved.inner().get_content_without_compacting(expr_var);

View file

@ -8,17 +8,20 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_reporting { mod test_reporting {
use crate::helpers::test_home; use crate::helpers::{can_expr, infer_expr, test_home, CanExprOut, ParseErrOut};
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
use bumpalo::Bump; use bumpalo::Bump;
use indoc::indoc; 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_module::symbol::{Interns, ModuleId};
use roc_mono::ir::{Procs, Stmt, UpdateModeIds}; use roc_mono::ir::{Procs, Stmt, UpdateModeIds};
use roc_mono::layout::LayoutCache; use roc_mono::layout::LayoutCache;
use roc_region::all::LineInfo; use roc_region::all::LineInfo;
use roc_reporting::report::{ use roc_reporting::report::{
can_problem, mono_problem, parse_problem, type_problem, Report, Severity, ANSI_STYLE_CODES, can_problem, mono_problem, parse_problem, type_problem, RenderTarget, Report, Severity,
DEFAULT_PALETTE, ANSI_STYLE_CODES, DEFAULT_PALETTE,
}; };
use roc_reporting::report::{RocDocAllocator, RocDocBuilder}; use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
use roc_solve::solve; 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>( fn infer_expr_help<'a>(
arena: &'a Bump, arena: &'a Bump,
expr_src: &'a str, expr_src: &'a str,
@ -85,12 +296,14 @@ mod test_reporting {
} }
let mut unify_problems = Vec::new(); let mut unify_problems = Vec::new();
let mut abilities_store = AbilitiesStore::default();
let (_content, mut subs) = infer_expr( let (_content, mut subs) = infer_expr(
subs, subs,
&mut unify_problems, &mut unify_problems,
&constraints, &constraints,
&constraint, &constraint,
&mut solve_aliases, &mut solve_aliases,
&mut abilities_store,
var, var,
); );
@ -298,6 +511,27 @@ mod test_reporting {
assert_eq!(readable, expected_rendering); 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 { fn human_readable(str: &str) -> String {
str.replace(ANSI_STYLE_CODES.red, "<red>") str.replace(ANSI_STYLE_CODES.red, "<red>")
.replace(ANSI_STYLE_CODES.white, "<white>") .replace(ANSI_STYLE_CODES.white, "<white>")
@ -8682,7 +8916,7 @@ I need all branches in an `if` to have the same type!
#[test] #[test]
fn ability_demands_not_indented_with_first() { fn ability_demands_not_indented_with_first() {
report_problem_as( new_report_problem_as(
indoc!( indoc!(
r#" r#"
Eq has Eq has
@ -8699,19 +8933,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 I was partway through parsing an ability definition, but I got stuck
here: here:
2 eq : a, a -> U64 | a has Eq 5 eq : a, a -> U64 | a has Eq
3 neq : 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] #[test]
fn ability_demand_value_has_args() { fn ability_demand_value_has_args() {
report_problem_as( new_report_problem_as(
indoc!( indoc!(
r#" r#"
Eq has Eq has
@ -8727,12 +8960,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 I was partway through parsing an ability definition, but I got stuck
here: 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 I was expecting to see a : annotating the signature of this value
next. next."#
"#
), ),
) )
} }
@ -8898,8 +9130,8 @@ I need all branches in an `if` to have the same type!
} }
#[test] #[test]
fn bad_type_parameter_in_ability() { fn ability_bad_type_parameter() {
report_problem_as( new_report_problem_as(
indoc!( indoc!(
r#" r#"
Hash a b c has Hash a b c has
@ -8914,7 +9146,7 @@ I need all branches in an `if` to have the same type!
The definition of the `Hash` ability includes type variables: 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 Abilities cannot depend on type variables, but their member values
@ -8924,7 +9156,7 @@ I need all branches in an `if` to have the same type!
`Hash` is not used anywhere in your code. `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 If you didn't intend on using `Hash` then remove it so future readers of
@ -8936,12 +9168,12 @@ I need all branches in an `if` to have the same type!
#[test] #[test]
fn alias_in_has_clause() { fn alias_in_has_clause() {
report_problem_as( new_report_problem_as(
indoc!( indoc!(
r#" r#"
Hash has hash : a, b -> U64 | a has Hash, b has Bool app "test" provides [ hash ] to "./platform"
1 Hash has hash : a, b -> U64 | a has Hash, b has Bool
"# "#
), ),
indoc!( indoc!(
@ -8950,18 +9182,8 @@ I need all branches in an `if` to have the same type!
The type referenced in this "has" clause is not an ability: The type referenced in this "has" clause is not an ability:
1 Hash has hash : a, b -> U64 | a has Hash, b has Bool 3 Hash has hash : a, b -> U64 | a has Hash, b has Bool
^^^^ ^^^^
UNUSED DEFINITION
`hash` is not used anywhere in your code.
1 Hash has hash : a, b -> U64 | a has Hash, b has Bool
^^^^
If you didn't intend on using `hash` then remove it so future readers of
your code don't wonder why it is there.
"# "#
), ),
) )
@ -8969,12 +9191,12 @@ I need all branches in an `if` to have the same type!
#[test] #[test]
fn shadowed_type_variable_in_has_clause() { fn shadowed_type_variable_in_has_clause() {
report_problem_as( new_report_problem_as(
indoc!( indoc!(
r#" 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!( indoc!(
@ -8983,26 +9205,16 @@ I need all branches in an `if` to have the same type!
The `a` name is first defined here: 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: 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 Since these variables have the same name, it's easy to use the wrong
one on accident. Give one of them a new name. 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.
"# "#
), ),
) )
@ -9010,7 +9222,7 @@ I need all branches in an `if` to have the same type!
#[test] #[test]
fn alias_using_ability() { fn alias_using_ability() {
report_problem_as( new_report_problem_as(
indoc!( indoc!(
r#" r#"
Ability has ab : a -> {} | a has Ability Ability has ab : a -> {} | a has Ability
@ -9027,7 +9239,7 @@ I need all branches in an `if` to have the same type!
The definition of the `Alias` aliases references the ability `Ability`: 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 Abilities are not types, but you can add an ability constraint to a
@ -9041,7 +9253,7 @@ I need all branches in an `if` to have the same type!
`ab` is not used anywhere in your code. `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 If you didn't intend on using `ab` then remove it so future readers of
@ -9053,7 +9265,7 @@ I need all branches in an `if` to have the same type!
#[test] #[test]
fn ability_shadows_ability() { fn ability_shadows_ability() {
report_problem_as( new_report_problem_as(
indoc!( indoc!(
r#" r#"
Ability has ab : a -> U64 | a has Ability Ability has ab : a -> U64 | a has Ability
@ -9069,12 +9281,12 @@ I need all branches in an `if` to have the same type!
The `Ability` name is first defined here: The `Ability` name is first defined here:
1 Ability has ab : a -> U64 | a has Ability 4 Ability has ab : a -> U64 | a has Ability
^^^^^^^ ^^^^^^^
But then it's defined a second time here: But then it's defined a second time here:
3 Ability has ab1 : a -> U64 | a has Ability 6 Ability has ab1 : a -> U64 | a has Ability
^^^^^^^ ^^^^^^^
Since these abilities have the same name, it's easy to use the wrong Since these abilities have the same name, it's easy to use the wrong
@ -9084,7 +9296,7 @@ I need all branches in an `if` to have the same type!
`ab` is not used anywhere in your code. `ab` is not used anywhere in your code.
1 Ability has ab : a -> U64 | a has Ability 4 Ability has ab : a -> U64 | a has Ability
^^ ^^
If you didn't intend on using `ab` then remove it so future readers of If you didn't intend on using `ab` then remove it so future readers of
@ -9096,12 +9308,12 @@ I need all branches in an `if` to have the same type!
#[test] #[test]
fn ability_member_does_not_bind_ability() { fn ability_member_does_not_bind_ability() {
report_problem_as( new_report_problem_as(
indoc!( indoc!(
r#" r#"
Ability has ab : {} -> {} app "test" provides [ ] to "./platform"
1 Ability has ab : {} -> {}
"# "#
), ),
indoc!( indoc!(
@ -9111,7 +9323,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 The definition of the ability member `ab` does not include a `has` clause
binding a type variable to the ability `Ability`: 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 Ability members must include a `has` clause binding a type variable to
@ -9125,7 +9337,7 @@ I need all branches in an `if` to have the same type!
`Ability` is not used anywhere in your code. `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 If you didn't intend on using `Ability` then remove it so future readers
@ -9135,7 +9347,7 @@ I need all branches in an `if` to have the same type!
`ab` is not used anywhere in your code. `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 If you didn't intend on using `ab` then remove it so future readers of
@ -9147,13 +9359,13 @@ I need all branches in an `if` to have the same type!
#[test] #[test]
fn ability_member_binds_extra_ability() { fn ability_member_binds_extra_ability() {
report_problem_as( new_report_problem_as(
indoc!( indoc!(
r#" r#"
app "test" provides [ eq ] to "./platform"
Eq has eq : a, a -> Bool | a has Eq Eq has eq : a, a -> Bool | a has Eq
Hash has hash : a, b -> U64 | a has Eq, b has Hash Hash has hash : a, b -> U64 | a has Eq, b has Hash
1
"# "#
), ),
indoc!( indoc!(
@ -9163,7 +9375,7 @@ I need all branches in an `if` to have the same type!
The definition of the ability member `hash` includes a has clause The definition of the ability member `hash` includes a has clause
binding an ability it is not a part of: binding an ability it is not a part of:
2 Hash has hash : a, b -> U64 | a has Eq, b has Hash 4 Hash has hash : a, b -> U64 | a has Eq, b has Hash
^^^^^^^^ ^^^^^^^^
Currently, ability members can only bind variables to the ability they Currently, ability members can only bind variables to the ability they
@ -9173,19 +9385,9 @@ I need all branches in an `if` to have the same type!
UNUSED DEFINITION UNUSED DEFINITION
`eq` is not used anywhere in your code.
1 Eq has eq : a, a -> 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. `hash` is not used anywhere in your code.
2 Hash has hash : a, b -> U64 | a has Eq, b has Hash 4 Hash has hash : a, b -> U64 | a has Eq, b has Hash
^^^^ ^^^^
If you didn't intend on using `hash` then remove it so future readers of If you didn't intend on using `hash` then remove it so future readers of
@ -9196,15 +9398,55 @@ I need all branches in an `if` to have the same type!
} }
#[test] #[test]
fn has_clause_outside_of_ability() { fn ability_member_binds_parent_twice() {
report_problem_as( new_report_problem_as(
indoc!( indoc!(
r#" r#"
app "test" provides [ ] to "./platform"
Eq has eq : a, b -> 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 | 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 | 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 -> U64 | a has Hash Hash has hash : a -> U64 | a has Hash
f : a -> U64 | a has Hash f : a -> U64 | a has Hash
f
"# "#
), ),
indoc!( indoc!(
@ -9213,21 +9455,327 @@ I need all branches in an `if` to have the same type!
A `has` clause is not allowed here: A `has` clause is not allowed here:
3 f : a -> U64 | a has Hash 5 f : a -> U64 | a has Hash
^^^^^^^^^^ ^^^^^^^^^^
`has` clauses can only be specified on the top-level type annotation of `has` clauses can only be specified on the top-level type annotation of
an ability member. 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 -> U64 | a has Hash
1 Hash has hash : a -> U64 | a has Hash hash = \{} -> 0u64
"#
),
indoc!(
r#"
TYPE MISMATCH
Something is off with this specialization of `hash`:
5 hash = \{} -> 0u64
^^^^ ^^^^
If you didn't intend on using `hash` then remove it so future readers of This value is a declared specialization of type:
your code don't wonder why it is there.
{}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
^^^^
"# "#
), ),
) )

View file

@ -1130,7 +1130,7 @@ so calculations involving them take longer.
Roc does not let floating point calculations result in `Infinity`, `-Infinity`, Roc does not let floating point calculations result in `Infinity`, `-Infinity`,
or `NaN`. Any operation which would result in one of these 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, Similarly to how there are different sizes of floating point numbers,
there are also different sizes of integer to choose from: there are also different sizes of integer to choose from: