Merging in remote, implemented basics of List append in many modules

This commit is contained in:
Chad Stearns 2020-07-12 14:35:22 -04:00
commit f807947ce4
81 changed files with 8314 additions and 5180 deletions

View file

@ -54,9 +54,10 @@ jobs:
name: cargo test name: cargo test
with: with:
command: test command: test
args: --no-fail-fast
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
name: cargo test --release name: cargo test --release
with: with:
command: test command: test
args: --release args: --release --no-fail-fast

614
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -19,11 +19,11 @@ members = [
"compiler/mono", "compiler/mono",
"compiler/load", "compiler/load",
"compiler/gen", "compiler/gen",
"compiler/build",
"vendor/ena", "vendor/ena",
"vendor/pathfinding", "vendor/pathfinding",
"vendor/pretty", "vendor/pretty",
"editor", "editor"
"cli"
] ]
# Optimizations based on https://deterministic.space/high-performance-rust.html # Optimizations based on https://deterministic.space/high-performance-rust.html

View file

@ -46,6 +46,7 @@ roc_solve = { path = "../compiler/solve" }
roc_mono = { path = "../compiler/mono" } roc_mono = { path = "../compiler/mono" }
roc_load = { path = "../compiler/load" } roc_load = { path = "../compiler/load" }
roc_gen = { path = "../compiler/gen" } roc_gen = { path = "../compiler/gen" }
roc_build = { path = "../compiler/build" }
roc_reporting = { path = "../compiler/reporting" } roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor" } roc_editor = { path = "../editor" }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it # TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it

View file

@ -14,6 +14,7 @@ use roc_gen::llvm::build::{
build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel, build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel,
}; };
use roc_gen::llvm::convert::basic_type_from_layout; use roc_gen::llvm::convert::basic_type_from_layout;
use roc_build::program::build;
use roc_load::file::{LoadedModule, LoadingProblem}; use roc_load::file::{LoadedModule, LoadingProblem};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::expr::{Env, Expr, PartialProc, Procs}; use roc_mono::expr::{Env, Expr, PartialProc, Procs};
@ -247,6 +248,7 @@ async fn build_file(
Ok(binary_path) Ok(binary_path)
} }
// TODO this should probably use more helper functions // TODO this should probably use more helper functions
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn gen( fn gen(
@ -362,8 +364,8 @@ fn gen(
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| { let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
panic!( panic!(
"Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", "Code gen error in CLI: could not convert to layout. Err was {:?}",
err, subs err
) )
}); });

View file

@ -221,8 +221,8 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| { let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
panic!( panic!(
"Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", "Code gen error in test: could not convert to layout. Err was {:?}",
err, subs err
) )
}); });
let execution_engine = module let execution_engine = module
@ -329,7 +329,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St
if main_fn.verify(true) { if main_fn.verify(true) {
fpm.run_on(&main_fn); fpm.run_on(&main_fn);
} else { } else {
panic!("Function {} failed LLVM verification.", main_fn_name); panic!("Main function {} failed LLVM verification. Uncomment things near this error message for more details.", main_fn_name);
} }
// Verify the module // Verify the module

55
compiler/build/Cargo.toml Normal file
View file

@ -0,0 +1,55 @@
[package]
name = "roc_build"
version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"]
edition = "2018"
license = "Apache-2.0"
[dependencies]
roc_collections = { path = "../collections" }
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" }
roc_uniq = { path = "../uniq" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_load = { path = "../load" }
roc_gen = { path = "../gen" }
roc_reporting = { path = "../reporting" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] }
inlinable_string = "0.1.0"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded", "process", "io-driver"] }
# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything.
#
# The reason for this fork is that the way Inkwell is designed, you have to use
# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that
# breaking changes get pushed directly to that branch, which breaks our build
# without warning.
#
# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch),
# but although that worked locally, it did not work on GitHub Actions. (After a few
# hours of investigation, gave up trying to figure out why.) So this is the workaround:
# having an immutable tag on the rtfeldman/inkwell fork which points to
# a particular "release" of Inkwell.
#
# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" }
target-lexicon = "0.10"
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

13
compiler/build/src/lib.rs Normal file
View file

@ -0,0 +1,13 @@
#![warn(clippy::all, clippy::dbg_macro)]
// I'm skeptical that clippy:large_enum_variant is a good lint to have globally enabled.
//
// It warns about a performance problem where the only quick remediation is
// to allocate more on the heap, which has lots of tradeoffs - including making it
// long-term unclear which allocations *need* to happen for compilation's sake
// (e.g. recursive structures) versus those which were only added to appease clippy.
//
// Effectively optimizing data struture memory layout isn't a quick fix,
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
pub mod program;

View file

@ -0,0 +1,393 @@
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::module::Linkage;
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{
build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel,
};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_load::file::LoadedModule;
use roc_module::symbol::Symbol;
use roc_mono::expr::{Env, Expr, PartialProc, Procs};
use roc_mono::layout::{Layout, LayoutCache};
use inkwell::targets::{
CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple,
};
use std::path::{Path, PathBuf};
use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor};
// TODO how should imported modules factor into this? What if those use builtins too?
// TODO this should probably use more helper functions
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
#[allow(clippy::cognitive_complexity)]
pub fn build(
arena: &Bump,
loaded: LoadedModule,
filename: PathBuf,
target: Triple,
dest_filename: &Path,
opt_level: OptLevel,
) {
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE};
let src = loaded.src;
let home = loaded.module_id;
let src_lines: Vec<&str> = src.split('\n').collect();
let palette = DEFAULT_PALETTE;
// Report parsing and canonicalization problems
let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns);
for problem in loaded.can_problems.into_iter() {
let report = can_problem(&alloc, filename.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
println!("\n{}\n", buf);
}
for problem in loaded.type_problems.into_iter() {
let report = type_problem(&alloc, filename.clone(), problem);
let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette);
println!("\n{}\n", buf);
}
// Look up the types and expressions of the `provided` values
// TODO instead of hardcoding this to `main`, use the `provided` list and gen all of them.
let ident_ids = loaded.interns.all_ident_ids.get(&home).unwrap();
let main_ident_id = *ident_ids.get_id(&"main".into()).unwrap_or_else(|| {
todo!("TODO gracefully handle the case where `main` wasn't declared in the app")
});
let main_symbol = Symbol::new(home, main_ident_id);
let mut main_var = None;
let mut main_expr = None;
for (symbol, var) in loaded.exposed_vars_by_symbol {
if symbol == main_symbol {
main_var = Some(var);
break;
}
}
let mut decls_by_id = loaded.declarations_by_id;
let home_decls = decls_by_id
.remove(&loaded.module_id)
.expect("Root module ID not found in loaded declarations_by_id");
// We use a loop label here so we can break all the way out of a nested
// loop inside DeclareRec if we find the expr there.
//
// https://doc.rust-lang.org/1.30.0/book/first-edition/loops.html#loop-labels
'find_expr: for decl in home_decls.iter() {
use roc_can::def::Declaration::*;
match decl {
Declare(def) => {
if def.pattern_vars.contains_key(&main_symbol) {
main_expr = Some(def.loc_expr.clone());
break 'find_expr;
}
}
DeclareRec(defs) => {
for def in defs {
if def.pattern_vars.contains_key(&main_symbol) {
main_expr = Some(def.loc_expr.clone());
break 'find_expr;
}
}
}
InvalidCycle(_, _) | Builtin(_) => {
// These can never contain main.
}
}
}
let loc_expr = main_expr.unwrap_or_else(|| {
panic!("TODO gracefully handle the case where `main` was declared but not exposed")
});
let mut subs = loaded.solved.into_inner();
let content = match main_var {
Some(var) => subs.get_without_compacting(var).content,
None => todo!("TODO gracefully handle the case where `main` was declared but not exposed"),
};
// Generate the binary
let context = Context::create();
let module = module_from_builtins(&context, "app");
let builder = context.create_builder();
let fpm = PassManager::create(&module);
roc_gen::llvm::build::add_passes(&fpm, opt_level);
fpm.initialize();
// Compute main_fn_type before moving subs to Env
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| {
panic!(
"Code gen error in Program: could not convert to layout. Err was {:?}",
err
)
});
let main_fn_type =
basic_type_from_layout(&arena, &context, &layout, ptr_bytes).fn_type(&[], false);
let main_fn_name = "$main";
// Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
context: &context,
interns: loaded.interns,
module: arena.alloc(module),
ptr_bytes,
};
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = LayoutIds::default();
let mut procs = Procs::default();
let mut mono_problems = std::vec::Vec::new();
let mut mono_env = Env {
arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
pointer_size: ptr_bytes,
jump_counter: arena.alloc(0),
};
// Add modules' decls to Procs
for (_, mut decls) in decls_by_id
.drain()
.chain(std::iter::once((loaded.module_id, home_decls)))
{
for decl in decls.drain(..) {
use roc_can::def::Declaration::*;
use roc_can::expr::Expr::*;
use roc_can::pattern::Pattern::*;
match decl {
Declare(def) | Builtin(def) => match def.loc_pattern.value {
Identifier(symbol) => {
match def.loc_expr.value {
Closure(annotation, _, _, loc_args, boxed_body) => {
let (loc_body, ret_var) = *boxed_body;
procs.insert_named(
&mut mono_env,
symbol,
annotation,
loc_args,
loc_body,
ret_var,
);
}
body => {
let proc = PartialProc {
annotation: def.expr_var,
// This is a 0-arity thunk, so it has no arguments.
pattern_symbols: bumpalo::collections::Vec::new_in(arena),
body,
};
procs.partial_procs.insert(symbol, proc);
procs.module_thunks.insert(symbol);
}
};
}
other => {
todo!("TODO gracefully handle Declare({:?})", other);
}
},
DeclareRec(_defs) => {
todo!("TODO support DeclareRec");
}
InvalidCycle(_loc_idents, _regions) => {
todo!("TODO handle InvalidCycle");
}
}
}
}
// Populate Procs further and get the low-level Expr from the canonical Expr
let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs);
let mut headers = Vec::with_capacity(procs.pending_specializations.len());
let mut layout_cache = LayoutCache::default();
let (mut specializations, runtime_errors) =
roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
// Put this module's ident_ids back in the interns, so we can use them in env.
// This must happen *after* building the headers, because otherwise there's
// a conflicting mutable borrow on ident_ids.
env.interns.all_ident_ids.insert(home, ident_ids);
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for ((symbol, layout), proc) in specializations.drain() {
let (fn_val, arg_basic_types) =
build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types));
}
// Build each proc using its header info.
for (proc, fn_val, arg_basic_types) in headers {
// NOTE: This is here to be uncommented in case verification fails.
// (This approach means we don't have to defensively clone name here.)
//
// println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) {
fpm.run_on(&fn_val);
} else {
// NOTE: If this fails, uncomment the above println to debug.
panic!(
"Non-main function failed LLVM verification. Uncomment the above println to debug!"
);
}
}
// Add main to the module.
let cc = get_call_conventions(target.default_calling_convention().unwrap());
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
main_fn.set_call_conventions(cc);
main_fn.set_linkage(Linkage::External);
// Add main's body
let basic_block = context.append_basic_block(main_fn, "entry");
builder.position_at_end(basic_block);
let ret = roc_gen::llvm::build::build_expr(
&env,
&mut layout_ids,
&ImMap::default(),
main_fn,
&main_body,
);
builder.build_return(Some(&ret));
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
if main_fn.verify(true) {
fpm.run_on(&main_fn);
} else {
panic!("Function {} failed LLVM verification.", main_fn_name);
}
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("😱 LLVM errors when defining module: {:?}", errors);
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
// Emit the .o file
// NOTE: arch_str is *not* the same as the beginning of the magic target triple
// string! For example, if it's "x86-64" here, the magic target triple string
// will begin with "x86_64" (with an underscore) instead.
let arch_str = match target.architecture {
Architecture::X86_64 => {
Target::initialize_x86(&InitializationConfig::default());
"x86-64"
}
Architecture::Arm(_) if cfg!(feature = "target-arm") => {
// NOTE: why not enable arm and wasm by default?
//
// We had some trouble getting them to link properly. This may be resolved in the
// future, or maybe it was just some weird configuration on one machine.
Target::initialize_arm(&InitializationConfig::default());
"arm"
}
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => {
Target::initialize_webassembly(&InitializationConfig::default());
"wasm32"
}
_ => panic!(
"TODO gracefully handle unsupported target architecture: {:?}",
target.architecture
),
};
let opt = OptimizationLevel::Default;
let reloc = RelocMode::Default;
let model = CodeModel::Default;
// Best guide I've found on how to determine these magic strings:
//
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
let target_triple_str = match target {
Triple {
architecture: Architecture::X86_64,
vendor: Vendor::Unknown,
operating_system: OperatingSystem::Linux,
..
} => "x86_64-unknown-linux-gnu",
Triple {
architecture: Architecture::X86_64,
vendor: Vendor::Pc,
operating_system: OperatingSystem::Linux,
..
} => "x86_64-pc-linux-gnu",
Triple {
architecture: Architecture::X86_64,
vendor: Vendor::Unknown,
operating_system: OperatingSystem::Darwin,
..
} => "x86_64-unknown-darwin10",
Triple {
architecture: Architecture::X86_64,
vendor: Vendor::Apple,
operating_system: OperatingSystem::Darwin,
..
} => "x86_64-apple-darwin10",
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
};
let target_machine = Target::from_name(arch_str)
.unwrap()
.create_target_machine(
&TargetTriple::create(target_triple_str),
arch_str,
"+avx2", // TODO this string was used uncritically from an example, and should be reexamined
opt,
reloc,
model,
)
.unwrap();
target_machine
.write_to_file(&env.module, FileType::Object, &dest_filename)
.expect("Writing .o file failed");
println!("\nSuccess! 🎉\n\n\t{}\n", dest_filename.display());
}

View file

@ -0,0 +1,26 @@
# So you want to add a builtin?
Builtins are the functions and modules that are implicitly imported into every module. Some of them compile down to llvm, others need to be constructed and defined. Making a new builtin means touching many files. Here is what it takes:
### module/src/symbol.rs
Towards the bottom of a file there is a `define_builtins!` macro being used that takes many modules and function names. The first level (`List`, `Int` ..) is the module name, and the second level is the function or value name (`reverse`, `mod` ..). If you wanted to add a `Int` function called `addTwo` go to `2 Int: "Int" => {` and inside that case add to the bottom `38 INT_ADD_TWO: "addTwo"` (assuming there are 37 existing ones).
Some of these have `#` inside their name (`first#list`, #lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them.
But we can use these values and some of these are necessary for implementing builtins. For example, `List.get` returns tags, and it is not easy for us to create tags when composing LLVM. What is easier however, is:
- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, Int -> elem` in LLVM
- ..writing `List elem, Int -> Result elem [ OutOfBounds ]*` in a type safe way that uses `getUnsafe` internally, only after it checks if the `elem` at `Int` index exists.
## Bottom level LLVM values and functions
### gen/src/llvm/build.rs
This is where bottom-level functions that need to be written as LLVM are created. If the function leads to a tag thats a good sign it should not be written here in `build.rs`. If its simple fundamental stuff like `INT_ADD` then it certainly should be written here.
## More abstract values and functions that likely return tags.
### can/src/builtins.rs
If the function you are making is _not_ low level or returns something like a tag, then it should probably be written here by means of lower level functions written in `build.rs`.
## Letting the compiler know these functions exist
Its one thing to actually write these functions, its _another_ thing to let the Roc compiler know they exist. You have to tell the compiler "Hey, this function exists, and it has this type signature". That happens in these modules:
### builtins/src/std.rs
### builtins/src/unique.rs

View file

@ -3,6 +3,5 @@ interface Defaults
imports [ imports [
Map.{ Map }, Map.{ Map },
Set.{ Set }, Set.{ Set },
Float.{ Float }, Num.{ Num, Int, Float }
Int.{ Int }
] ]

View file

@ -258,15 +258,12 @@ Int size : Num [ @Int size ]
## decimal floats have [some hardware support](http://speleotrove.com/decimal/) ## decimal floats have [some hardware support](http://speleotrove.com/decimal/)
## among the rare processors which support decimal float instructions at all. ## among the rare processors which support decimal float instructions at all.
## ##
## This specification covers these float formats, all of which Roc supports: ## This specification covers several float formats. Here are the ones Roc supports:
## ##
## - #F16 (16-bit binary float) # TODO show a table like we do with ints, with the min/max ranges
## - #F32 (32-bit binary float) ## - #F32 (32-bit binary float)
## - #F64 (64-bit binary float) ## - #F64 (64-bit binary float)
## - #F128 (128-bit binary float)
## - #D32 (32-bit decimal float) ## - #D32 (32-bit decimal float)
## - #D64 (64-bit decimal float) ## - #D64 (64-bit decimal float) # TODO show a table like we do with ints, with the min/max ranges
## - #D128 (128-bit decimal float)
## ##
## Like #Int, it's possible for #Float operations to overflow. Like with ints, ## Like #Int, it's possible for #Float operations to overflow. Like with ints,
## you'll typically get a crash when this happens. ## you'll typically get a crash when this happens.
@ -495,12 +492,21 @@ toU32 : Int * -> U32
toU64 : Int * -> U64 toU64 : Int * -> U64
toU128 : Int * -> U128 toU128 : Int * -> U128
## Convert a #Num to a #F16. If the given number can't be precisely represented in a #F16, ## Convert a #Num to a #F32. If the given number can't be precisely represented in a #F32,
## there will be a loss of precision. ## there will be a loss of precision.
toF16 : Num * -> F16
toF32 : Num * -> F32 toF32 : Num * -> F32
## Convert a #Num to a #F64. If the given number can't be precisely represented in a #F64,
## there will be a loss of precision.
toF64 : Num * -> F64 toF64 : Num * -> F64
toF128 : Num * -> F128
## Convert a #Num to a #D32. If the given number can't be precisely represented in a #D32,
## there will be a loss of precision.
toD32 : Num * -> D32
## Convert a #Num to a #D64. If the given number can't be precisely represented in a #D64,
## there will be a loss of precision.
toD64 : Num * -> D64
## Divide two integers and #Num.round the resulut. ## Divide two integers and #Num.round the resulut.
## ##
@ -708,7 +714,7 @@ pi : Float *
## Divide two #Float numbers. ## Divide two #Float numbers.
## ##
## `a / b` is shorthand for `Float.div a b`. ## `a / b` is shorthand for `Num.div a b`.
## ##
## Division by zero is undefined in mathematics. As such, you should make ## Division by zero is undefined in mathematics. As such, you should make
## sure never to pass zero as the denomaintor to this function! ## sure never to pass zero as the denomaintor to this function!
@ -722,12 +728,12 @@ pi : Float *
## ##
## >>> 5.0 / 7.0 ## >>> 5.0 / 7.0
## ##
## >>> Float.div 5 7 ## >>> Num.div 5 7
## ##
## `Float.div` can be convenient in pipelines. ## `Num.div` can be convenient in pipelines.
## ##
## >>> Float.pi ## >>> Float.pi
## >>> |> Float.div 2.0 ## >>> |> Num.div 2.0
#div : Float, Float -> Result Float DivByZero #div : Float, Float -> Result Float DivByZero
div = \numerator, denominator -> div = \numerator, denominator ->
when numerator is when numerator is

View file

@ -78,46 +78,46 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
// Integer : [ @Integer ] // Integer : [ @Integer ]
add_alias( add_alias(
Symbol::INT_INTEGER, Symbol::NUM_INTEGER,
BuiltinAlias { BuiltinAlias {
region: Region::zero(), region: Region::zero(),
vars: Vec::new(), vars: Vec::new(),
typ: single_private_tag(Symbol::INT_AT_INTEGER, Vec::new()), typ: single_private_tag(Symbol::NUM_AT_INTEGER, Vec::new()),
}, },
); );
// Int : Num Integer // Int : Num Integer
add_alias( add_alias(
Symbol::INT_INT, Symbol::NUM_INT,
BuiltinAlias { BuiltinAlias {
region: Region::zero(), region: Region::zero(),
vars: Vec::new(), vars: Vec::new(),
typ: SolvedType::Apply( typ: SolvedType::Apply(
Symbol::NUM_NUM, Symbol::NUM_NUM,
vec![SolvedType::Apply(Symbol::INT_INTEGER, Vec::new())], vec![SolvedType::Apply(Symbol::NUM_INTEGER, Vec::new())],
), ),
}, },
); );
// FloatingPoint : [ @FloatingPoint ] // FloatingPoint : [ @FloatingPoint ]
add_alias( add_alias(
Symbol::FLOAT_FLOATINGPOINT, Symbol::NUM_FLOATINGPOINT,
BuiltinAlias { BuiltinAlias {
region: Region::zero(), region: Region::zero(),
vars: Vec::new(), vars: Vec::new(),
typ: single_private_tag(Symbol::FLOAT_AT_FLOATINGPOINT, Vec::new()), typ: single_private_tag(Symbol::NUM_AT_FLOATINGPOINT, Vec::new()),
}, },
); );
// Float : Num FloatingPoint // Float : Num FloatingPoint
add_alias( add_alias(
Symbol::FLOAT_FLOAT, Symbol::NUM_FLOAT,
BuiltinAlias { BuiltinAlias {
region: Region::zero(), region: Region::zero(),
vars: Vec::new(), vars: Vec::new(),
typ: SolvedType::Apply( typ: SolvedType::Apply(
Symbol::NUM_NUM, Symbol::NUM_NUM,
vec![SolvedType::Apply(Symbol::FLOAT_FLOATINGPOINT, Vec::new())], vec![SolvedType::Apply(Symbol::NUM_FLOATINGPOINT, Vec::new())],
), ),
}, },
); );
@ -271,131 +271,137 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(float_type())), SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(float_type())),
); );
// Int module // isNegative : Num a -> Bool
// isLt or (<) : Num a, Num a -> Bool
add_type( add_type(
Symbol::INT_LT, Symbol::NUM_IS_NEGATIVE,
SolvedType::Func(vec![int_type(), int_type()], Box::new(bool_type())), SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())),
); );
// equals : Int, Int -> Bool // isPositive : Num a -> Bool
add_type( add_type(
Symbol::INT_EQ_I64, Symbol::NUM_IS_POSITIVE,
SolvedType::Func(vec![int_type(), int_type()], Box::new(bool_type())), SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())),
); );
// notEquals : Int, Int -> Bool // isZero : Num a -> Bool
add_type( add_type(
Symbol::INT_NEQ_I64, Symbol::NUM_IS_ZERO,
SolvedType::Func(vec![int_type(), int_type()], Box::new(bool_type())), SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())),
); );
// abs : Int -> Int // isEven : Num a -> Bool
add_type( add_type(
Symbol::INT_ABS, Symbol::NUM_IS_EVEN,
SolvedType::Func(vec![int_type()], Box::new(int_type())), SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())),
); );
// highest : Int // isOdd : Num a -> Bool
add_type(Symbol::INT_HIGHEST, int_type());
// lowest : Int
add_type(Symbol::INT_LOWEST, int_type());
// div : Int, Int -> Int
add_type( add_type(
Symbol::INT_DIV_UNSAFE, Symbol::NUM_IS_ODD,
SolvedType::Func(vec![int_type(), int_type()], Box::new(int_type())), SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())),
); );
// rem : Int, Int -> Int // maxInt : Int
add_type( add_type(Symbol::NUM_MAX_INT, int_type());
Symbol::INT_REM_UNSAFE,
SolvedType::Func(vec![int_type(), int_type()], Box::new(int_type())),
);
// mod : Int, Int -> Result Int [ DivByZero ]* // minInt : Int
add_type(Symbol::NUM_MIN_INT, int_type());
// div : Int, Int -> Result Int [ DivByZero ]*
let div_by_zero = SolvedType::TagUnion( let div_by_zero = SolvedType::TagUnion(
vec![(TagName::Global("DivByZero".into()), vec![])], vec![(TagName::Global("DivByZero".into()), vec![])],
Box::new(SolvedType::Wildcard), Box::new(SolvedType::Wildcard),
); );
add_type( add_type(
Symbol::INT_MOD, Symbol::NUM_DIV_INT,
SolvedType::Func( SolvedType::Func(
vec![int_type(), int_type()], vec![int_type(), int_type()],
Box::new(result_type(flex(TVAR1), div_by_zero)), Box::new(result_type(int_type(), div_by_zero.clone())),
),
);
// rem : Int, Int -> Result Int [ DivByZero ]*
add_type(
Symbol::NUM_REM,
SolvedType::Func(
vec![int_type(), int_type()],
Box::new(result_type(int_type(), div_by_zero.clone())),
),
);
// mod : Int, Int -> Result Int [ DivByZero ]*
add_type(
Symbol::NUM_MOD_INT,
SolvedType::Func(
vec![int_type(), int_type()],
Box::new(result_type(int_type(), div_by_zero.clone())),
), ),
); );
// Float module // Float module
// isGt or (>) : Num a, Num a -> Bool
add_type(
Symbol::FLOAT_GT,
SolvedType::Func(vec![float_type(), float_type()], Box::new(bool_type())),
);
// eq or (==) : Num a, Num a -> Bool
add_type(
Symbol::FLOAT_EQ,
SolvedType::Func(vec![float_type(), float_type()], Box::new(bool_type())),
);
// div : Float, Float -> Float // div : Float, Float -> Float
add_type( add_type(
Symbol::FLOAT_DIV, Symbol::NUM_DIV_FLOAT,
SolvedType::Func(vec![float_type(), float_type()], Box::new(float_type())), SolvedType::Func(
vec![float_type(), float_type()],
Box::new(result_type(float_type(), div_by_zero.clone())),
),
); );
// mod : Float, Float -> Float // mod : Float, Float -> Result Int [ DivByZero ]*
add_type( add_type(
Symbol::FLOAT_MOD, Symbol::NUM_MOD_FLOAT,
SolvedType::Func(vec![float_type(), float_type()], Box::new(float_type())), SolvedType::Func(
vec![float_type(), float_type()],
Box::new(result_type(float_type(), div_by_zero)),
),
); );
// sqrt : Float -> Float // sqrt : Float -> Float
let sqrt_of_negative = SolvedType::TagUnion(
vec![(TagName::Global("SqrtOfNegative".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_type( add_type(
Symbol::FLOAT_SQRT, Symbol::NUM_SQRT,
SolvedType::Func(vec![float_type()], Box::new(float_type())), SolvedType::Func(
vec![float_type()],
Box::new(result_type(float_type(), sqrt_of_negative)),
),
); );
// round : Float -> Int // round : Float -> Int
add_type( add_type(
Symbol::FLOAT_ROUND, Symbol::NUM_ROUND,
SolvedType::Func(vec![float_type()], Box::new(int_type())), SolvedType::Func(vec![float_type()], Box::new(int_type())),
); );
// abs : Float -> Float
add_type(
Symbol::FLOAT_ABS,
SolvedType::Func(vec![float_type()], Box::new(float_type())),
);
// sin : Float -> Float // sin : Float -> Float
add_type( add_type(
Symbol::FLOAT_SIN, Symbol::NUM_SIN,
SolvedType::Func(vec![float_type()], Box::new(float_type())), SolvedType::Func(vec![float_type()], Box::new(float_type())),
); );
// cos : Float -> Float // cos : Float -> Float
add_type( add_type(
Symbol::FLOAT_COS, Symbol::NUM_COS,
SolvedType::Func(vec![float_type()], Box::new(float_type())), SolvedType::Func(vec![float_type()], Box::new(float_type())),
); );
// tan : Float -> Float // tan : Float -> Float
add_type( add_type(
Symbol::FLOAT_TAN, Symbol::NUM_TAN,
SolvedType::Func(vec![float_type()], Box::new(float_type())), SolvedType::Func(vec![float_type()], Box::new(float_type())),
); );
// highest : Float // maxFloat : Float
add_type(Symbol::FLOAT_HIGHEST, float_type()); add_type(Symbol::NUM_MAX_FLOAT, float_type());
// lowest : Float // minFloat : Float
add_type(Symbol::FLOAT_LOWEST, float_type()); add_type(Symbol::NUM_MIN_FLOAT, float_type());
// Bool module // Bool module
@ -447,11 +453,17 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// first : List elem -> Result elem [ ListWasEmpty ]*
let list_was_empty = SolvedType::TagUnion(
vec![(TagName::Global("ListWasEmpty".into()), vec![])],
Box::new(SolvedType::Wildcard),
);
add_type( add_type(
Symbol::LIST_GET_UNSAFE, Symbol::LIST_FIRST,
SolvedType::Func( SolvedType::Func(
vec![list_type(flex(TVAR1)), int_type()], vec![list_type(flex(TVAR1))],
Box::new(flex(TVAR1)), Box::new(result_type(flex(TVAR1), list_was_empty)),
), ),
); );
@ -531,6 +543,15 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// append : List elem, List elem -> List elem
add_type(
Symbol::LIST_APPEND,
SolvedType::Func(
vec![list_type(flex(TVAR1)), list_type(flex(TVAR1))],
Box::new(list_type(flex(TVAR1))),
),
);
// len : List * -> Int // len : List * -> Int
add_type( add_type(
Symbol::LIST_LEN, Symbol::LIST_LEN,
@ -661,12 +682,12 @@ fn flex(tvar: VarId) -> SolvedType {
#[inline(always)] #[inline(always)]
fn float_type() -> SolvedType { fn float_type() -> SolvedType {
SolvedType::Apply(Symbol::FLOAT_FLOAT, Vec::new()) SolvedType::Apply(Symbol::NUM_FLOAT, Vec::new())
} }
#[inline(always)] #[inline(always)]
fn int_type() -> SolvedType { fn int_type() -> SolvedType {
SolvedType::Apply(Symbol::INT_INT, Vec::new()) SolvedType::Apply(Symbol::NUM_INT, Vec::new())
} }
#[inline(always)] #[inline(always)]

View file

@ -153,27 +153,27 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
// Integer : [ @Integer ] // Integer : [ @Integer ]
add_alias( add_alias(
Symbol::INT_INTEGER, Symbol::NUM_INTEGER,
BuiltinAlias { BuiltinAlias {
region: Region::zero(), region: Region::zero(),
vars: Vec::new(), vars: Vec::new(),
typ: single_private_tag(Symbol::INT_AT_INTEGER, Vec::new()), typ: single_private_tag(Symbol::NUM_AT_INTEGER, Vec::new()),
}, },
); );
// FloatingPoint : [ @FloatingPoint ] // FloatingPoint : [ @FloatingPoint ]
add_alias( add_alias(
Symbol::FLOAT_FLOATINGPOINT, Symbol::NUM_FLOATINGPOINT,
BuiltinAlias { BuiltinAlias {
region: Region::zero(), region: Region::zero(),
vars: Vec::new(), vars: Vec::new(),
typ: single_private_tag(Symbol::FLOAT_AT_FLOATINGPOINT, Vec::new()), typ: single_private_tag(Symbol::NUM_AT_FLOATINGPOINT, Vec::new()),
}, },
); );
// Int : Num Integer // Int : Num Integer
add_alias( add_alias(
Symbol::INT_INT, Symbol::NUM_INT,
BuiltinAlias { BuiltinAlias {
region: Region::zero(), region: Region::zero(),
vars: Vec::new(), vars: Vec::new(),
@ -181,7 +181,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
Symbol::NUM_NUM, Symbol::NUM_NUM,
vec![lift( vec![lift(
star, star,
SolvedType::Apply(Symbol::INT_INTEGER, Vec::new()), SolvedType::Apply(Symbol::NUM_INTEGER, Vec::new()),
)], )],
), ),
}, },
@ -189,7 +189,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
// Float : Num FloatingPoint // Float : Num FloatingPoint
add_alias( add_alias(
Symbol::FLOAT_FLOAT, Symbol::NUM_FLOAT,
BuiltinAlias { BuiltinAlias {
region: Region::zero(), region: Region::zero(),
vars: Vec::new(), vars: Vec::new(),
@ -197,7 +197,7 @@ pub fn aliases() -> MutMap<Symbol, BuiltinAlias> {
Symbol::NUM_NUM, Symbol::NUM_NUM,
vec![lift( vec![lift(
star, star,
SolvedType::Apply(Symbol::FLOAT_FLOATINGPOINT, Vec::new()), SolvedType::Apply(Symbol::NUM_FLOATINGPOINT, Vec::new()),
)], )],
), ),
}, },
@ -323,34 +323,8 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![num_type(star1, a)], float_type(star2)) unique_function(vec![num_type(star1, a)], float_type(star2))
}); });
// Int module
// isLt or (<) : Num a, Num a -> Bool
add_type(Symbol::INT_LT, {
let_tvars! { u, v, w };
unique_function(vec![int_type(u), int_type(v)], bool_type(w))
});
// equals or (==) : Int, Int -> Bool
add_type(Symbol::INT_EQ_I64, {
let_tvars! { u, v, w };
unique_function(vec![int_type(u), int_type(v)], bool_type(w))
});
// not equals or (!=) : Int, Int -> Bool
add_type(Symbol::INT_NEQ_I64, {
let_tvars! { u, v, w };
unique_function(vec![int_type(u), int_type(v)], bool_type(w))
});
// abs : Int -> Int
add_type(Symbol::INT_ABS, {
let_tvars! { u, v };
unique_function(vec![int_type(u)], int_type(v))
});
// rem : Attr * Int, Attr * Int -> Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*)) // rem : Attr * Int, Attr * Int -> Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))
add_type(Symbol::INT_REM, { add_type(Symbol::NUM_REM, {
let_tvars! { star1, star2, star3, star4, star5 }; let_tvars! { star1, star2, star3, star4, star5 };
unique_function( unique_function(
vec![int_type(star1), int_type(star2)], vec![int_type(star1), int_type(star2)],
@ -358,25 +332,20 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
) )
}); });
add_type(Symbol::INT_REM_UNSAFE, { // maxInt : Int
let_tvars! { star1, star2, star3, }; add_type(Symbol::NUM_MAX_INT, {
unique_function(vec![int_type(star1), int_type(star2)], int_type(star3))
});
// highest : Int
add_type(Symbol::INT_HIGHEST, {
let_tvars! { star }; let_tvars! { star };
int_type(star) int_type(star)
}); });
// lowest : Int // minInt : Int
add_type(Symbol::INT_LOWEST, { add_type(Symbol::NUM_MIN_INT, {
let_tvars! { star }; let_tvars! { star };
int_type(star) int_type(star)
}); });
// div or (//) : Int, Int -> Result Int [ DivByZero ]* // divFloor or (//) : Int, Int -> Result Int [ DivByZero ]*
add_type(Symbol::INT_DIV, { add_type(Symbol::NUM_DIV_INT, {
let_tvars! { star1, star2, star3, star4, star5 }; let_tvars! { star1, star2, star3, star4, star5 };
unique_function( unique_function(
vec![int_type(star1), int_type(star2)], vec![int_type(star1), int_type(star2)],
@ -384,97 +353,95 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
) )
}); });
add_type(Symbol::INT_DIV_UNSAFE, { // divFloat : Float, Float -> Float
let_tvars! { star1, star2, star3, }; add_type(Symbol::NUM_DIV_FLOAT, {
unique_function(vec![int_type(star1), int_type(star2)], int_type(star3)) let_tvars! { star1, star2, star3, star4, star5};
});
// mod : Int, Int -> Int
add_type(Symbol::INT_MOD, {
let_tvars! { star1, star2, star3, };
unique_function(vec![int_type(star1), int_type(star2)], int_type(star3))
});
// Float module
// isGt or (>) : Num a, Num a -> Bool
add_type(Symbol::FLOAT_GT, {
let_tvars! { star1, star2, star3}
unique_function(vec![float_type(star1), float_type(star2)], bool_type(star3))
});
// eq or (==) : Num a, Num a -> Bool
add_type(Symbol::FLOAT_EQ, {
let_tvars! { star1, star2, star3}
unique_function(vec![float_type(star1), float_type(star2)], bool_type(star3))
});
// div : Float, Float -> Float
add_type(Symbol::FLOAT_DIV, {
let_tvars! { star1, star2, star3};
unique_function( unique_function(
vec![float_type(star1), float_type(star2)], vec![float_type(star1), float_type(star2)],
float_type(star3), result_type(star3, float_type(star4), lift(star5, div_by_zero())),
)
});
// mod : Float, Float -> Float
add_type(Symbol::FLOAT_MOD, {
let_tvars! { star1, star2, star3};
unique_function(
vec![float_type(star1), float_type(star2)],
float_type(star3),
) )
}); });
// round : Float -> Int // round : Float -> Int
add_type(Symbol::FLOAT_ROUND, { add_type(Symbol::NUM_ROUND, {
let_tvars! { star1, star2 }; let_tvars! { star1, star2 };
unique_function(vec![float_type(star1)], int_type(star2)) unique_function(vec![float_type(star1)], int_type(star2))
}); });
// sqrt : Float -> Float // sqrt : Float -> Float
add_type(Symbol::FLOAT_SQRT, { let sqrt_of_negative = SolvedType::TagUnion(
let_tvars! { star1, star2 }; vec![(TagName::Global("SqrtOfNegative".into()), vec![])],
unique_function(vec![float_type(star1)], float_type(star2)) Box::new(SolvedType::Wildcard),
}); );
// abs : Float -> Float add_type(Symbol::NUM_SQRT, {
add_type(Symbol::FLOAT_ABS, { let_tvars! { star1, star2, star3, star4 };
let_tvars! { star1, star2 }; unique_function(
unique_function(vec![float_type(star1)], float_type(star2)) vec![float_type(star1)],
result_type(star2, float_type(star3), lift(star4, sqrt_of_negative)),
)
}); });
// sin : Float -> Float // sin : Float -> Float
add_type(Symbol::FLOAT_SIN, { add_type(Symbol::NUM_SIN, {
let_tvars! { star1, star2 }; let_tvars! { star1, star2 };
unique_function(vec![float_type(star1)], float_type(star2)) unique_function(vec![float_type(star1)], float_type(star2))
}); });
// cos : Float -> Float // cos : Float -> Float
add_type(Symbol::FLOAT_COS, { add_type(Symbol::NUM_COS, {
let_tvars! { star1, star2 }; let_tvars! { star1, star2 };
unique_function(vec![float_type(star1)], float_type(star2)) unique_function(vec![float_type(star1)], float_type(star2))
}); });
// tan : Float -> Float // tan : Float -> Float
add_type(Symbol::FLOAT_TAN, { add_type(Symbol::NUM_TAN, {
let_tvars! { star1, star2 }; let_tvars! { star1, star2 };
unique_function(vec![float_type(star1)], float_type(star2)) unique_function(vec![float_type(star1)], float_type(star2))
}); });
// highest : Float // maxFloat : Float
add_type(Symbol::FLOAT_HIGHEST, { add_type(Symbol::NUM_MAX_FLOAT, {
let_tvars! { star }; let_tvars! { star };
float_type(star) float_type(star)
}); });
// lowest : Float // minFloat : Float
add_type(Symbol::FLOAT_LOWEST, { add_type(Symbol::NUM_MIN_FLOAT, {
let_tvars! { star }; let_tvars! { star };
float_type(star) float_type(star)
}); });
// isNegative : Num a -> Bool
add_type(Symbol::NUM_IS_NEGATIVE, {
let_tvars! { star1, star2, a };
unique_function(vec![num_type(star1, a)], bool_type(star2))
});
// isPositive : Num a -> Bool
add_type(Symbol::NUM_IS_POSITIVE, {
let_tvars! { star1, star2, a };
unique_function(vec![num_type(star1, a)], bool_type(star2))
});
// isZero : Num a -> Bool
add_type(Symbol::NUM_IS_ZERO, {
let_tvars! { star1, star2, a };
unique_function(vec![num_type(star1, a)], bool_type(star2))
});
// isEven : Num a -> Bool
add_type(Symbol::NUM_IS_EVEN, {
let_tvars! { star1, star2, a };
unique_function(vec![num_type(star1, a)], bool_type(star2))
});
// isOdd : Num a -> Bool
add_type(Symbol::NUM_IS_ODD, {
let_tvars! { star1, star2, a };
unique_function(vec![num_type(star1, a)], bool_type(star2))
});
// Bool module // Bool module
// isEq or (==) : Attr * a, Attr * a -> Attr * Bool // isEq or (==) : Attr * a, Attr * a -> Attr * Bool
@ -559,12 +526,6 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
) )
}); });
// is LIST_GET_UNSAFE still used?
add_type(Symbol::LIST_GET_UNSAFE, {
let_tvars! { star1, star2, a };
unique_function(vec![list_type(star1, a), int_type(star2)], flex(a))
});
// set : Attr (w | u | v) (List (Attr u a)) // set : Attr (w | u | v) (List (Attr u a))
// , Attr * Int // , Attr * Int
// , Attr (u | v) a // , Attr (u | v) a
@ -610,6 +571,28 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
) )
}); });
// reverse : Attr * (List (Attr * a)) -> Attr * (List (Attr * a))
add_type(Symbol::LIST_REVERSE, {
let_tvars! { a, star1, star2 };
unique_function(
vec![SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![
flex(star1),
SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]),
],
)],
SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![
boolean(star2),
SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]),
],
),
)
});
// To repeat an item, it must be shared! // To repeat an item, it must be shared!
// //
// repeat : Attr * Int // repeat : Attr * Int
@ -630,22 +613,31 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
) )
}); });
// reverse : Attr * (List (Attr * a)) -> Attr * (List (Attr * a)) // append : Attr * (List (Attr * a)), Attr * (List (Attr * a)) -> Attr * (List (Attr * a))
add_type(Symbol::LIST_REVERSE, { add_type(Symbol::LIST_APPEND, {
let_tvars! { a, star1, star2 }; let_tvars! { a, star1, star2, star3 };
unique_function( unique_function(
vec![SolvedType::Apply( vec![
Symbol::ATTR_ATTR, SolvedType::Apply(
vec![ Symbol::ATTR_ATTR,
flex(star1), vec![
SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]), flex(star1),
], SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]),
)], ],
),
SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![
flex(star2),
SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]),
],
),
],
SolvedType::Apply( SolvedType::Apply(
Symbol::ATTR_ATTR, Symbol::ATTR_ATTR,
vec![ vec![
boolean(star2), boolean(star3),
SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]), SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)]),
], ],
), ),
@ -1044,7 +1036,7 @@ fn lift(u: VarId, a: SolvedType) -> SolvedType {
fn float_type(u: VarId) -> SolvedType { fn float_type(u: VarId) -> SolvedType {
SolvedType::Apply( SolvedType::Apply(
Symbol::ATTR_ATTR, Symbol::ATTR_ATTR,
vec![flex(u), SolvedType::Apply(Symbol::FLOAT_FLOAT, Vec::new())], vec![flex(u), SolvedType::Apply(Symbol::NUM_FLOAT, Vec::new())],
) )
} }
@ -1052,7 +1044,7 @@ fn float_type(u: VarId) -> SolvedType {
fn int_type(u: VarId) -> SolvedType { fn int_type(u: VarId) -> SolvedType {
SolvedType::Apply( SolvedType::Apply(
Symbol::ATTR_ATTR, Symbol::ATTR_ATTR,
vec![flex(u), SolvedType::Apply(Symbol::INT_INT, Vec::new())], vec![flex(u), SolvedType::Apply(Symbol::NUM_INT, Vec::new())],
) )
} }

View file

@ -1,12 +1,10 @@
use crate::env::Env; use crate::env::Env;
use crate::scope::Scope; use crate::scope::Scope;
use roc_collections::all::{MutMap, MutSet, SendMap}; use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::ident::Ident; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast::{AssignedField, Tag, TypeAnnotation}; use roc_parse::ast::{AssignedField, Tag, TypeAnnotation};
use roc_region::all::Located; use roc_region::all::{Located, Region};
use roc_region::all::Region;
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Problem, Type}; use roc_types::types::{Alias, Problem, Type};

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,7 @@ use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern};
use crate::procedure::References; use crate::procedure::References;
use crate::scope::Scope; use crate::scope::Scope;
use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase}; use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast; use roc_parse::ast;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
@ -41,9 +41,7 @@ pub struct Annotation {
#[derive(Debug)] #[derive(Debug)]
pub struct CanDefs { pub struct CanDefs {
// TODO don't store the Ident in here (lots of cloning!) - instead, pub refs_by_symbol: MutMap<Symbol, (Region, References)>,
// make refs_by_symbol be something like MutMap<Symbol, (Region, References)>
pub refs_by_symbol: MutMap<Symbol, (Located<Ident>, References)>,
pub can_defs_by_symbol: MutMap<Symbol, Def>, pub can_defs_by_symbol: MutMap<Symbol, Def>,
pub aliases: SendMap<Symbol, Alias>, pub aliases: SendMap<Symbol, Alias>,
} }
@ -79,7 +77,10 @@ enum PendingDef<'a> {
ann: &'a Located<ast::TypeAnnotation<'a>>, ann: &'a Located<ast::TypeAnnotation<'a>>,
}, },
ShadowedAlias, /// An invalid alias, that is ignored in the rest of the pipeline
/// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int`
/// with an incorrect pattern
InvalidAlias,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -87,10 +88,8 @@ enum PendingDef<'a> {
pub enum Declaration { pub enum Declaration {
Declare(Def), Declare(Def),
DeclareRec(Vec<Def>), DeclareRec(Vec<Def>),
InvalidCycle( Builtin(Def),
Vec<Located<Ident>>, InvalidCycle(Vec<Symbol>, Vec<(Region /* pattern */, Region /* expr */)>),
Vec<(Region /* pattern */, Region /* expr */)>,
),
} }
impl Declaration { impl Declaration {
@ -100,6 +99,7 @@ impl Declaration {
Declare(_) => 1, Declare(_) => 1,
DeclareRec(defs) => defs.len(), DeclareRec(defs) => defs.len(),
InvalidCycle(_, _) => 0, InvalidCycle(_, _) => 0,
Builtin(_) => 0,
} }
} }
} }
@ -153,7 +153,7 @@ pub fn canonicalize_defs<'a>(
match iter.peek() { match iter.peek() {
Some(Located { Some(Located {
value: Body(body_pattern, body_expr), value: Body(body_pattern, body_expr),
.. region: body_region,
}) => { }) => {
if pattern.value.equivalent(&body_pattern.value) { if pattern.value.equivalent(&body_pattern.value) {
iter.next(); iter.next();
@ -167,6 +167,10 @@ pub fn canonicalize_defs<'a>(
&mut scope, &mut scope,
pattern_type, pattern_type,
) )
} else if loc_def.region.lines_between(body_region) > 1 {
// there is a line of whitespace between the annotation and the body
// treat annotation and body separately
to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type)
} else { } else {
// the pattern of the annotation does not match the pattern of the body directly below it // the pattern of the annotation does not match the pattern of the body directly below it
env.problems.push(Problem::SignatureDefMismatch { env.problems.push(Problem::SignatureDefMismatch {
@ -557,17 +561,18 @@ pub fn sort_can_defs(
if is_invalid_cycle { if is_invalid_cycle {
// We want to show the entire cycle in the error message, so expand it out. // We want to show the entire cycle in the error message, so expand it out.
let mut loc_idents_in_cycle: Vec<Located<Ident>> = Vec::new(); let mut loc_symbols = Vec::new();
for symbol in cycle { for symbol in cycle {
let refs = refs_by_symbol.get(&symbol).unwrap_or_else(|| { match refs_by_symbol.get(&symbol) {
panic!( None => unreachable!(
"Symbol not found in refs_by_symbol: {:?} - refs_by_symbol was: {:?}", r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#,
symbol, refs_by_symbol symbol, refs_by_symbol
) ),
}); Some((region, _)) => {
loc_symbols.push(Located::at(*region, symbol));
loc_idents_in_cycle.push(refs.0.clone()); }
}
} }
let mut regions = Vec::with_capacity(can_defs_by_symbol.len()); let mut regions = Vec::with_capacity(can_defs_by_symbol.len());
@ -575,16 +580,19 @@ pub fn sort_can_defs(
regions.push((def.loc_pattern.region, def.loc_expr.region)); regions.push((def.loc_pattern.region, def.loc_expr.region));
} }
// Sort them to make the report more helpful. // Sort them by line number to make the report more helpful.
loc_idents_in_cycle.sort(); loc_symbols.sort();
regions.sort(); regions.sort();
let symbols_in_cycle: Vec<Symbol> =
loc_symbols.into_iter().map(|s| s.value).collect();
problems.push(Problem::RuntimeError(RuntimeError::CircularDef( problems.push(Problem::RuntimeError(RuntimeError::CircularDef(
loc_idents_in_cycle.clone(), symbols_in_cycle.clone(),
regions.clone(), regions.clone(),
))); )));
declarations.push(Declaration::InvalidCycle(loc_idents_in_cycle, regions)); declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions));
} else { } else {
// slightly inefficient, because we know this becomes exactly one DeclareRec already // slightly inefficient, because we know this becomes exactly one DeclareRec already
group_to_declaration( group_to_declaration(
@ -717,6 +725,7 @@ fn pattern_to_vars_by_symbol(
| FloatLiteral(_) | FloatLiteral(_)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => {} | UnsupportedPattern(_) => {}
Shadowed(_, _) => {} Shadowed(_, _) => {}
@ -733,7 +742,7 @@ fn canonicalize_pending_def<'a>(
scope: &mut Scope, scope: &mut Scope,
can_defs_by_symbol: &mut MutMap<Symbol, Def>, can_defs_by_symbol: &mut MutMap<Symbol, Def>,
var_store: &mut VarStore, var_store: &mut VarStore,
refs_by_symbol: &mut MutMap<Symbol, (Located<Ident>, References)>, refs_by_symbol: &mut MutMap<Symbol, (Region, References)>,
aliases: &mut SendMap<Symbol, Alias>, aliases: &mut SendMap<Symbol, Alias>,
) -> Output { ) -> Output {
use PendingDef::*; use PendingDef::*;
@ -899,9 +908,8 @@ fn canonicalize_pending_def<'a>(
.union(&can_ann.introduced_variables); .union(&can_ann.introduced_variables);
} }
ShadowedAlias => { InvalidAlias => {
// Since this alias was shadowed, it gets ignored and has no // invalid aliases (shadowed, incorrect patterns) get ignored
// effect on the output.
} }
TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
let ann = let ann =
@ -1001,7 +1009,7 @@ fn canonicalize_pending_def<'a>(
// Store the referenced locals in the refs_by_symbol map, so we can later figure out // Store the referenced locals in the refs_by_symbol map, so we can later figure out
// which defined names reference each other. // which defined names reference each other.
for (ident, (symbol, region)) in scope.idents() { for (_, (symbol, region)) in scope.idents() {
if !vars_by_symbol.contains_key(&symbol) { if !vars_by_symbol.contains_key(&symbol) {
continue; continue;
} }
@ -1016,16 +1024,7 @@ fn canonicalize_pending_def<'a>(
can_output.references.clone() can_output.references.clone()
}; };
refs_by_symbol.insert( refs_by_symbol.insert(*symbol, (*region, refs));
*symbol,
(
Located {
value: ident.clone(),
region: *region,
},
refs,
),
);
can_defs_by_symbol.insert( can_defs_by_symbol.insert(
*symbol, *symbol,
@ -1146,23 +1145,7 @@ fn canonicalize_pending_def<'a>(
can_output.references.clone() can_output.references.clone()
}; };
let ident = env refs_by_symbol.insert(symbol, (region, refs));
.ident_ids
.get_name(symbol.ident_id())
.unwrap_or_else(|| {
panic!("Could not find {:?} in env.ident_ids", symbol);
});
refs_by_symbol.insert(
symbol,
(
Located {
value: ident.clone().into(),
region,
},
refs,
),
);
can_defs_by_symbol.insert( can_defs_by_symbol.insert(
symbol, symbol,
@ -1258,6 +1241,10 @@ fn decl_to_let(
Declaration::InvalidCycle(symbols, regions) => { Declaration::InvalidCycle(symbols, regions) => {
Expr::RuntimeError(RuntimeError::CircularDef(symbols, regions)) Expr::RuntimeError(RuntimeError::CircularDef(symbols, regions))
} }
Declaration::Builtin(_) => {
// Builtins should only be added to top-level decls, not to let-exprs!
unreachable!()
}
} }
} }
@ -1365,7 +1352,13 @@ fn to_pending_def<'a>(
}); });
} }
_ => { _ => {
panic!("TODO gracefully handle an invalid pattern appearing where a type alias rigid var should be."); // any other pattern in this position is a syntax error.
env.problems.push(Problem::InvalidAliasRigid {
alias_name: symbol,
region: loc_var.region,
});
return PendingDef::InvalidAlias;
} }
} }
} }
@ -1386,7 +1379,7 @@ fn to_pending_def<'a>(
shadow: loc_shadowed_symbol, shadow: loc_shadowed_symbol,
}); });
PendingDef::ShadowedAlias PendingDef::InvalidAlias
} }
} }
} }

View file

@ -1,4 +1,5 @@
use crate::annotation::IntroducedVariables; use crate::annotation::IntroducedVariables;
use crate::builtins::builtin_defs;
use crate::def::{can_defs_with_return, Def}; use crate::def::{can_defs_with_return, Def};
use crate::env::Env; use crate::env::Env;
use crate::num::{ use crate::num::{
@ -10,6 +11,7 @@ use crate::procedure::References;
use crate::scope::Scope; use crate::scope::Scope;
use roc_collections::all::{ImSet, MutMap, MutSet, SendMap}; use roc_collections::all::{ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia; use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast; use roc_parse::ast;
@ -20,7 +22,6 @@ use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias; use roc_types::types::Alias;
use std::fmt::Debug; use std::fmt::Debug;
use std::i64; use std::i64;
use std::ops::Neg;
#[derive(Clone, Default, Debug, PartialEq)] #[derive(Clone, Default, Debug, PartialEq)]
pub struct Output { pub struct Output {
@ -89,6 +90,11 @@ pub enum Expr {
Vec<(Variable, Located<Expr>)>, Vec<(Variable, Located<Expr>)>,
CalledVia, CalledVia,
), ),
RunLowLevel {
op: LowLevel,
args: Vec<(Variable, Expr)>,
ret_var: Variable,
},
Closure( Closure(
Variable, Variable,
@ -177,12 +183,13 @@ pub fn canonicalize_expr<'a>(
let (expr, output) = match expr { let (expr, output) = match expr {
ast::Expr::Num(string) => { ast::Expr::Num(string) => {
let answer = num_expr_from_result(var_store, finish_parsing_int(*string), env); let answer = num_expr_from_result(var_store, finish_parsing_int(*string), region, env);
(answer, Output::default()) (answer, Output::default())
} }
ast::Expr::Float(string) => { ast::Expr::Float(string) => {
let answer = float_expr_from_result(var_store, finish_parsing_float(string), env); let answer =
float_expr_from_result(var_store, finish_parsing_float(string), region, env);
(answer, Output::default()) (answer, Output::default())
} }
@ -623,13 +630,9 @@ pub fn canonicalize_expr<'a>(
base, base,
is_negative, is_negative,
} => { } => {
let mut result = finish_parsing_base(string, *base); // the minus sign is added before parsing, to get correct overflow/underflow behavior
let result = finish_parsing_base(string, *base, *is_negative);
if *is_negative { let answer = int_expr_from_result(var_store, result, region, *base, env);
result = result.map(i64::neg);
}
let answer = int_expr_from_result(var_store, result, env);
(answer, Output::default()) (answer, Output::default())
} }
@ -1007,3 +1010,290 @@ fn canonicalize_lookup(
(can_expr, output) (can_expr, output)
} }
/// Currently uses the heuristic of "only inline if it's a builtin"
pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> Expr {
use Expr::*;
match expr {
// Num stores the `a` variable in `Num a`. Not the same as the variable
// stored in Int and Float below, which is strictly for better error messages
other @ Num(_, _)
| other @ Int(_, _)
| other @ Float(_, _)
| other @ Str(_)
| other @ BlockStr(_)
| other @ RuntimeError(_)
| other @ EmptyRecord
| other @ Accessor { .. }
| other @ Update { .. }
| other @ Var(_)
| other @ RunLowLevel { .. } => other,
List {
elem_var,
loc_elems,
} => {
let mut new_elems = Vec::with_capacity(loc_elems.len());
for loc_elem in loc_elems {
let value = inline_calls(var_store, scope, loc_elem.value);
new_elems.push(Located {
value,
region: loc_elem.region,
});
}
List {
elem_var,
loc_elems: new_elems,
}
}
// Branching
When {
cond_var,
expr_var,
region,
loc_cond,
branches,
} => {
let loc_cond = Box::new(Located {
region: loc_cond.region,
value: inline_calls(var_store, scope, loc_cond.value),
});
let mut new_branches = Vec::with_capacity(branches.len());
for branch in branches {
let value = Located {
value: inline_calls(var_store, scope, branch.value.value),
region: branch.value.region,
};
let guard = match branch.guard {
Some(loc_expr) => Some(Located {
region: loc_expr.region,
value: inline_calls(var_store, scope, loc_expr.value),
}),
None => None,
};
let new_branch = WhenBranch {
patterns: branch.patterns,
value,
guard,
};
new_branches.push(new_branch);
}
When {
cond_var,
expr_var,
region,
loc_cond,
branches: new_branches,
}
}
If {
cond_var,
branch_var,
branches,
final_else,
} => {
let mut new_branches = Vec::with_capacity(branches.len());
for (loc_cond, loc_expr) in branches {
let loc_cond = Located {
value: inline_calls(var_store, scope, loc_cond.value),
region: loc_cond.region,
};
let loc_expr = Located {
value: inline_calls(var_store, scope, loc_expr.value),
region: loc_expr.region,
};
new_branches.push((loc_cond, loc_expr));
}
let final_else = Box::new(Located {
region: final_else.region,
value: inline_calls(var_store, scope, final_else.value),
});
If {
cond_var,
branch_var,
branches: new_branches,
final_else,
}
}
LetRec(defs, loc_expr, var, aliases) => {
let mut new_defs = Vec::with_capacity(defs.len());
for def in defs {
new_defs.push(Def {
loc_pattern: def.loc_pattern,
loc_expr: Located {
region: def.loc_expr.region,
value: inline_calls(var_store, scope, def.loc_expr.value),
},
expr_var: def.expr_var,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
});
}
let loc_expr = Located {
region: loc_expr.region,
value: inline_calls(var_store, scope, loc_expr.value),
};
LetRec(new_defs, Box::new(loc_expr), var, aliases)
}
LetNonRec(def, loc_expr, var, aliases) => {
let def = Def {
loc_pattern: def.loc_pattern,
loc_expr: Located {
region: def.loc_expr.region,
value: inline_calls(var_store, scope, def.loc_expr.value),
},
expr_var: def.expr_var,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
};
let loc_expr = Located {
region: loc_expr.region,
value: inline_calls(var_store, scope, loc_expr.value),
};
LetNonRec(Box::new(def), Box::new(loc_expr), var, aliases)
}
Closure(var, symbol, recursive, patterns, boxed_expr) => {
let (loc_expr, expr_var) = *boxed_expr;
let loc_expr = Located {
value: inline_calls(var_store, scope, loc_expr.value),
region: loc_expr.region,
};
Closure(
var,
symbol,
recursive,
patterns,
Box::new((loc_expr, expr_var)),
)
}
Record { record_var, fields } => {
todo!(
"Inlining for Record with record_var {:?} and fields {:?}",
record_var,
fields
);
}
Access {
record_var,
ext_var,
field_var,
loc_expr,
field,
} => {
todo!("Inlining for Access with record_var {:?}, ext_var {:?}, field_var {:?}, loc_expr {:?}, field {:?}", record_var, ext_var, field_var, loc_expr, field);
}
Tag {
variant_var,
ext_var,
name,
arguments,
} => {
todo!(
"Inlining for Tag with variant_var {:?}, ext_var {:?}, name {:?}, arguments {:?}",
variant_var,
ext_var,
name,
arguments
);
}
Call(boxed_tuple, args, called_via) => {
let (fn_var, loc_expr, expr_var) = *boxed_tuple;
match loc_expr.value {
Var(symbol) if symbol.is_builtin() => match builtin_defs(var_store).get(&symbol) {
Some(Def {
loc_expr:
Located {
value: Closure(_var, _, recursive, params, boxed_body),
..
},
..
}) => {
debug_assert_eq!(*recursive, Recursive::NotRecursive);
// Since this is a canonicalized Expr, we should have
// already detected any arity mismatches and replaced this
// with a RuntimeError if there was a mismatch.
debug_assert_eq!(params.len(), args.len());
// Start with the function's body as the answer.
let (mut loc_answer, _body_var) = *boxed_body.clone();
// Wrap the body in one LetNonRec for each argument,
// such that at the end we have all the arguments in
// scope with the values the caller provided.
for ((_param_var, loc_pattern), (expr_var, loc_expr)) in
params.iter().cloned().zip(args.into_iter()).rev()
{
// TODO get the correct vars into here.
// Not sure if param_var should be involved.
let pattern_vars = SendMap::default();
// TODO get the actual correct aliases
let aliases = SendMap::default();
let def = Def {
loc_pattern,
loc_expr,
expr_var,
pattern_vars,
annotation: None,
};
loc_answer = Located {
region: Region::zero(),
value: LetNonRec(
Box::new(def),
Box::new(loc_answer),
var_store.fresh(),
aliases,
),
};
}
loc_answer.value
}
Some(_) => {
unreachable!("Tried to inline a non-function");
}
None => {
unreachable!(
"Tried to inline a builtin that wasn't registered: {:?}",
symbol
);
}
},
_ => {
// For now, we only inline calls to builtins. Leave this alone!
Call(Box::new((fn_var, loc_expr, expr_var)), args, called_via)
}
}
}
}
}

View file

@ -24,6 +24,3 @@ pub mod pattern;
pub mod procedure; pub mod procedure;
pub mod scope; pub mod scope;
pub mod string; pub mod string;
#[macro_use]
extern crate roc_collections;

View file

@ -137,11 +137,16 @@ pub fn canonicalize_module_defs<'a>(
let mut references = MutSet::default(); let mut references = MutSet::default();
// Gather up all the symbols that were referenced across all the defs. // Gather up all the symbols that were referenced across all the defs' lookups.
for symbol in output.references.lookups.iter() { for symbol in output.references.lookups.iter() {
references.insert(*symbol); references.insert(*symbol);
} }
// Gather up all the symbols that were referenced across all the defs' calls.
for symbol in output.references.calls.iter() {
references.insert(*symbol);
}
// Gather up all the symbols that were referenced from other modules. // Gather up all the symbols that were referenced from other modules.
for symbol in env.referenced_symbols.iter() { for symbol in env.referenced_symbols.iter() {
references.insert(*symbol); references.insert(*symbol);
@ -151,6 +156,7 @@ pub fn canonicalize_module_defs<'a>(
(Ok(declarations), output) => { (Ok(declarations), output) => {
use crate::def::Declaration::*; use crate::def::Declaration::*;
// Record the variables for all exposed symbols.
let mut exposed_vars_by_symbol = Vec::with_capacity(exposed_symbols.len()); let mut exposed_vars_by_symbol = Vec::with_capacity(exposed_symbols.len());
for decl in declarations.iter() { for decl in declarations.iter() {
@ -193,6 +199,14 @@ pub fn canonicalize_module_defs<'a>(
InvalidCycle(identifiers, _) => { InvalidCycle(identifiers, _) => {
panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers); panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers);
} }
Builtin(def) => {
// Builtins cannot be exposed in module declarations.
// This should never happen!
debug_assert!(def
.pattern_vars
.iter()
.all(|(symbol, _)| !exposed_symbols.contains(symbol)));
}
} }
} }
@ -222,6 +236,11 @@ pub fn canonicalize_module_defs<'a>(
references.insert(symbol); references.insert(symbol);
} }
// Incorporate any remaining output.calls entries into references.
for symbol in output.references.calls {
references.insert(symbol);
}
Ok(ModuleOutput { Ok(ModuleOutput {
aliases, aliases,
rigid_variables, rigid_variables,

View file

@ -3,22 +3,30 @@ use crate::expr::Expr;
use roc_parse::ast::Base; use roc_parse::ast::Base;
use roc_problem::can::Problem; use roc_problem::can::Problem;
use roc_problem::can::RuntimeError::*; use roc_problem::can::RuntimeError::*;
use roc_problem::can::{FloatErrorKind, IntErrorKind};
use roc_region::all::Region;
use roc_types::subs::VarStore; use roc_types::subs::VarStore;
use std::i64; use std::i64;
// TODO use rust's integer parsing again
//
// We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639
// There is a nightly API for exposing the parse error.
#[inline(always)] #[inline(always)]
pub fn num_expr_from_result( pub fn num_expr_from_result(
var_store: &mut VarStore, var_store: &mut VarStore,
result: Result<i64, &str>, result: Result<i64, (&str, IntErrorKind)>,
region: Region,
env: &mut Env, env: &mut Env,
) -> Expr { ) -> Expr {
match result { match result {
Ok(int) => Expr::Num(var_store.fresh(), int), Ok(int) => Expr::Num(var_store.fresh(), int),
Err(raw) => { Err((raw, error)) => {
// (Num *) compiles to Int if it doesn't // (Num *) compiles to Int if it doesn't
// get specialized to something else first, // get specialized to something else first,
// so use int's overflow bounds here. // so use int's overflow bounds here.
let runtime_error = IntOutsideRange(raw.into()); let runtime_error = InvalidInt(error, Base::Decimal, region, raw.into());
env.problem(Problem::RuntimeError(runtime_error.clone())); env.problem(Problem::RuntimeError(runtime_error.clone()));
@ -30,14 +38,16 @@ pub fn num_expr_from_result(
#[inline(always)] #[inline(always)]
pub fn int_expr_from_result( pub fn int_expr_from_result(
var_store: &mut VarStore, var_store: &mut VarStore,
result: Result<i64, &str>, result: Result<i64, (&str, IntErrorKind)>,
region: Region,
base: Base,
env: &mut Env, env: &mut Env,
) -> Expr { ) -> Expr {
// Int stores a variable to generate better error messages // Int stores a variable to generate better error messages
match result { match result {
Ok(int) => Expr::Int(var_store.fresh(), int), Ok(int) => Expr::Int(var_store.fresh(), int),
Err(raw) => { Err((raw, error)) => {
let runtime_error = IntOutsideRange(raw.into()); let runtime_error = InvalidInt(error, base, region, raw.into());
env.problem(Problem::RuntimeError(runtime_error.clone())); env.problem(Problem::RuntimeError(runtime_error.clone()));
@ -49,14 +59,15 @@ pub fn int_expr_from_result(
#[inline(always)] #[inline(always)]
pub fn float_expr_from_result( pub fn float_expr_from_result(
var_store: &mut VarStore, var_store: &mut VarStore,
result: Result<f64, &str>, result: Result<f64, (&str, FloatErrorKind)>,
region: Region,
env: &mut Env, env: &mut Env,
) -> Expr { ) -> Expr {
// Float stores a variable to generate better error messages // Float stores a variable to generate better error messages
match result { match result {
Ok(float) => Expr::Float(var_store.fresh(), float), Ok(float) => Expr::Float(var_store.fresh(), float),
Err(raw) => { Err((raw, error)) => {
let runtime_error = FloatOutsideRange(raw.into()); let runtime_error = InvalidFloat(error, region, raw.into());
env.problem(Problem::RuntimeError(runtime_error.clone())); env.problem(Problem::RuntimeError(runtime_error.clone()));
@ -66,28 +77,183 @@ pub fn float_expr_from_result(
} }
#[inline(always)] #[inline(always)]
pub fn finish_parsing_int(raw: &str) -> Result<i64, &str> { pub fn finish_parsing_int(raw: &str) -> Result<i64, (&str, IntErrorKind)> {
// Ignore underscores. // Ignore underscores.
raw.replace("_", "").parse::<i64>().map_err(|_| raw) let radix = 10;
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind))
} }
#[inline(always)] #[inline(always)]
pub fn finish_parsing_base(raw: &str, base: Base) -> Result<i64, &str> { pub fn finish_parsing_base(
raw: &str,
base: Base,
is_negative: bool,
) -> Result<i64, (&str, IntErrorKind)> {
let radix = match base { let radix = match base {
Base::Hex => 16, Base::Hex => 16,
Base::Decimal => 10,
Base::Octal => 8, Base::Octal => 8,
Base::Binary => 2, Base::Binary => 2,
}; };
// Ignore underscores. // Ignore underscores, insert - when negative to get correct underflow/overflow behavior
i64::from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|_| raw) (if is_negative {
from_str_radix::<i64>(format!("-{}", raw.replace("_", "")).as_str(), radix)
} else {
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix)
})
.map_err(|e| (raw, e.kind))
} }
#[inline(always)] #[inline(always)]
pub fn finish_parsing_float(raw: &str) -> Result<f64, &str> { pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
// Ignore underscores. // Ignore underscores.
match raw.replace("_", "").parse::<f64>() { match raw.replace("_", "").parse::<f64>() {
Ok(float) if float.is_finite() => Ok(float), Ok(float) if float.is_finite() => Ok(float),
_ => Err(raw), Ok(float) => {
if float.is_sign_positive() {
Err((raw, FloatErrorKind::PositiveInfinity))
} else {
Err((raw, FloatErrorKind::NegativeInfinity))
}
}
Err(_) => Err((raw, FloatErrorKind::Error)),
}
}
/// Integer parsing code taken from the rust libcore,
/// pulled in so we can give custom error messages
///
/// The Rust Project is dual-licensed under Apache 2.0 and MIT terms.
/// As roc is Apache licensed, we use this rust code under the apache 2.0 license
///
/// Thanks to the rust-lang project and its contributors
trait FromStrRadixHelper: PartialOrd + Copy {
fn min_value() -> Self;
fn max_value() -> Self;
fn from_u32(u: u32) -> Self;
fn checked_mul(&self, other: u32) -> Option<Self>;
fn checked_sub(&self, other: u32) -> Option<Self>;
fn checked_add(&self, other: u32) -> Option<Self>;
}
macro_rules! doit {
($($t:ty)*) => ($(impl FromStrRadixHelper for $t {
#[inline]
fn min_value() -> Self { Self::min_value() }
#[inline]
fn max_value() -> Self { Self::max_value() }
#[inline]
fn from_u32(u: u32) -> Self { u as Self }
#[inline]
fn checked_mul(&self, other: u32) -> Option<Self> {
Self::checked_mul(*self, other as Self)
}
#[inline]
fn checked_sub(&self, other: u32) -> Option<Self> {
Self::checked_sub(*self, other as Self)
}
#[inline]
fn checked_add(&self, other: u32) -> Option<Self> {
Self::checked_add(*self, other as Self)
}
})*)
}
// We only need the i64 implementation, but libcore defines
// doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
doit! { i64 }
fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, ParseIntError> {
use self::IntErrorKind::*;
use self::ParseIntError as PIE;
assert!(
radix >= 2 && radix <= 36,
"from_str_radix_int: must lie in the range `[2, 36]` - found {}",
radix
);
if src.is_empty() {
return Err(PIE { kind: Empty });
}
let is_signed_ty = T::from_u32(0) > T::min_value();
// all valid digits are ascii, so we will just iterate over the utf8 bytes
// and cast them to chars. .to_digit() will safely return None for anything
// other than a valid ascii digit for the given radix, including the first-byte
// of multi-byte sequences
let src = src.as_bytes();
let (is_positive, digits) = match src[0] {
b'+' => (true, &src[1..]),
b'-' if is_signed_ty => (false, &src[1..]),
_ => (true, src),
};
if digits.is_empty() {
return Err(PIE { kind: Empty });
}
let mut result = T::from_u32(0);
if is_positive {
// The number is positive
for &c in digits {
let x = match (c as char).to_digit(radix) {
Some(x) => x,
None => return Err(PIE { kind: InvalidDigit }),
};
result = match result.checked_mul(radix) {
Some(result) => result,
None => return Err(PIE { kind: Overflow }),
};
result = match result.checked_add(x) {
Some(result) => result,
None => return Err(PIE { kind: Overflow }),
};
}
} else {
// The number is negative
for &c in digits {
let x = match (c as char).to_digit(radix) {
Some(x) => x,
None => return Err(PIE { kind: InvalidDigit }),
};
result = match result.checked_mul(radix) {
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
};
result = match result.checked_sub(x) {
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
};
}
}
Ok(result)
}
/// An error which can be returned when parsing an integer.
///
/// This error is used as the error type for the `from_str_radix()` functions
/// on the primitive integer types, such as [`i8::from_str_radix`].
///
/// # Potential causes
///
/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace
/// in the string e.g., when it is obtained from the standard input.
/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing.
///
/// [`str.trim()`]: ../../std/primitive.str.html#method.trim
/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseIntError {
kind: IntErrorKind,
}
impl ParseIntError {
/// Outputs the detailed cause of parsing an integer failing.
pub fn kind(&self) -> &IntErrorKind {
&self.kind
} }
} }

View file

@ -300,7 +300,7 @@ fn desugar_field<'a>(
} }
} }
// TODO move this desugaring to canonicalization, to avoid dealing with strings as much // TODO move this desugaring to canonicalization, so we can use Symbols instead of strings
#[inline(always)] #[inline(always)]
fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
use self::BinOp::*; use self::BinOp::*;
@ -308,8 +308,8 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
match binop { match binop {
Caret => (ModuleName::NUM, "pow"), Caret => (ModuleName::NUM, "pow"),
Star => (ModuleName::NUM, "mul"), Star => (ModuleName::NUM, "mul"),
Slash => (ModuleName::FLOAT, "div"), Slash => (ModuleName::NUM, "div"),
DoubleSlash => (ModuleName::INT, "div"), DoubleSlash => (ModuleName::NUM, "divFloor"),
Percent => (ModuleName::NUM, "rem"), Percent => (ModuleName::NUM, "rem"),
DoublePercent => (ModuleName::NUM, "mod"), DoublePercent => (ModuleName::NUM, "mod"),
Plus => (ModuleName::NUM, "add"), Plus => (ModuleName::NUM, "add"),

View file

@ -5,7 +5,7 @@ use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast; use roc_parse::ast;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
@ -35,6 +35,8 @@ pub enum Pattern {
Shadowed(Region, Located<Ident>), Shadowed(Region, Located<Ident>),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region), UnsupportedPattern(Region),
// parse error patterns
MalformedPattern(MalformedPatternProblem, Region),
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -76,6 +78,7 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
| FloatLiteral(_) | FloatLiteral(_)
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_) => {} | UnsupportedPattern(_) => {}
Shadowed(_, _) => {} Shadowed(_, _) => {}
@ -165,32 +168,30 @@ pub fn canonicalize_pattern<'a>(
} }
FloatLiteral(ref string) => match pattern_type { FloatLiteral(ref string) => match pattern_type {
WhenBranch => { WhenBranch => match finish_parsing_float(string) {
let float = finish_parsing_float(string) Err(_error) => {
.unwrap_or_else(|_| panic!("TODO handle malformed float pattern")); let problem = MalformedPatternProblem::MalformedFloat;
malformed_pattern(env, problem, region)
Pattern::FloatLiteral(float) }
} Ok(float) => Pattern::FloatLiteral(float),
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { },
unsupported_pattern(env, ptype, region) ptype => unsupported_pattern(env, ptype, region),
}
}, },
Underscore => match pattern_type { Underscore => match pattern_type {
WhenBranch | FunctionArg => Pattern::Underscore, WhenBranch | FunctionArg => Pattern::Underscore,
ptype @ DefExpr | ptype @ TopLevelDef => unsupported_pattern(env, ptype, region), ptype => unsupported_pattern(env, ptype, region),
}, },
NumLiteral(string) => match pattern_type { NumLiteral(string) => match pattern_type {
WhenBranch => { WhenBranch => match finish_parsing_int(string) {
let int = finish_parsing_int(string) Err(_error) => {
.unwrap_or_else(|_| panic!("TODO handle malformed int pattern")); let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region)
Pattern::NumLiteral(var_store.fresh(), int) }
} Ok(int) => Pattern::NumLiteral(var_store.fresh(), int),
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { },
unsupported_pattern(env, ptype, region) ptype => unsupported_pattern(env, ptype, region),
}
}, },
NonBase10Literal { NonBase10Literal {
@ -198,29 +199,33 @@ pub fn canonicalize_pattern<'a>(
base, base,
is_negative, is_negative,
} => match pattern_type { } => match pattern_type {
WhenBranch => { WhenBranch => match finish_parsing_base(string, *base, *is_negative) {
let int = finish_parsing_base(string, *base) Err(_error) => {
.unwrap_or_else(|_| panic!("TODO handle malformed {:?} pattern", base)); let problem = MalformedPatternProblem::MalformedBase(*base);
malformed_pattern(env, problem, region)
if *is_negative {
Pattern::IntLiteral(-int)
} else {
Pattern::IntLiteral(int)
} }
} Ok(int) => {
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { if *is_negative {
unsupported_pattern(env, ptype, region) Pattern::IntLiteral(-int)
} } else {
Pattern::IntLiteral(int)
}
}
},
ptype => unsupported_pattern(env, ptype, region),
}, },
StrLiteral(_string) => match pattern_type { StrLiteral(string) => match pattern_type {
WhenBranch => { WhenBranch => {
panic!("TODO check whether string pattern is malformed."); // TODO report whether string was malformed
// Pattern::StrLiteral((*string).into()) Pattern::StrLiteral((*string).into())
}
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
unsupported_pattern(env, ptype, region)
} }
ptype => unsupported_pattern(env, ptype, region),
},
BlockStrLiteral(_lines) => match pattern_type {
WhenBranch => todo!("TODO block string literal pattern"),
ptype => unsupported_pattern(env, ptype, region),
}, },
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => { SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => {
@ -288,7 +293,7 @@ pub fn canonicalize_pattern<'a>(
}, },
}); });
} }
_ => panic!("invalid pattern in record"), _ => unreachable!("Any other pattern should have given a parse error"),
} }
} }
@ -304,7 +309,15 @@ pub fn canonicalize_pattern<'a>(
unreachable!("should have been handled in RecordDestructure"); unreachable!("should have been handled in RecordDestructure");
} }
_ => panic!("TODO finish restoring can_pattern branch for {:?}", pattern), Malformed(_str) => {
let problem = MalformedPatternProblem::Unknown;
malformed_pattern(env, problem, region)
}
QualifiedIdentifier { .. } => {
let problem = MalformedPatternProblem::QualifiedIdentifier;
malformed_pattern(env, problem, region)
}
}; };
Located { Located {
@ -325,6 +338,20 @@ fn unsupported_pattern<'a>(
Pattern::UnsupportedPattern(region) Pattern::UnsupportedPattern(region)
} }
/// When we detect a malformed pattern like `3.X` or `0b5`,
/// report it to Env and return an UnsupportedPattern runtime error pattern.
fn malformed_pattern<'a>(
env: &mut Env<'a>,
problem: MalformedPatternProblem,
region: Region,
) -> Pattern {
env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern(
problem, region,
)));
Pattern::MalformedPattern(problem, region)
}
pub fn bindings_from_patterns<'a, I>(loc_patterns: I, scope: &Scope) -> Vec<(Symbol, Region)> pub fn bindings_from_patterns<'a, I>(loc_patterns: I, scope: &Scope) -> Vec<(Symbol, Region)>
where where
I: Iterator<Item = &'a Located<Pattern>>, I: Iterator<Item = &'a Located<Pattern>>,
@ -374,6 +401,7 @@ fn add_bindings_from_patterns(
| StrLiteral(_) | StrLiteral(_)
| Underscore | Underscore
| Shadowed(_, _) | Shadowed(_, _)
| MalformedPattern(_, _)
| UnsupportedPattern(_) => (), | UnsupportedPattern(_) => (),
} }
} }

View file

@ -0,0 +1,110 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate roc_can;
extern crate roc_parse;
extern crate roc_region;
mod helpers;
#[cfg(test)]
mod can_inline {
use crate::helpers::{can_expr_with, test_home};
use bumpalo::Bump;
use roc_can::expr::inline_calls;
use roc_can::expr::Expr::{self, *};
use roc_can::scope::Scope;
use roc_types::subs::VarStore;
fn assert_inlines_to(input: &str, expected: Expr, var_store: &mut VarStore) {
let arena = Bump::new();
let scope = &mut Scope::new(test_home());
let actual_out = can_expr_with(&arena, test_home(), input);
let actual = inline_calls(var_store, scope, actual_out.loc_expr.value);
assert_eq!(actual, expected);
}
#[test]
fn inline_empty_record() {
// fn inline_list_len() {
let var_store = &mut VarStore::default();
assert_inlines_to(
indoc!(
r#"
{}
"#
),
EmptyRecord,
var_store,
);
// TODO testing with hardcoded variables is very brittle.
// Should find a better way to test this!
// (One idea would be to traverse both Exprs and zero out all the Variables,
// so they always pass equality.)
// let aliases = SendMap::default();
// assert_inlines_to(
// indoc!(
// r#"
// Int.isZero 5
// "#
// ),
// LetNonRec(
// Box::new(Def {
// loc_pattern: Located {
// region: Region::zero(),
// value: Pattern::Identifier(Symbol::ARG_1),
// },
// pattern_vars: SendMap::default(),
// loc_expr: Located {
// region: Region::new(0, 0, 11, 12),
// value: Num(unsafe { Variable::unsafe_test_debug_variable(7) }, 5),
// },
// expr_var: unsafe { Variable::unsafe_test_debug_variable(8) },
// annotation: None,
// }),
// Box::new(Located {
// region: Region::zero(),
// value: Expr::Call(
// Box::new((
// unsafe { Variable::unsafe_test_debug_variable(138) },
// Located {
// region: Region::zero(),
// value: Expr::Var(Symbol::BOOL_EQ),
// },
// unsafe { Variable::unsafe_test_debug_variable(139) },
// )),
// vec![
// (
// unsafe { Variable::unsafe_test_debug_variable(140) },
// Located {
// region: Region::zero(),
// value: Var(Symbol::ARG_1),
// },
// ),
// (
// unsafe { Variable::unsafe_test_debug_variable(141) },
// Located {
// region: Region::zero(),
// value: Int(
// unsafe { Variable::unsafe_test_debug_variable(137) },
// 0,
// ),
// },
// ),
// ],
// CalledVia::Space,
// ),
// }),
// unsafe { Variable::unsafe_test_debug_variable(198) },
// aliases,
// ),
// var_store,
// )
}
}

View file

@ -16,8 +16,8 @@ mod test_can {
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::expr::Expr::{self, *}; use roc_can::expr::Expr::{self, *};
use roc_can::expr::Recursive; use roc_can::expr::Recursive;
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::Region;
use std::{f64, i64}; use std::{f64, i64};
fn assert_can(input: &str, expected: Expr) { fn assert_can(input: &str, expected: Expr) {
@ -73,41 +73,65 @@ mod test_can {
#[test] #[test]
fn int_too_large() { fn int_too_large() {
use roc_parse::ast::Base;
let string = (i64::MAX as i128 + 1).to_string(); let string = (i64::MAX as i128 + 1).to_string();
assert_can( assert_can(
&string.clone(), &string.clone(),
RuntimeError(RuntimeError::IntOutsideRange(string.into())), RuntimeError(RuntimeError::InvalidInt(
IntErrorKind::Overflow,
Base::Decimal,
Region::zero(),
string.into(),
)),
); );
} }
#[test] #[test]
fn int_too_small() { fn int_too_small() {
use roc_parse::ast::Base;
let string = (i64::MIN as i128 - 1).to_string(); let string = (i64::MIN as i128 - 1).to_string();
assert_can( assert_can(
&string.clone(), &string.clone(),
RuntimeError(RuntimeError::IntOutsideRange(string.into())), RuntimeError(RuntimeError::InvalidInt(
IntErrorKind::Underflow,
Base::Decimal,
Region::zero(),
string.into(),
)),
); );
} }
#[test] #[test]
fn float_too_large() { fn float_too_large() {
let string = format!("{}1.0", f64::MAX); let string = format!("{}1.0", f64::MAX);
let region = Region::zero();
assert_can( assert_can(
&string.clone(), &string.clone(),
RuntimeError(RuntimeError::FloatOutsideRange(string.into())), RuntimeError(RuntimeError::InvalidFloat(
FloatErrorKind::PositiveInfinity,
region,
string.into(),
)),
); );
} }
#[test] #[test]
fn float_too_small() { fn float_too_small() {
let string = format!("{}1.0", f64::MIN); let string = format!("{}1.0", f64::MIN);
let region = Region::zero();
assert_can( assert_can(
&string.clone(), &string.clone(),
RuntimeError(RuntimeError::FloatOutsideRange(string.into())), RuntimeError(RuntimeError::InvalidFloat(
FloatErrorKind::NegativeInfinity,
region,
string.into(),
)),
); );
} }
@ -131,6 +155,46 @@ mod test_can {
assert_can_float("-0.0", -0.0); assert_can_float("-0.0", -0.0);
} }
#[test]
fn num_max() {
assert_can_num(&(i64::MAX.to_string()), i64::MAX);
}
#[test]
fn num_min() {
assert_can_num(&(i64::MIN.to_string()), i64::MIN);
}
#[test]
fn hex_max() {
assert_can_int(&format!("0x{:x}", i64::MAX), i64::MAX);
}
#[test]
fn hex_min() {
assert_can_int(&format!("-0x{:x}", i64::MAX as i128 + 1), i64::MIN);
}
#[test]
fn oct_max() {
assert_can_int(&format!("0o{:o}", i64::MAX), i64::MAX);
}
#[test]
fn oct_min() {
assert_can_int(&format!("-0o{:o}", i64::MAX as i128 + 1), i64::MIN);
}
#[test]
fn bin_max() {
assert_can_int(&format!("0b{:b}", i64::MAX), i64::MAX);
}
#[test]
fn bin_min() {
assert_can_int(&format!("-0b{:b}", i64::MAX as i128 + 1), i64::MIN);
}
#[test] #[test]
fn hex_zero() { fn hex_zero() {
assert_can_int("0x0", 0x0); assert_can_int("0x0", 0x0);
@ -505,10 +569,14 @@ mod test_can {
"# "#
); );
let home = test_home();
let arena = Bump::new(); let arena = Bump::new();
let CanExprOut { let CanExprOut {
loc_expr, problems, .. loc_expr,
} = can_expr_with(&arena, test_home(), src); problems,
interns,
..
} = can_expr_with(&arena, home, src);
let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_, _)) = loc_expr.value let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_, _)) = loc_expr.value
{ {
@ -518,7 +586,7 @@ mod test_can {
}; };
let problem = Problem::RuntimeError(RuntimeError::CircularDef( let problem = Problem::RuntimeError(RuntimeError::CircularDef(
vec![Located::at(Region::new(0, 0, 0, 1), "x".into())], vec![interns.symbol(home, "x".into())],
vec![(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5))], vec![(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5))],
)); ));
@ -537,16 +605,20 @@ mod test_can {
x x
"# "#
); );
let home = test_home();
let arena = Bump::new(); let arena = Bump::new();
let CanExprOut { let CanExprOut {
loc_expr, problems, .. loc_expr,
} = can_expr_with(&arena, test_home(), src); problems,
interns,
..
} = can_expr_with(&arena, home, src);
let problem = Problem::RuntimeError(RuntimeError::CircularDef( let problem = Problem::RuntimeError(RuntimeError::CircularDef(
vec![ vec![
Located::at(Region::new(0, 0, 0, 1), "x".into()), interns.symbol(home, "x".into()),
Located::at(Region::new(1, 1, 0, 1), "y".into()), interns.symbol(home, "y".into()),
Located::at(Region::new(2, 2, 0, 1), "z".into()), interns.symbol(home, "z".into()),
], ],
vec![ vec![
(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5)), (Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5)),

View file

@ -13,7 +13,7 @@ use roc_types::types::Type::{self, *};
pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint { pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
let num_type = Variable(num_var); let num_type = Variable(num_var);
let reason = Reason::IntLiteral; let reason = Reason::IntLiteral;
let expected_literal = ForReason(reason, Type::Apply(Symbol::INT_INT, vec![]), region); let expected_literal = ForReason(reason, Type::Apply(Symbol::NUM_INT, vec![]), region);
exists( exists(
vec![num_var], vec![num_var],
@ -28,7 +28,7 @@ pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region)
pub fn float_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint { pub fn float_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
let num_type = Variable(num_var); let num_type = Variable(num_var);
let reason = Reason::FloatLiteral; let reason = Reason::FloatLiteral;
let expected_literal = ForReason(reason, Type::Apply(Symbol::FLOAT_FLOAT, vec![]), region); let expected_literal = ForReason(reason, Type::Apply(Symbol::NUM_FLOAT, vec![]), region);
exists( exists(
vec![num_var], vec![num_var],

View file

@ -118,8 +118,8 @@ pub fn constrain_expr(
let record_type = Type::Record( let record_type = Type::Record(
field_types, field_types,
// TODO can we avoid doing Box::new on every single one of these? // TODO can we avoid doing Box::new on every single one of these?
// For example, could we have a single lazy_static global Box they // We can put `static EMPTY_REC: Type = Type::EmptyRec`, but that requires a
// could all share? // lifetime parameter on `Type`
Box::new(Type::EmptyRec), Box::new(Type::EmptyRec),
); );
let record_con = Eq(record_type, expected.clone(), Category::Record, region); let record_con = Eq(record_type, expected.clone(), Category::Record, region);
@ -600,11 +600,7 @@ pub fn constrain_expr(
} }
} }
// TODO check for exhaustiveness. If this `case` is non-exaustive, then: // exhautiveness checking happens when converting to mono::Expr
//
// 1. Record a Problem.
// 2. Add an extra _ branch at the end which throws a runtime error.
exists(vec![cond_var, *expr_var], And(constraints)) exists(vec![cond_var, *expr_var], And(constraints))
} }
Access { Access {
@ -778,7 +774,52 @@ pub fn constrain_expr(
exists(vars, And(arg_cons)) exists(vars, And(arg_cons))
} }
RuntimeError(_) => True, RunLowLevel { args, ret_var, op } => {
// This is a modified version of what we do for function calls.
// The operation's return type
let ret_type = Variable(*ret_var);
// This will be used in the occurs check
let mut vars = Vec::with_capacity(1 + args.len());
vars.push(*ret_var);
let mut arg_types = Vec::with_capacity(args.len());
let mut arg_cons = Vec::with_capacity(args.len());
let mut add_arg = |index, arg_type: Type, arg| {
let reason = Reason::LowLevelOpArg {
op: *op,
arg_index: Index::zero_based(index),
};
let expected_arg = ForReason(reason, arg_type.clone(), Region::zero());
let arg_con = constrain_expr(env, Region::zero(), arg, expected_arg);
arg_types.push(arg_type);
arg_cons.push(arg_con);
};
for (index, (arg_var, arg)) in args.iter().enumerate() {
vars.push(*arg_var);
add_arg(index, Variable(*arg_var), arg);
}
let category = Category::LowLevelOpResult(*op);
exists(
vars,
And(vec![
And(arg_cons),
Eq(ret_type, expected, category, region),
]),
)
}
RuntimeError(_) => {
// Runtime Errors have no constraints because they're going to crash.
True
}
} }
} }
@ -798,7 +839,6 @@ fn constrain_when_branch(
constraints: Vec::with_capacity(1), constraints: Vec::with_capacity(1),
}; };
// TODO ensure this is correct
// TODO investigate for error messages, is it better to unify all branches with a variable, // TODO investigate for error messages, is it better to unify all branches with a variable,
// then unify that variable with the expectation? // then unify that variable with the expectation?
for loc_pattern in &when_branch.patterns { for loc_pattern in &when_branch.patterns {
@ -872,38 +912,35 @@ pub fn constrain_decls(
) -> Constraint { ) -> Constraint {
let mut constraint = Constraint::SaveTheEnvironment; let mut constraint = Constraint::SaveTheEnvironment;
let mut env = Env {
home,
rigids: ImMap::default(),
};
for decl in decls.iter().rev() { for decl in decls.iter().rev() {
// NOTE: rigids are empty because they are not shared between top-level definitions // Clear the rigids from the previous iteration.
// rigids are not shared between top-level definitions
env.rigids.clear();
match decl { match decl {
Declaration::Declare(def) => { Declaration::Declare(def) | Declaration::Builtin(def) => {
constraint = exists_with_aliases( constraint = exists_with_aliases(
aliases.clone(), aliases.clone(),
Vec::new(), Vec::new(),
constrain_def( constrain_def(&env, def, constraint),
&Env {
home,
rigids: ImMap::default(),
},
def,
constraint,
),
); );
} }
Declaration::DeclareRec(defs) => { Declaration::DeclareRec(defs) => {
constraint = exists_with_aliases( constraint = exists_with_aliases(
aliases.clone(), aliases.clone(),
Vec::new(), Vec::new(),
constrain_recursive_defs( constrain_recursive_defs(&env, defs, constraint),
&Env {
home,
rigids: ImMap::default(),
},
defs,
constraint,
),
); );
} }
Declaration::InvalidCycle(_, _) => panic!("TODO handle invalid cycle"), Declaration::InvalidCycle(_, _) => {
// invalid cycles give a canonicalization error. we skip them here.
continue;
}
} }
} }
@ -970,8 +1007,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
expr_type, expr_type,
annotation_expected.clone(), annotation_expected.clone(),
Category::Storage, Category::Storage,
// TODO proper region annotation.region,
Region::zero(),
)); ));
constrain_expr( constrain_expr(

View file

@ -1,15 +1,15 @@
use crate::expr::constrain_decls; use crate::expr::constrain_decls;
use roc_builtins::std::Mode; use roc_builtins::std::{Mode, StdLib};
use roc_can::constraint::{Constraint, LetConstraint}; use roc_can::constraint::{Constraint, LetConstraint};
use roc_can::module::ModuleOutput; use roc_can::module::ModuleOutput;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap}; use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::Located; use roc_region::all::{Located, Region};
use roc_types::boolean_algebra::Bool; use roc_types::boolean_algebra::Bool;
use roc_types::solved_types::{BuiltinAlias, SolvedBool, SolvedType}; use roc_types::solved_types::{BuiltinAlias, SolvedBool, SolvedType};
use roc_types::subs::{VarId, VarStore, Variable}; use roc_types::subs::{VarId, VarStore, Variable};
use roc_types::types::{Alias, Type}; use roc_types::types::{Alias, Problem, Type};
pub type SubsByModule = MutMap<ModuleId, ExposedModuleTypes>; pub type SubsByModule = MutMap<ModuleId, ExposedModuleTypes>;
@ -284,7 +284,7 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V
Type::Alias(*symbol, type_variables, Box::new(actual)) Type::Alias(*symbol, type_variables, Box::new(actual))
} }
Error => Type::Erroneous(roc_types::types::Problem::SolvedTypeError), Error => Type::Erroneous(Problem::SolvedTypeError),
Erroneous(problem) => Type::Erroneous(problem.clone()), Erroneous(problem) => Type::Erroneous(problem.clone()),
} }
} }
@ -349,3 +349,134 @@ pub fn constrain_imported_aliases(
ret_constraint: body_con, ret_constraint: body_con,
})) }))
} }
/// Run pre_constrain_imports to get imported_symbols and imported_aliases.
pub fn constrain_imports(
imported_symbols: Vec<Import>,
imported_aliases: MutMap<Symbol, Alias>,
constraint: Constraint,
var_store: &mut VarStore,
) -> Constraint {
let (_introduced_rigids, constraint) =
constrain_imported_values(imported_symbols, constraint, var_store);
// TODO determine what to do with those rigids
// for var in introduced_rigids {
// output.ftv.insert(var, format!("internal_{:?}", var).into());
// }
constrain_imported_aliases(imported_aliases, constraint, var_store)
}
pub struct ConstrainableImports {
pub imported_symbols: Vec<Import>,
pub imported_aliases: MutMap<Symbol, Alias>,
pub unused_imports: MutSet<ModuleId>,
}
/// Run this before constraining imports.
///
/// Constraining imports is split into two different functions, because this
/// part of the work needs to be done on the main thread, whereas the rest of it
/// can be done on a different thread.
pub fn pre_constrain_imports(
home: ModuleId,
references: &MutSet<Symbol>,
imported_modules: MutSet<ModuleId>,
exposed_types: &mut SubsByModule,
stdlib: &StdLib,
) -> ConstrainableImports {
let mut imported_symbols = Vec::with_capacity(references.len());
let mut imported_aliases = MutMap::default();
let mut unused_imports = imported_modules; // We'll remove these as we encounter them.
// Translate referenced symbols into constraints. We do this on the main
// thread because we need exclusive access to the exposed_types map, in order
// to get the necessary constraint info for any aliases we imported. We also
// resolve builtin types now, so we can use a refernce to stdlib instead of
// having to either clone it or recreate it from scratch on the other thread.
for &symbol in references.iter() {
let module_id = symbol.module_id();
// We used this module, so clearly it is not unused!
unused_imports.remove(&module_id);
if module_id.is_builtin() {
// For builtin modules, we create imports from the
// hardcoded builtin map.
match stdlib.types.get(&symbol) {
Some((solved_type, region)) => {
let loc_symbol = Located {
value: symbol,
region: *region,
};
imported_symbols.push(Import {
loc_symbol,
solved_type: solved_type.clone(),
});
}
None => {
let is_valid_alias = stdlib.applies.contains(&symbol)
// This wasn't a builtin value or Apply; maybe it was a builtin alias.
|| stdlib.aliases.contains_key(&symbol);
if !is_valid_alias {
panic!(
"Could not find {:?} in builtin types {:?} or aliases {:?}",
symbol, stdlib.types, stdlib.aliases
);
}
}
}
} else if module_id != home {
// We already have constraints for our own symbols.
let region = Region::zero(); // TODO this should be the region where this symbol was declared in its home module. Look that up!
let loc_symbol = Located {
value: symbol,
region,
};
match exposed_types.get(&module_id) {
Some(ExposedModuleTypes::Valid(solved_types, new_aliases)) => {
let solved_type = solved_types.get(&symbol).unwrap_or_else(|| {
panic!(
"Could not find {:?} in solved_types {:?}",
loc_symbol.value, solved_types
)
});
// TODO should this be a union?
for (k, v) in new_aliases.clone() {
imported_aliases.insert(k, v);
}
imported_symbols.push(Import {
loc_symbol,
solved_type: solved_type.clone(),
});
}
Some(ExposedModuleTypes::Invalid) => {
// If that module was invalid, use True constraints
// for everything imported from it.
imported_symbols.push(Import {
loc_symbol,
solved_type: SolvedType::Erroneous(Problem::InvalidModule),
});
}
None => {
panic!(
"Could not find module {:?} in exposed_types {:?}",
module_id, exposed_types
);
}
}
}
}
ConstrainableImports {
imported_symbols,
imported_aliases,
unused_imports,
}
}

View file

@ -52,6 +52,7 @@ fn headers_from_annotation_help(
} }
Underscore Underscore
| Shadowed(_, _) | Shadowed(_, _)
| MalformedPattern(_, _)
| UnsupportedPattern(_) | UnsupportedPattern(_)
| NumLiteral(_, _) | NumLiteral(_, _)
| IntLiteral(_) | IntLiteral(_)
@ -117,7 +118,7 @@ pub fn constrain_pattern(
state: &mut PatternState, state: &mut PatternState,
) { ) {
match pattern { match pattern {
Underscore | UnsupportedPattern(_) | Shadowed(_, _) => { Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed(_, _) => {
// Neither the _ pattern nor erroneous ones add any constraints. // Neither the _ pattern nor erroneous ones add any constraints.
} }
@ -146,7 +147,7 @@ pub fn constrain_pattern(
state.constraints.push(Constraint::Pattern( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Float, PatternCategory::Float,
builtins::builtin_type(Symbol::INT_INT, vec![]), builtins::builtin_type(Symbol::NUM_INT, vec![]),
expected, expected,
)); ));
} }
@ -155,7 +156,7 @@ pub fn constrain_pattern(
state.constraints.push(Constraint::Pattern( state.constraints.push(Constraint::Pattern(
region, region,
PatternCategory::Float, PatternCategory::Float,
builtins::builtin_type(Symbol::FLOAT_FLOAT, vec![]), builtins::builtin_type(Symbol::NUM_FLOAT, vec![]),
expected, expected,
)); ));
} }

View file

@ -70,7 +70,7 @@ pub fn constrain_decls(
for decl in decls.iter().rev() { for decl in decls.iter().rev() {
// NOTE: rigids are empty because they are not shared between top-level definitions // NOTE: rigids are empty because they are not shared between top-level definitions
match decl { match decl {
Declaration::Declare(def) => { Declaration::Declare(def) | Declaration::Builtin(def) => {
sharing::annotate_usage(&def.loc_expr.value, &mut var_usage); sharing::annotate_usage(&def.loc_expr.value, &mut var_usage);
} }
Declaration::DeclareRec(defs) => { Declaration::DeclareRec(defs) => {
@ -78,24 +78,33 @@ pub fn constrain_decls(
sharing::annotate_usage(&def.loc_expr.value, &mut var_usage); sharing::annotate_usage(&def.loc_expr.value, &mut var_usage);
} }
} }
Declaration::InvalidCycle(_, _) => panic!("TODO handle invalid cycle"), Declaration::InvalidCycle(_, _) => {
// any usage of a value defined in an invalid cycle will blow up
// so for the analysis usage by such values doesn't count
continue;
}
} }
} }
aliases_to_attr_type(var_store, &mut aliases); aliases_to_attr_type(var_store, &mut aliases);
let mut env = Env {
home,
rigids: ImMap::default(),
};
for decl in decls.iter().rev() { for decl in decls.iter().rev() {
// NOTE: rigids are empty because they are not shared between top-level definitions // clear the set of rigids from the previous iteration.
// rigids are not shared between top-level definitions.
env.rigids.clear();
match decl { match decl {
Declaration::Declare(def) => { Declaration::Declare(def) | Declaration::Builtin(def) => {
constraint = exists_with_aliases( constraint = exists_with_aliases(
aliases.clone(), aliases.clone(),
Vec::new(), Vec::new(),
constrain_def( constrain_def(
&Env { &env,
home,
rigids: ImMap::default(),
},
var_store, var_store,
&var_usage, &var_usage,
&mut ImSet::default(), &mut ImSet::default(),
@ -109,10 +118,7 @@ pub fn constrain_decls(
aliases.clone(), aliases.clone(),
Vec::new(), Vec::new(),
constrain_recursive_defs( constrain_recursive_defs(
&Env { &env,
home,
rigids: ImMap::default(),
},
var_store, var_store,
&var_usage, &var_usage,
&mut ImSet::default(), &mut ImSet::default(),
@ -121,7 +127,10 @@ pub fn constrain_decls(
), ),
); );
} }
Declaration::InvalidCycle(_, _) => panic!("TODO handle invalid cycle"), Declaration::InvalidCycle(_, _) => {
// invalid cycles give a canonicalization error. we skip them here.
continue;
}
} }
} }
@ -333,7 +342,7 @@ fn constrain_pattern(
state.constraints.push(tag_con); state.constraints.push(tag_con);
} }
Underscore | Shadowed(_, _) | UnsupportedPattern(_) => { Underscore | Shadowed(_, _) | MalformedPattern(_, _) | UnsupportedPattern(_) => {
// no constraints // no constraints
} }
} }
@ -370,11 +379,11 @@ fn unique_num(var_store: &mut VarStore, symbol: Symbol) -> (Variable, Variable,
} }
fn unique_int(var_store: &mut VarStore) -> (Variable, Variable, Type) { fn unique_int(var_store: &mut VarStore) -> (Variable, Variable, Type) {
unique_num(var_store, Symbol::INT_INTEGER) unique_num(var_store, Symbol::NUM_INTEGER)
} }
fn unique_float(var_store: &mut VarStore) -> (Variable, Variable, Type) { fn unique_float(var_store: &mut VarStore) -> (Variable, Variable, Type) {
unique_num(var_store, Symbol::FLOAT_FLOATINGPOINT) unique_num(var_store, Symbol::NUM_FLOATINGPOINT)
} }
pub fn constrain_expr( pub fn constrain_expr(
@ -795,6 +804,54 @@ pub fn constrain_expr(
]), ]),
) )
} }
RunLowLevel { op, args, ret_var } => {
// This is a modified version of what we do for function calls.
let ret_type = Variable(*ret_var);
let mut vars = Vec::with_capacity(1 + args.len());
vars.push(*ret_var);
// Canonicalize the function expression and its arguments
let mut arg_types = Vec::with_capacity(args.len());
let mut arg_cons = Vec::with_capacity(args.len());
for (index, (arg_var, arg_expr)) in args.iter().enumerate() {
let arg_type = Variable(*arg_var);
let reason = Reason::LowLevelOpArg {
op: *op,
arg_index: Index::zero_based(index),
};
let expected_arg = Expected::ForReason(reason, arg_type.clone(), region);
let arg_con = constrain_expr(
env,
var_store,
var_usage,
applied_usage_constraint,
Region::zero(),
arg_expr,
expected_arg,
);
vars.push(*arg_var);
arg_types.push(arg_type);
arg_cons.push(arg_con);
}
let expected_uniq_type = var_store.fresh();
vars.push(expected_uniq_type);
exists(
vars,
And(vec![
And(arg_cons),
Eq(ret_type, expected, Category::LowLevelOpResult(*op), region),
]),
)
}
LetRec(defs, loc_ret, var, unlifted_aliases) => { LetRec(defs, loc_ret, var, unlifted_aliases) => {
// NOTE doesn't currently unregister bound symbols // NOTE doesn't currently unregister bound symbols
// may be a problem when symbols are not globally unique // may be a problem when symbols are not globally unique

View file

@ -0,0 +1,500 @@
use crate::spaces::{fmt_comments_only, fmt_condition_spaces, fmt_spaces, newline, INDENT};
use bumpalo::collections::String;
use roc_parse::ast::{AssignedField, Expr, Tag, TypeAnnotation};
use roc_region::all::Located;
/// Does an AST node need parens around it?
///
/// Usually not, but there are two cases where it may be required
///
/// 1. In a function type, function types are in parens
///
/// a -> b, c -> d
/// (a -> b), c -> d
///
/// 2. In applications, applications are in brackets
/// This is true in patterns, type annotations and expressions
///
/// Just (Just a)
/// List (List a)
/// reverse (reverse l)
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Parens {
NotNeeded,
InFunctionType,
InApply,
}
/// In an AST node, do we show newlines around it
///
/// Sometimes, we only want to show comments, at other times
/// we also want to show newlines. By default the formatter
/// takes care of inserting newlines, but sometimes the user's
/// newlines are taken into account.
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Newlines {
Yes,
No,
}
pub fn fmt_annotation<'a>(buf: &mut String<'a>, annotation: &'a TypeAnnotation<'a>, indent: u16) {
annotation.format(buf, indent);
}
pub trait Formattable<'a> {
fn is_multiline(&self) -> bool;
fn format_with_options(
&self,
buf: &mut String<'a>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
self.format(buf, indent);
}
fn format(&self, buf: &mut String<'a>, indent: u16) {
self.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
/// A Located formattable value is also formattable
impl<'a, T> Formattable<'a> for Located<T>
where
T: Formattable<'a>,
{
fn is_multiline(&self) -> bool {
self.value.is_multiline()
}
fn format_with_options(
&self,
buf: &mut String<'a>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
self.value
.format_with_options(buf, parens, newlines, indent)
}
fn format(&self, buf: &mut String<'a>, indent: u16) {
self.value.format(buf, indent)
}
}
impl<'a> Formattable<'a> for TypeAnnotation<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeAnnotation::*;
match self {
// Return whether these spaces contain any Newlines
SpaceBefore(_, spaces) | SpaceAfter(_, spaces) => {
debug_assert!(!spaces.is_empty());
// "spaces" always contain either a newline or comment, and comments have newlines
true
}
Wildcard | BoundVariable(_) | Malformed(_) => false,
Function(args, result) => {
(&result.value).is_multiline()
|| args.iter().any(|loc_arg| (&loc_arg.value).is_multiline())
}
Apply(_, _, args) => args.iter().any(|loc_arg| loc_arg.value.is_multiline()),
As(lhs, _, rhs) => lhs.value.is_multiline() || rhs.value.is_multiline(),
Record { fields, ext } => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
}
fields.iter().any(|field| field.value.is_multiline())
}
TagUnion { tags, ext } => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
}
tags.iter().any(|tag| tag.value.is_multiline())
}
}
}
fn format_with_options(
&self,
buf: &mut String<'a>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
use roc_parse::ast::TypeAnnotation::*;
match self {
Function(arguments, result) => {
let write_parens = parens != Parens::NotNeeded;
if write_parens {
buf.push('(')
}
let mut it = arguments.iter().peekable();
while let Some(argument) = it.next() {
(&argument.value).format_with_options(
buf,
Parens::InFunctionType,
Newlines::No,
indent,
);
if it.peek().is_some() {
buf.push_str(", ");
}
}
buf.push_str(" -> ");
(&result.value).format_with_options(
buf,
Parens::InFunctionType,
Newlines::No,
indent,
);
if write_parens {
buf.push(')')
}
}
Apply(_, name, arguments) => {
// NOTE apply is never multiline
let write_parens = parens == Parens::InApply && !arguments.is_empty();
if write_parens {
buf.push('(')
}
buf.push_str(name);
for argument in *arguments {
buf.push(' ');
(&argument.value).format_with_options(
buf,
Parens::InApply,
Newlines::No,
indent,
);
}
if write_parens {
buf.push(')')
}
}
BoundVariable(v) => buf.push_str(v),
Wildcard => buf.push('*'),
TagUnion { tags, ext } => {
tags.format(buf, indent);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
Record { fields, ext } => {
fields.format(buf, indent);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
As(lhs, _spaces, rhs) => {
// TODO use spaces?
lhs.value.format(buf, indent);
buf.push_str(" as ");
rhs.value.format(buf, indent);
}
SpaceBefore(ann, _spaces) | SpaceAfter(ann, _spaces) => {
ann.format_with_options(buf, parens, newlines, indent)
}
Malformed(raw) => buf.push_str(raw),
}
}
}
/// Fields are subtly different on the type and term level:
///
/// > type: { x : Int, y : Bool }
/// > term: { x: 100, y: True }
///
/// So we need two instances, each having the specific separator
impl<'a> Formattable<'a> for AssignedField<'a, TypeAnnotation<'a>> {
fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self)
}
fn format_with_options(
&self,
buf: &mut String<'a>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
// we abuse the `Newlines` type to decide between multiline or single-line layout
format_assigned_field_help(self, buf, parens, indent, " : ", newlines == Newlines::Yes);
}
}
impl<'a> Formattable<'a> for AssignedField<'a, Expr<'a>> {
fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self)
}
fn format_with_options(
&self,
buf: &mut String<'a>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
// we abuse the `Newlines` type to decide between multiline or single-line layout
format_assigned_field_help(self, buf, parens, indent, ": ", newlines == Newlines::Yes);
}
}
fn is_multiline_assigned_field_help<'a, T: Formattable<'a>>(afield: &AssignedField<'a, T>) -> bool {
use self::AssignedField::*;
match afield {
LabeledValue(_, spaces, ann) => !spaces.is_empty() || ann.value.is_multiline(),
LabelOnly(_) => false,
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
Malformed(text) => text.chars().any(|c| c == '\n'),
}
}
fn format_assigned_field_help<'a, T>(
zelf: &AssignedField<'a, T>,
buf: &mut String<'a>,
parens: Parens,
indent: u16,
separator: &str,
is_multiline: bool,
) where
T: Formattable<'a>,
{
// TODO multiline?
use self::AssignedField::*;
match zelf {
LabeledValue(name, spaces, ann) => {
if is_multiline {
newline(buf, indent);
}
buf.push_str(name.value);
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
}
buf.push_str(separator);
ann.value.format(buf, indent);
}
LabelOnly(name) => {
if is_multiline {
newline(buf, indent);
}
buf.push_str(name.value);
}
AssignedField::SpaceBefore(sub_field, spaces) => {
fmt_comments_only(buf, spaces.iter(), indent);
format_assigned_field_help(sub_field, buf, parens, indent, separator, is_multiline);
}
AssignedField::SpaceAfter(sub_field, spaces) => {
format_assigned_field_help(sub_field, buf, parens, indent, separator, is_multiline);
fmt_comments_only(buf, spaces.iter(), indent);
}
Malformed(raw) => {
buf.push_str(raw);
}
}
}
impl<'a> Formattable<'a> for Tag<'a> {
fn is_multiline(&self) -> bool {
use self::Tag::*;
match self {
Global { args, .. } | Private { args, .. } => {
args.iter().any(|arg| (&arg.value).is_multiline())
}
Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => true,
Malformed(text) => text.chars().any(|c| c == '\n'),
}
}
fn format_with_options(
&self,
buf: &mut String<'a>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
let is_multiline = self.is_multiline();
match self {
Tag::Global { name, args } => {
buf.push_str(name.value);
if is_multiline {
let arg_indent = indent + INDENT;
for arg in *args {
newline(buf, arg_indent);
arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
}
} else {
for arg in *args {
buf.push(' ');
arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
}
}
}
Tag::Private { name, args } => {
buf.push('@');
buf.push_str(name.value);
if is_multiline {
let arg_indent = indent + INDENT;
for arg in *args {
newline(buf, arg_indent);
arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
}
} else {
for arg in *args {
buf.push(' ');
arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
}
}
}
Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => unreachable!(),
Tag::Malformed(raw) => buf.push_str(raw),
}
}
}
macro_rules! implement_format_sequence {
($start:expr, $end:expr, $t:ident) => {
fn format_with_options(
&self,
buf: &mut String<'a>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
buf.push($start);
let mut iter = self.iter().peekable();
let is_multiline = self.is_multiline();
let item_indent = if is_multiline {
indent + INDENT
} else {
indent
};
while let Some(item) = iter.next() {
if is_multiline {
match &item.value {
$t::SpaceBefore(expr_below, spaces_above_expr) => {
newline(buf, item_indent);
fmt_comments_only(buf, spaces_above_expr.iter(), item_indent);
match &expr_below {
$t::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
fmt_condition_spaces(
buf,
spaces_below_expr.iter(),
item_indent,
);
}
_ => {
expr_below.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
}
}
}
$t::SpaceAfter(sub_expr, spaces) => {
newline(buf, item_indent);
sub_expr.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
fmt_condition_spaces(buf, spaces.iter(), item_indent);
}
_ => {
newline(buf, item_indent);
item.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
}
}
} else {
buf.push(' ');
item.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(',');
}
}
}
if is_multiline {
newline(buf, indent);
}
if !self.is_empty() && !is_multiline {
buf.push(' ');
}
buf.push($end);
}
};
}
impl<'a> Formattable<'a> for &'a [Located<Tag<'a>>] {
fn is_multiline(&self) -> bool {
self.iter().any(|t| t.value.is_multiline())
}
implement_format_sequence!('[', ']', Tag);
}
impl<'a> Formattable<'a> for &'a [Located<AssignedField<'a, TypeAnnotation<'a>>>] {
fn is_multiline(&self) -> bool {
self.iter().any(|f| f.value.is_multiline())
}
implement_format_sequence!('{', '}', AssignedField);
}

View file

@ -1,46 +1,110 @@
use crate::expr::{fmt_expr, is_multiline_expr}; use crate::annotation::{Formattable, Newlines, Parens};
use crate::pattern::fmt_pattern; use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_spaces, newline, INDENT}; use crate::spaces::{fmt_spaces, is_comment, newline, INDENT};
use bumpalo::collections::String; use bumpalo::collections::String;
use roc_parse::ast::{Def, Expr}; use roc_parse::ast::{Def, Expr, Pattern};
pub fn fmt_def<'a>(buf: &mut String<'a>, def: &'a Def<'a>, indent: u16) { /// A Located formattable value is also formattable
use roc_parse::ast::Def::*; impl<'a> Formattable<'a> for Def<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::Def::*;
match def { match self {
Annotation(_, _) => panic!("TODO have format_def support Annotation"), Alias { ann, .. } => ann.is_multiline(),
Alias { .. } => panic!("TODO have format_def support Alias"), Annotation(loc_pattern, loc_annotation) => {
Body(loc_pattern, loc_expr) => { loc_pattern.is_multiline() || loc_annotation.is_multiline()
fmt_pattern(buf, &loc_pattern.value, indent, true, false); }
buf.push_str(" ="); Body(loc_pattern, loc_expr) => loc_pattern.is_multiline() || loc_expr.is_multiline(),
if is_multiline_expr(&loc_expr.value) {
match &loc_expr.value { TypedBody(_loc_pattern, _loc_annotation, _loc_expr) => {
Expr::Record { .. } | Expr::List(_) => { unreachable!("annotations and bodies have not yet been merged into TypedBody");
newline(buf, indent + INDENT); }
fmt_expr(buf, &loc_expr.value, indent + INDENT, false, true);
} SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => {
_ => { spaces.iter().any(|s| is_comment(s)) || sub_def.is_multiline()
}
Nested(def) => def.is_multiline(),
}
}
fn format_with_options(
&self,
buf: &mut String<'a>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
use roc_parse::ast::Def::*;
match self {
Annotation(loc_pattern, loc_annotation) => {
loc_pattern.format(buf, indent);
buf.push_str(" : ");
loc_annotation.format(buf, indent);
}
Alias { name, vars, ann } => {
buf.push_str(name.value);
if vars.is_empty() {
buf.push(' ');
} else {
for var in *vars {
buf.push(' '); buf.push(' ');
fmt_expr(buf, &loc_expr.value, indent, false, true); fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
} }
} }
} else {
buf.push(' ');
fmt_expr(buf, &loc_expr.value, indent, false, true);
}
}
TypedBody(_loc_pattern, _loc_annotation, _loc_expr) => {
panic!("TODO support Annotation in TypedBody");
}
SpaceBefore(sub_def, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
fmt_def(buf, sub_def, indent);
}
SpaceAfter(sub_def, spaces) => {
fmt_def(buf, sub_def, indent);
fmt_spaces(buf, spaces.iter(), indent); buf.push_str(" : ");
ann.format(buf, indent)
}
Body(loc_pattern, loc_expr) => {
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
}
TypedBody(_loc_pattern, _loc_annotation, _loc_expr) => {
unreachable!("annotations and bodies have not yet been merged into TypedBody");
}
SpaceBefore(sub_def, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
sub_def.format(buf, indent);
}
SpaceAfter(sub_def, spaces) => {
sub_def.format(buf, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
Nested(def) => def.format(buf, indent),
} }
Nested(def) => fmt_def(buf, def, indent), }
}
pub fn fmt_def<'a>(buf: &mut String<'a>, def: &Def<'a>, indent: u16) {
def.format(buf, indent);
}
pub fn fmt_body<'a>(
buf: &mut String<'a>,
pattern: &'a Pattern<'a>,
body: &'a Expr<'a>,
indent: u16,
) {
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
buf.push_str(" =");
if body.is_multiline() {
match body {
Expr::SpaceBefore(_, _) => {
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
Expr::Record { .. } | Expr::List(_) => {
newline(buf, indent + INDENT);
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
}
_ => {
buf.push(' ');
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
}
} else {
buf.push(' ');
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,7 @@
// and encouraging shortcuts here creates bad incentives. I would rather temporarily // and encouraging shortcuts here creates bad incentives. I would rather temporarily
// re-enable this when working on performance optimizations than have it block PRs. // re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
pub mod annotation;
pub mod def; pub mod def;
pub mod expr; pub mod expr;
pub mod module; pub mod module;

View file

@ -1,4 +1,5 @@
use crate::spaces::{fmt_comments_only, fmt_spaces}; use crate::annotation::{Formattable, Newlines, Parens};
use crate::spaces::{fmt_comments_only, fmt_spaces, is_comment};
use bumpalo::collections::String; use bumpalo::collections::String;
use roc_parse::ast::{Base, Pattern}; use roc_parse::ast::{Base, Pattern};
@ -6,116 +7,158 @@ pub fn fmt_pattern<'a>(
buf: &mut String<'a>, buf: &mut String<'a>,
pattern: &'a Pattern<'a>, pattern: &'a Pattern<'a>,
indent: u16, indent: u16,
apply_needs_parens: bool, parens: Parens,
only_comments: bool,
) { ) {
use self::Pattern::*; pattern.format_with_options(buf, parens, Newlines::No, indent);
}
match pattern { impl<'a> Formattable<'a> for Pattern<'a> {
Identifier(string) => buf.push_str(string), fn is_multiline(&self) -> bool {
GlobalTag(name) | PrivateTag(name) => { // Theory: a pattern should only be multiline when it contains a comment
buf.push_str(name); match self {
Pattern::SpaceBefore(_, spaces) | Pattern::SpaceAfter(_, spaces) => {
debug_assert!(!spaces.is_empty());
spaces.iter().any(|s| is_comment(s))
}
Pattern::Nested(nested_pat) => nested_pat.is_multiline(),
Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()),
Pattern::RecordField(_, subpattern) => subpattern.is_multiline(),
Pattern::Identifier(_)
| Pattern::GlobalTag(_)
| Pattern::PrivateTag(_)
| Pattern::Apply(_, _)
| Pattern::NumLiteral(_)
| Pattern::NonBase10Literal { .. }
| Pattern::FloatLiteral(_)
| Pattern::StrLiteral(_)
| Pattern::BlockStrLiteral(_)
| Pattern::Underscore
| Pattern::Malformed(_)
| Pattern::QualifiedIdentifier { .. } => false,
} }
Apply(loc_pattern, loc_arg_patterns) => { }
if apply_needs_parens {
buf.push('('); fn format_with_options(
&self,
buf: &mut String<'a>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
use self::Pattern::*;
match self {
Identifier(string) => buf.push_str(string),
GlobalTag(name) | PrivateTag(name) => {
buf.push_str(name);
} }
Apply(loc_pattern, loc_arg_patterns) => {
// Sometimes, an Apply pattern needs parens around it.
// In particular when an Apply's argument is itself an Apply (> 0) arguments
let parens = !loc_arg_patterns.is_empty() && parens == Parens::InApply;
fmt_pattern(buf, &loc_pattern.value, indent, true, only_comments); if parens {
buf.push('(');
for loc_arg in loc_arg_patterns.iter() {
buf.push(' ');
fmt_pattern(buf, &loc_arg.value, indent, true, only_comments);
}
if apply_needs_parens {
buf.push(')');
}
}
RecordDestructure(loc_patterns) => {
buf.push_str("{ ");
let mut is_first = true;
for loc_pattern in *loc_patterns {
if is_first {
is_first = false;
} else {
buf.push_str(", ");
} }
fmt_pattern(buf, &loc_pattern.value, indent, true, only_comments); loc_pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
for loc_arg in loc_arg_patterns.iter() {
buf.push(' ');
loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
}
if parens {
buf.push(')');
}
}
RecordDestructure(loc_patterns) => {
buf.push_str("{ ");
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
loc_pattern.format(buf, indent);
if it.peek().is_some() {
buf.push_str(", ");
}
}
buf.push_str(" }");
} }
buf.push_str(" }"); RecordField(name, loc_pattern) => {
} buf.push_str(name);
buf.push_str(": ");
RecordField(name, loc_pattern) => { loc_pattern.format(buf, indent);
buf.push_str(name);
buf.push_str(": ");
fmt_pattern(buf, &loc_pattern.value, indent, true, only_comments);
}
NumLiteral(string) => buf.push_str(string),
NonBase10Literal {
base,
string,
is_negative,
} => {
if *is_negative {
buf.push('-');
} }
buf.push('0'); NumLiteral(string) => buf.push_str(string),
NonBase10Literal {
base,
string,
is_negative,
} => {
if *is_negative {
buf.push('-');
}
buf.push(match base { match base {
Base::Hex => 'x', Base::Hex => buf.push_str("0x"),
Base::Octal => 'o', Base::Octal => buf.push_str("0o"),
Base::Binary => 'b', Base::Binary => buf.push_str("0b"),
}); Base::Decimal => { /* nothing */ }
}
buf.push_str(string); buf.push_str(string);
}
FloatLiteral(string) => buf.push_str(string),
StrLiteral(string) => buf.push_str(string),
BlockStrLiteral(lines) => {
for line in *lines {
buf.push_str(line)
} }
} FloatLiteral(string) => buf.push_str(string),
Underscore => buf.push('_'), StrLiteral(string) => buf.push_str(string),
BlockStrLiteral(lines) => {
// Space for line in *lines {
SpaceBefore(sub_pattern, spaces) => { buf.push_str(line)
if only_comments { }
fmt_comments_only(buf, spaces.iter(), indent)
} else {
fmt_spaces(buf, spaces.iter(), indent);
} }
fmt_pattern(buf, sub_pattern, indent, apply_needs_parens, only_comments); Underscore => buf.push('_'),
}
SpaceAfter(sub_pattern, spaces) => { // Space
fmt_pattern(buf, sub_pattern, indent, apply_needs_parens, only_comments); SpaceBefore(sub_pattern, spaces) => {
if only_comments { if !sub_pattern.is_multiline() {
fmt_comments_only(buf, spaces.iter(), indent) fmt_comments_only(buf, spaces.iter(), indent)
} else { } else {
fmt_spaces(buf, spaces.iter(), indent); fmt_spaces(buf, spaces.iter(), indent);
}
sub_pattern.format_with_options(buf, parens, newlines, indent);
} }
} SpaceAfter(sub_pattern, spaces) => {
sub_pattern.format_with_options(buf, parens, newlines, indent);
Nested(sub_pattern) => { // if only_comments {
fmt_pattern(buf, sub_pattern, indent, apply_needs_parens, only_comments); if !sub_pattern.is_multiline() {
} fmt_comments_only(buf, spaces.iter(), indent)
} else {
// Malformed fmt_spaces(buf, spaces.iter(), indent);
Malformed(string) => buf.push_str(string), }
QualifiedIdentifier { module_name, ident } => {
if !module_name.is_empty() {
buf.push_str(module_name);
buf.push('.');
} }
buf.push_str(ident); Nested(sub_pattern) => {
sub_pattern.format_with_options(buf, parens, newlines, indent);
}
// Malformed
Malformed(string) => buf.push_str(string),
QualifiedIdentifier { module_name, ident } => {
if !module_name.is_empty() {
buf.push_str(module_name);
buf.push('.');
}
buf.push_str(ident);
}
} }
} }
} }

View file

@ -11,8 +11,8 @@ extern crate roc_parse;
mod test_fmt { mod test_fmt {
use bumpalo::collections::String; use bumpalo::collections::String;
use bumpalo::Bump; use bumpalo::Bump;
use roc_fmt::annotation::{Formattable, Newlines, Parens};
use roc_fmt::def::fmt_def; use roc_fmt::def::fmt_def;
use roc_fmt::expr::fmt_expr;
use roc_fmt::module::fmt_module; use roc_fmt::module::fmt_module;
use roc_parse::ast::{Attempting, Expr}; use roc_parse::ast::{Attempting, Expr};
use roc_parse::blankspace::space0_before; use roc_parse::blankspace::space0_before;
@ -38,7 +38,7 @@ mod test_fmt {
Ok(actual) => { Ok(actual) => {
let mut buf = String::new_in(&arena); let mut buf = String::new_in(&arena);
fmt_expr(&mut buf, &actual, 0, false, true); actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0);
assert_eq!(buf, expected) assert_eq!(buf, expected)
} }
@ -665,14 +665,40 @@ mod test_fmt {
expr_formats_same(indoc!( expr_formats_same(indoc!(
r#" r#"
identity = \a, identity = \a, b -> a
b
-> a
identity 43 identity 43
"# "#
)); ));
// expr_formats_same(indoc!(
// r#"
// identity =
// \{
// x,
// y
// }
// -> a
//
// identity 43
// "#
// ));
//
expr_formats_same(indoc!(
r#"
identity = \a,
b,
# it's c!!
c
-> a
identity 43
"#
));
}
#[test]
fn closure_multiline_pattern() {
expr_formats_same(indoc!( expr_formats_same(indoc!(
r#" r#"
identity = \a, identity = \a,
@ -2055,4 +2081,140 @@ mod test_fmt {
"# "#
)); ));
} }
/// Annotations and aliases
#[test]
fn list_alias() {
expr_formats_same(indoc!(
r#"
ConsList a : [ Cons a (ConsList a), Nil ]
f : ConsList a -> ConsList a
f = \_ -> Nil
f
"#
));
}
#[test]
fn wildcard() {
expr_formats_same(indoc!(
r#"
f : List *
f = []
a
"#
));
}
#[test]
fn identity() {
expr_formats_same(indoc!(
r#"
f : a -> a
f = []
a
"#
));
}
#[test]
fn tag_union() {
expr_formats_same(indoc!(
r#"
f : [ True, False ] -> [ True, False ]
f = \x -> x
a
"#
));
}
#[test]
fn recursive_tag_union() {
expr_formats_same(indoc!(
r#"
f : [ Cons a (ConsList a), Nil ] as ConsList a -> [ Just a, Nothing ]
f = \list ->
when list is
Nil ->
Nothing
Cons first _ ->
Just first
f
"#
));
}
#[test]
fn record_type() {
expr_formats_same(indoc!(
r#"
f : { foo : Int }
f = { foo: 1000 }
a
"#
));
}
#[test]
fn record_pattern_with_apply_guard() {
expr_formats_same(indoc!(
r#"
when { x: 1 } is
{ x: Just 4 } ->
4
"#
));
}
#[test]
fn record_pattern_with_record_guard() {
expr_formats_same(indoc!(
r#"
when { x: 1 } is
{ x: { x: True } } ->
4
"#
));
}
#[test]
fn body_starts_with_spaces_multiline() {
expr_formats_same(indoc!(
r#"
y =
Foo
1
2
y
"#
));
}
// this is a parse error atm
// #[test]
// fn multiline_apply() {
// expr_formats_same(indoc!(
// r#"
// f :
// Result a
// { x : Int
// , y : Float
// }
// c
// -> Int
// f =
// \_ -> 4
// "#
// ));
// }
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,112 @@
use crate::llvm::build::Env;
use inkwell::values::BasicValueEnum;
use inkwell::{FloatPredicate, IntPredicate};
use roc_mono::layout::{Builtin, Layout};
pub fn build_eq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
lhs_val: BasicValueEnum<'ctx>,
rhs_val: BasicValueEnum<'ctx>,
lhs_layout: &Layout<'a>,
rhs_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
match (lhs_layout, rhs_layout) {
(Layout::Builtin(lhs_builtin), Layout::Builtin(rhs_builtin)) => {
let int_cmp = |pred, label| {
let int_val = env.builder.build_int_compare(
pred,
lhs_val.into_int_value(),
rhs_val.into_int_value(),
label,
);
BasicValueEnum::IntValue(int_val)
};
let float_cmp = |pred, label| {
let int_val = env.builder.build_float_compare(
pred,
lhs_val.into_float_value(),
rhs_val.into_float_value(),
label,
);
BasicValueEnum::IntValue(int_val)
};
match (lhs_builtin, rhs_builtin) {
(Builtin::Int128, Builtin::Int128) => int_cmp(IntPredicate::EQ, "eq_i128"),
(Builtin::Int64, Builtin::Int64) => int_cmp(IntPredicate::EQ, "eq_i64"),
(Builtin::Int32, Builtin::Int32) => int_cmp(IntPredicate::EQ, "eq_i32"),
(Builtin::Int16, Builtin::Int16) => int_cmp(IntPredicate::EQ, "eq_i16"),
(Builtin::Int8, Builtin::Int8) => int_cmp(IntPredicate::EQ, "eq_i8"),
(Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::EQ, "eq_i1"),
(Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::OEQ, "eq_f64"),
(Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::OEQ, "eq_f32"),
(b1, b2) => {
todo!("Handle equals for builtin layouts {:?} == {:?}", b1, b2);
}
}
}
(other1, other2) => {
// TODO NOTE: This should ultimately have a _ => todo!("type mismatch!") branch
todo!("implement equals for layouts {:?} == {:?}", other1, other2);
}
}
}
pub fn build_neq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
lhs_val: BasicValueEnum<'ctx>,
rhs_val: BasicValueEnum<'ctx>,
lhs_layout: &Layout<'a>,
rhs_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
match (lhs_layout, rhs_layout) {
(Layout::Builtin(lhs_builtin), Layout::Builtin(rhs_builtin)) => {
let int_cmp = |pred, label| {
let int_val = env.builder.build_int_compare(
pred,
lhs_val.into_int_value(),
rhs_val.into_int_value(),
label,
);
BasicValueEnum::IntValue(int_val)
};
let float_cmp = |pred, label| {
let int_val = env.builder.build_float_compare(
pred,
lhs_val.into_float_value(),
rhs_val.into_float_value(),
label,
);
BasicValueEnum::IntValue(int_val)
};
match (lhs_builtin, rhs_builtin) {
(Builtin::Int128, Builtin::Int128) => int_cmp(IntPredicate::NE, "neq_i128"),
(Builtin::Int64, Builtin::Int64) => int_cmp(IntPredicate::NE, "neq_i64"),
(Builtin::Int32, Builtin::Int32) => int_cmp(IntPredicate::NE, "neq_i32"),
(Builtin::Int16, Builtin::Int16) => int_cmp(IntPredicate::NE, "neq_i16"),
(Builtin::Int8, Builtin::Int8) => int_cmp(IntPredicate::NE, "neq_i8"),
(Builtin::Int1, Builtin::Int1) => int_cmp(IntPredicate::NE, "neq_i1"),
(Builtin::Float64, Builtin::Float64) => float_cmp(FloatPredicate::ONE, "neq_f64"),
(Builtin::Float32, Builtin::Float32) => float_cmp(FloatPredicate::ONE, "neq_f32"),
(b1, b2) => {
todo!("Handle not equals for builtin layouts {:?} == {:?}", b1, b2);
}
}
}
(other1, other2) => {
// TODO NOTE: This should ultimately have a _ => todo!("type mismatch!") branch
todo!(
"implement not equals for layouts {:?} == {:?}",
other1,
other2
);
}
}
}

View file

@ -121,10 +121,16 @@ pub fn basic_type_from_layout<'ctx>(
} }
Builtin(builtin) => match builtin { Builtin(builtin) => match builtin {
Int128 => context.i128_type().as_basic_type_enum(),
Int64 => context.i64_type().as_basic_type_enum(), Int64 => context.i64_type().as_basic_type_enum(),
Int32 => context.i32_type().as_basic_type_enum(),
Int16 => context.i16_type().as_basic_type_enum(),
Int8 => context.i8_type().as_basic_type_enum(),
Int1 => context.bool_type().as_basic_type_enum(),
Float128 => context.f128_type().as_basic_type_enum(),
Float64 => context.f64_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(),
Bool => context.bool_type().as_basic_type_enum(), Float32 => context.f32_type().as_basic_type_enum(),
Byte => context.i8_type().as_basic_type_enum(), Float16 => context.f16_type().as_basic_type_enum(),
Str | EmptyStr => context Str | EmptyStr => context
.i8_type() .i8_type()
.ptr_type(AddressSpace::Generic) .ptr_type(AddressSpace::Generic)

View file

@ -1,2 +1,3 @@
pub mod build; pub mod build;
pub mod compare;
pub mod convert; pub mod convert;

View file

@ -1,4 +0,0 @@
.text
.file "my_module"
.section ".note.GNU-stack","",@progbits

View file

@ -1,805 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_builtins {
use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut};
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::expr::{Expr, Procs};
use roc_mono::layout::Layout;
use roc_types::subs::Subs;
#[test]
fn f64_sqrt() {
assert_evals_to!("Float.sqrt 144", 12.0, f64);
}
#[test]
fn f64_round() {
assert_evals_to!("Float.round 3.6", 4, i64);
}
#[test]
fn f64_abs() {
assert_evals_to!("Float.abs -4.7", 4.7, f64);
assert_evals_to!("Float.abs 5.8", 5.8, f64);
}
#[test]
fn i64_abs() {
assert_evals_to!("Int.abs -6", 6, i64);
assert_evals_to!("Int.abs 7", 7, i64);
}
#[test]
fn empty_list_literal() {
assert_evals_to!("[]", &[], &'static [i64]);
}
#[test]
fn int_list_literal() {
assert_evals_to!("[ 12, 9, 6, 3 ]", &[12, 9, 6, 3], &'static [i64]);
}
#[test]
fn gen_if_fn() {
assert_evals_to!(
indoc!(
r#"
limitedNegate = \num ->
if num == 1 then
-1
else if num == -1 then
1
else
num
limitedNegate 1
"#
),
-1,
i64
);
}
#[test]
fn gen_float_eq() {
assert_evals_to!(
indoc!(
r#"
1.0 == 1.0
"#
),
true,
bool
);
}
#[test]
fn gen_add_f64() {
with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"
1.1 + 2.4 + 3
"#
),
6.5,
f64
);
})
}
#[test]
fn gen_div_f64() {
assert_evals_to!(
indoc!(
r#"
48 / 2
"#
),
24.0,
f64
);
}
#[test]
fn gen_int_eq() {
assert_evals_to!(
indoc!(
r#"
4 == 4
"#
),
true,
bool
);
}
#[test]
fn gen_int_neq() {
assert_evals_to!(
indoc!(
r#"
4 != 5
"#
),
true,
bool
);
}
#[test]
fn gen_add_i64() {
with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"
1 + 2 + 3
"#
),
6,
i64
);
})
}
#[test]
fn gen_sub_f64() {
assert_evals_to!(
indoc!(
r#"
1.5 - 2.4 - 3
"#
),
-3.9,
f64
);
}
#[test]
fn gen_sub_i64() {
assert_evals_to!(
indoc!(
r#"
1 - 2 - 3
"#
),
-4,
i64
);
}
#[test]
fn gen_mul_i64() {
assert_evals_to!(
indoc!(
r#"
2 * 4 * 6
"#
),
48,
i64
);
}
#[test]
fn gen_div_i64() {
assert_evals_to!(
indoc!(
r#"
when 1000 // 10 is
Ok val -> val
Err _ -> -1
"#
),
100,
i64
);
}
#[test]
fn gen_div_by_zero_i64() {
assert_evals_to!(
indoc!(
r#"
when 1000 // 0 is
Err DivByZero -> 99
_ -> -24
"#
),
99,
i64
);
}
#[test]
fn gen_rem_i64() {
assert_evals_to!(
indoc!(
r#"
when Int.rem 8 3 is
Ok val -> val
_ -> -1
"#
),
2,
i64
);
}
#[test]
fn gen_rem_div_by_zero_i64() {
assert_evals_to!(
indoc!(
r#"
when Int.rem 8 0 is
Err DivByZero -> 4
_ -> -23
"#
),
4,
i64
);
}
#[test]
fn gen_is_zero_i64() {
assert_evals_to!("Int.isZero 0", true, bool);
assert_evals_to!("Int.isZero 1", false, bool);
}
#[test]
fn gen_is_positive_i64() {
assert_evals_to!("Int.isPositive 0", false, bool);
assert_evals_to!("Int.isPositive 1", true, bool);
assert_evals_to!("Int.isPositive -5", false, bool);
}
#[test]
fn gen_is_negative_i64() {
assert_evals_to!("Int.isNegative 0", false, bool);
assert_evals_to!("Int.isNegative 3", false, bool);
assert_evals_to!("Int.isNegative -2", true, bool);
}
#[test]
fn gen_is_positive_f64() {
assert_evals_to!("Float.isPositive 0.0", false, bool);
assert_evals_to!("Float.isPositive 4.7", true, bool);
assert_evals_to!("Float.isPositive -8.5", false, bool);
}
#[test]
fn gen_is_negative_f64() {
assert_evals_to!("Float.isNegative 0.0", false, bool);
assert_evals_to!("Float.isNegative 9.9", false, bool);
assert_evals_to!("Float.isNegative -4.4", true, bool);
}
#[test]
fn gen_is_zero_f64() {
assert_evals_to!("Float.isZero 0", true, bool);
assert_evals_to!("Float.isZero 0_0", true, bool);
assert_evals_to!("Float.isZero 0.0", true, bool);
assert_evals_to!("Float.isZero 1", false, bool);
}
#[test]
fn gen_is_odd() {
assert_evals_to!("Int.isOdd 4", false, bool);
assert_evals_to!("Int.isOdd 5", true, bool);
}
#[test]
fn gen_is_even() {
assert_evals_to!("Int.isEven 6", true, bool);
assert_evals_to!("Int.isEven 7", false, bool);
}
#[test]
fn sin() {
assert_evals_to!("Float.sin 0", 0.0, f64);
assert_evals_to!("Float.sin 1.41421356237", 0.9877659459922529, f64);
}
#[test]
fn cos() {
assert_evals_to!("Float.cos 0", 1.0, f64);
assert_evals_to!("Float.cos 3.14159265359", -1.0, f64);
}
#[test]
fn tan() {
assert_evals_to!("Float.tan 0", 0.0, f64);
assert_evals_to!("Float.tan 1", 1.557407724654902, f64);
}
#[test]
fn lt_i64() {
assert_evals_to!("1 < 2", true, bool);
assert_evals_to!("1 < 1", false, bool);
assert_evals_to!("2 < 1", false, bool);
assert_evals_to!("0 < 0", false, bool);
}
#[test]
fn lte_i64() {
assert_evals_to!("1 <= 1", true, bool);
assert_evals_to!("2 <= 1", false, bool);
assert_evals_to!("1 <= 2", true, bool);
assert_evals_to!("0 <= 0", true, bool);
}
#[test]
fn gt_i64() {
assert_evals_to!("2 > 1", true, bool);
assert_evals_to!("2 > 2", false, bool);
assert_evals_to!("1 > 1", false, bool);
assert_evals_to!("0 > 0", false, bool);
}
#[test]
fn gte_i64() {
assert_evals_to!("1 >= 1", true, bool);
assert_evals_to!("1 >= 2", false, bool);
assert_evals_to!("2 >= 1", true, bool);
assert_evals_to!("0 >= 0", true, bool);
}
#[test]
fn lt_f64() {
assert_evals_to!("1.1 < 1.2", true, bool);
assert_evals_to!("1.1 < 1.1", false, bool);
assert_evals_to!("1.2 < 1.1", false, bool);
assert_evals_to!("0.0 < 0.0", false, bool);
}
#[test]
fn lte_f64() {
assert_evals_to!("1.1 <= 1.1", true, bool);
assert_evals_to!("1.2 <= 1.1", false, bool);
assert_evals_to!("1.1 <= 1.2", true, bool);
assert_evals_to!("0.0 <= 0.0", true, bool);
}
#[test]
fn gt_f64() {
assert_evals_to!("2.2 > 1.1", true, bool);
assert_evals_to!("2.2 > 2.2", false, bool);
assert_evals_to!("1.1 > 2.2", false, bool);
assert_evals_to!("0.0 > 0.0", false, bool);
}
#[test]
fn gte_f64() {
assert_evals_to!("1.1 >= 1.1", true, bool);
assert_evals_to!("1.1 >= 1.2", false, bool);
assert_evals_to!("1.2 >= 1.1", true, bool);
assert_evals_to!("0.0 >= 0.0", true, bool);
}
#[test]
fn gen_order_of_arithmetic_ops() {
assert_evals_to!(
indoc!(
r#"
1 + 3 * 7 - 2
"#
),
20,
i64
);
}
#[test]
fn gen_order_of_arithmetic_ops_complex_float() {
assert_evals_to!(
indoc!(
r#"
48 / 2 + 3
"#
),
27.0,
f64
);
}
#[test]
fn if_guard_bind_variable() {
assert_evals_to!(
indoc!(
r#"
when 10 is
x if x == 5 -> 0
_ -> 42
"#
),
42,
i64
);
assert_evals_to!(
indoc!(
r#"
when 10 is
x if x == 10 -> 42
_ -> 0
"#
),
42,
i64
);
}
#[test]
fn tail_call_elimination() {
with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"
sum = \n, accum ->
when n is
0 -> accum
_ -> sum (n - 1) (n + accum)
sum 1_000_000 0
"#
),
500000500000,
i64
);
})
}
#[test]
fn int_negate() {
assert_evals_to!("Num.neg 123", -123, i64);
}
#[test]
fn gen_basic_fn() {
assert_evals_to!(
indoc!(
r#"
always42 : Num.Num Int.Integer -> Num.Num Int.Integer
always42 = \num -> 42
always42 5
"#
),
42,
i64
);
}
#[test]
fn list_push() {
assert_evals_to!("List.push [1] 2", &[1, 2], &'static [i64]);
assert_evals_to!("List.push [1, 1] 2", &[1, 1, 2], &'static [i64]);
assert_evals_to!("List.push [] 3", &[3], &'static [i64]);
assert_evals_to!(
"List.push [ True, False ] True",
&[true, false, true],
&'static [bool]
);
}
#[test]
fn list_single() {
assert_evals_to!("List.single 1", &[1], &'static [i64]);
assert_evals_to!("List.single 5.6", &[5.6], &'static [f64]);
}
#[test]
fn list_repeat() {
assert_evals_to!("List.repeat 5 1", &[1, 1, 1, 1, 1], &'static [i64]);
assert_evals_to!("List.repeat 4 2", &[2, 2, 2, 2], &'static [i64]);
assert_evals_to!("List.repeat 2 []", &[&[], &[]], &'static [&'static [i64]]);
}
#[test]
fn list_reverse() {
assert_evals_to!("List.reverse [1, 2, 3]", &[3, 2, 1], &'static [i64]);
assert_evals_to!("List.reverse [4]", &[4], &'static [i64]);
assert_evals_to!("List.reverse []", &[], &'static [i64]);
}
#[test]
fn empty_list_len() {
with_larger_debug_stack(|| {
assert_evals_to!("List.len []", 0, usize);
})
}
#[test]
fn basic_int_list_len() {
with_larger_debug_stack(|| {
assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize);
})
}
#[test]
fn loaded_int_list_len() {
assert_evals_to!(
indoc!(
r#"
nums = [ 2, 4, 6 ]
List.len nums
"#
),
3,
usize
);
}
#[test]
fn fn_int_list_len() {
assert_evals_to!(
indoc!(
r#"
getLen = \list -> List.len list
nums = [ 2, 4, 6, 8 ]
getLen nums
"#
),
4,
usize
);
}
#[test]
fn int_list_is_empty() {
assert_evals_to!("List.isEmpty [ 12, 9, 6, 3 ]", false, bool);
}
#[test]
fn empty_list_is_empty() {
with_larger_debug_stack(|| {
assert_evals_to!("List.isEmpty []", true, bool);
})
}
#[test]
fn first_int_list() {
with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"
when List.first [ 12, 9, 6, 3 ] is
Ok val -> val
Err _ -> -1
"#
),
12,
i64
);
})
}
#[test]
fn first_empty_list() {
with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"
when List.first [] is
Ok val -> val
Err _ -> -1
"#
),
-1,
i64
);
})
}
#[test]
fn get_int_list_ok() {
assert_evals_to!(
indoc!(
r#"
when List.get [ 12, 9, 6 ] 1 is
Ok val -> val
Err _ -> -1
"#
),
9,
i64
);
}
#[test]
fn get_int_list_oob() {
assert_evals_to!(
indoc!(
r#"
when List.get [ 12, 9, 6 ] 1000 is
Ok val -> val
Err _ -> -1
"#
),
-1,
i64
);
}
#[test]
fn get_set_unique_int_list() {
assert_evals_to!("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", 42, i64);
}
#[test]
fn set_unique_int_list() {
assert_evals_to!(
"List.set [ 12, 9, 7, 1, 5 ] 2 33",
&[12, 9, 33, 1, 5],
&'static [i64]
);
}
#[test]
fn set_unique_list_oob() {
assert_evals_to!(
"List.set [ 3, 17, 4.1 ] 1337 9.25",
&[3.0, 17.0, 4.1],
&'static [f64]
);
}
#[test]
fn set_shared_int_list() {
assert_evals_to!(
indoc!(
r#"
shared = [ 2.1, 4.3 ]
# This should not mutate the original
x = List.getUnsafe (List.set shared 1 7.7) 1
{ x, y: List.getUnsafe shared 1 }
"#
),
(7.7, 4.3),
(f64, f64)
);
}
#[test]
fn set_shared_list_oob() {
assert_evals_to!(
indoc!(
r#"
shared = [ 2, 4 ]
# This List.set is out of bounds, and should have no effect
x = List.getUnsafe (List.set shared 422 0) 1
{ x, y: List.getUnsafe shared 1 }
"#
),
(4, 4),
(i64, i64)
);
}
#[test]
fn get_unique_int_list() {
assert_evals_to!(
indoc!(
r#"
shared = [ 2, 4 ]
List.getUnsafe shared 1
"#
),
4,
i64
);
}
#[test]
fn int_to_float() {
assert_evals_to!(
indoc!(
r#"
Num.toFloat 0x9
"#
),
9.0,
f64
);
}
#[test]
fn gen_quicksort() {
with_larger_debug_stack(|| {
assert_evals_to!(
indoc!(
r#"
quicksort : List (Num a) -> List (Num a)
quicksort = \list ->
quicksortHelp list 0 (List.len list - 1)
quicksortHelp : List (Num a), Int, Int -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
Pair partitionIndex partitioned ->
partitioned
|> quicksortHelp low (partitionIndex - 1)
|> quicksortHelp (partitionIndex + 1) high
else
list
swap : Int, Int, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
list
|> List.set i atJ
|> List.set j atI
_ ->
[]
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
when partitionHelp (low - 1) low initialList high pivot is
Pair newI newList ->
Pair (newI + 1) (swap (newI + 1) high newList)
Err _ ->
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
Ok value ->
if value <= pivot then
partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
else
partitionHelp i (j + 1) list high pivot
Err _ ->
Pair i list
else
Pair i list
quicksort [ 7, 4, 21, 19 ]
"#
),
&[4, 7, 19, 21],
&'static [i64]
);
})
}
}

View file

@ -0,0 +1,465 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_list {
use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut};
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::expr::{Expr, Procs};
use roc_mono::layout::Layout;
use roc_types::subs::Subs;
#[test]
fn empty_list_literal() {
assert_evals_to!("[]", &[], &'static [i64]);
}
#[test]
fn int_list_literal() {
assert_evals_to!("[ 12, 9, 6, 3 ]", &[12, 9, 6, 3], &'static [i64]);
}
#[test]
fn list_push() {
assert_evals_to!("List.push [1] 2", &[1, 2], &'static [i64]);
assert_evals_to!("List.push [1, 1] 2", &[1, 1, 2], &'static [i64]);
assert_evals_to!("List.push [] 3", &[3], &'static [i64]);
assert_evals_to!(
indoc!(
r#"
initThrees : List Int
initThrees =
[]
List.push (List.push initThrees 3) 3
"#
),
&[3, 3],
&'static [i64]
);
assert_evals_to!(
"List.push [ True, False ] True",
&[true, false, true],
&'static [bool]
);
assert_evals_to!(
"List.push [ 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 ] 23",
&[11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
&'static [i64]
);
}
#[test]
fn list_single() {
assert_evals_to!("List.single 1", &[1], &'static [i64]);
assert_evals_to!("List.single 5.6", &[5.6], &'static [f64]);
}
#[test]
fn list_repeat() {
assert_evals_to!("List.repeat 5 1", &[1, 1, 1, 1, 1], &'static [i64]);
assert_evals_to!("List.repeat 4 2", &[2, 2, 2, 2], &'static [i64]);
assert_evals_to!("List.repeat 2 []", &[&[], &[]], &'static [&'static [i64]]);
assert_evals_to!(
indoc!(
r#"
noStrs : List Str
noStrs =
[]
List.repeat 2 noStrs
"#
),
&[&[], &[]],
&'static [&'static [i64]]
);
assert_evals_to!(
"List.repeat 15 4",
&[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
&'static [i64]
);
}
#[test]
fn list_reverse() {
assert_evals_to!(
"List.reverse [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]",
&[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
&'static [i64]
);
assert_evals_to!("List.reverse [1, 2, 3]", &[3, 2, 1], &'static [i64]);
assert_evals_to!("List.reverse [4]", &[4], &'static [i64]);
assert_evals_to!(
indoc!(
r#"
emptyList : List Int
emptyList =
[]
List.reverse emptyList
"#
),
&[],
&'static [i64]
);
assert_evals_to!("List.reverse []", &[], &'static [i64]);
}
#[test]
fn list_append() {
assert_evals_to!("List.append [] []", &[], &'static [i64]);
assert_evals_to!("List.append [ 12, 13 ] []", &[12, 13], &'static [i64]);
// assert_evals_to!(
// "List.append [ 1, 2 ] [ 3, 4 ]",
// &[1, 2, 3, 4],
// &'static [i64]
// );
}
#[test]
fn empty_list_len() {
assert_evals_to!("List.len []", 0, usize);
}
#[test]
fn basic_int_list_len() {
assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize);
}
#[test]
fn loaded_int_list_len() {
assert_evals_to!(
indoc!(
r#"
nums = [ 2, 4, 6 ]
List.len nums
"#
),
3,
usize
);
}
#[test]
fn fn_int_list_len() {
assert_evals_to!(
indoc!(
r#"
getLen = \list -> List.len list
nums = [ 2, 4, 6, 8 ]
getLen nums
"#
),
4,
usize
);
}
#[test]
fn int_list_is_empty() {
assert_evals_to!("List.isEmpty [ 12, 9, 6, 3 ]", false, bool);
}
#[test]
fn empty_list_is_empty() {
assert_evals_to!("List.isEmpty []", true, bool);
}
#[test]
fn first_int_list() {
assert_evals_to!(
indoc!(
r#"
when List.first [ 12, 9, 6, 3 ] is
Ok val -> val
Err _ -> -1
"#
),
12,
i64
);
}
#[test]
fn first_wildcard_empty_list() {
assert_evals_to!(
indoc!(
r#"
when List.first [] is
Ok _ -> 5
Err _ -> -1
"#
),
-1,
i64
);
}
// #[test]
// fn first_empty_list() {
// assert_evals_to!(
// indoc!(
// r#"
// when List.first [] is
// Ok val -> val
// Err _ -> -1
// "#
// ),
// -1,
// i64
// );
// }
#[test]
fn get_empty_list() {
assert_evals_to!(
indoc!(
r#"
when List.get [] 0 is
Ok val -> val
Err _ -> -1
"#
),
-1,
i64
);
}
#[test]
fn get_wildcard_empty_list() {
assert_evals_to!(
indoc!(
r#"
when List.get [] 0 is
Ok _ -> 5
Err _ -> -1
"#
),
-1,
i64
);
}
#[test]
fn get_int_list_ok() {
assert_evals_to!(
indoc!(
r#"
when List.get [ 12, 9, 6 ] 1 is
Ok val -> val
Err _ -> -1
"#
),
9,
i64
);
}
#[test]
fn get_int_list_oob() {
assert_evals_to!(
indoc!(
r#"
when List.get [ 12, 9, 6 ] 1000 is
Ok val -> val
Err _ -> -1
"#
),
-1,
i64
);
}
#[test]
fn get_set_unique_int_list() {
assert_evals_to!(
indoc!(
r#"
when List.get (List.set [ 12, 9, 7, 3 ] 1 42) 1 is
Ok val -> val
Err _ -> -1
"#
),
42,
i64
);
}
#[test]
fn set_unique_int_list() {
assert_evals_to!(
"List.set [ 12, 9, 7, 1, 5 ] 2 33",
&[12, 9, 33, 1, 5],
&'static [i64]
);
}
#[test]
fn set_unique_list_oob() {
assert_evals_to!(
"List.set [ 3, 17, 4.1 ] 1337 9.25",
&[3.0, 17.0, 4.1],
&'static [f64]
);
}
#[test]
fn set_shared_int_list() {
assert_evals_to!(
indoc!(
r#"
shared = [ 2.1, 4.3 ]
# This should not mutate the original
x =
when List.get (List.set shared 1 7.7) 1 is
Ok num -> num
Err _ -> 0
y =
when List.get shared 1 is
Ok num -> num
Err _ -> 0
{ x, y }
"#
),
(7.7, 4.3),
(f64, f64)
);
}
#[test]
fn set_shared_list_oob() {
assert_evals_to!(
indoc!(
r#"
shared = [ 2, 4 ]
# This List.set is out of bounds, and should have no effect
x =
when List.get (List.set shared 422 0) 1 is
Ok num -> num
Err _ -> 0
y =
when List.get shared 1 is
Ok num -> num
Err _ -> 0
{ x, y }
"#
),
(4, 4),
(i64, i64)
);
}
#[test]
fn get_unique_int_list() {
assert_evals_to!(
indoc!(
r#"
unique = [ 2, 4 ]
when List.get unique 1 is
Ok num -> num
Err _ -> -1
"#
),
4,
i64
);
}
#[test]
fn gen_quicksort() {
with_larger_debug_stack(|| {
// assert_evals_to!(
// indoc!(
// r#"
// quicksort : List (Num a) -> List (Num a)
// quicksort = \list ->
// quicksortHelp list 0 (List.len list - 1)
// quicksortHelp : List (Num a), Int, Int -> List (Num a)
// quicksortHelp = \list, low, high ->
// if low < high then
// when partition low high list is
// Pair partitionIndex partitioned ->
// partitioned
// |> quicksortHelp low (partitionIndex - 1)
// |> quicksortHelp (partitionIndex + 1) high
// else
// list
// swap : Int, Int, List a -> List a
// swap = \i, j, list ->
// when Pair (List.get list i) (List.get list j) is
// Pair (Ok atI) (Ok atJ) ->
// list
// |> List.set i atJ
// |> List.set j atI
// _ ->
// []
// partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
// partition = \low, high, initialList ->
// when List.get initialList high is
// Ok pivot ->
// when partitionHelp (low - 1) low initialList high pivot is
// Pair newI newList ->
// Pair (newI + 1) (swap (newI + 1) high newList)
// Err _ ->
// Pair (low - 1) initialList
// partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
// partitionHelp = \i, j, list, high, pivot ->
// if j < high then
// when List.get list j is
// Ok value ->
// if value <= pivot then
// partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
// else
// partitionHelp i (j + 1) list high pivot
// Err _ ->
// Pair i list
// else
// Pair i list
// quicksort [ 7, 4, 21, 19 ]
// "#
// ),
// &[4, 7, 19, 21],
// &'static [i64]
// );
})
}
}

View file

@ -0,0 +1,512 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate inkwell;
extern crate libc;
extern crate roc_gen;
#[macro_use]
mod helpers;
#[cfg(test)]
mod gen_num {
use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut};
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::expr::{Expr, Procs};
use roc_mono::layout::Layout;
use roc_types::subs::Subs;
#[test]
fn f64_sqrt() {
// FIXME this works with normal types, but fails when checking uniqueness types
assert_evals_to!(
indoc!(
r#"
when Num.sqrt 100 is
Ok val -> val
Err _ -> -1
"#
),
10.0,
f64
);
}
#[test]
fn f64_round() {
assert_evals_to!("Num.round 3.6", 4, i64);
}
#[test]
fn f64_abs() {
assert_evals_to!("Num.abs -4.7", 4.7, f64);
assert_evals_to!("Num.abs 5.8", 5.8, f64);
}
#[test]
fn i64_abs() {
assert_evals_to!("Num.abs -6", 6, i64);
assert_evals_to!("Num.abs 7", 7, i64);
assert_evals_to!("Num.abs 0", 0, i64);
assert_evals_to!("Num.abs -0", 0, i64);
assert_evals_to!("Num.abs -1", 1, i64);
assert_evals_to!("Num.abs 1", 1, i64);
assert_evals_to!("Num.abs 9_000_000_000_000", 9_000_000_000_000, i64);
assert_evals_to!("Num.abs -9_000_000_000_000", 9_000_000_000_000, i64);
}
#[test]
fn gen_if_fn() {
assert_evals_to!(
indoc!(
r#"
limitedNegate = \num ->
if num == 1 then
-1
else if num == -1 then
1
else
num
limitedNegate 1
"#
),
-1,
i64
);
}
#[test]
fn gen_float_eq() {
assert_evals_to!(
indoc!(
r#"
1.0 == 1.0
"#
),
true,
bool
);
}
#[test]
fn gen_add_f64() {
assert_evals_to!(
indoc!(
r#"
1.1 + 2.4 + 3
"#
),
6.5,
f64
);
}
#[test]
fn gen_div_f64() {
// FIXME this works with normal types, but fails when checking uniqueness types
assert_evals_to!(
indoc!(
r#"
when 48 / 2 is
Ok val -> val
Err _ -> -1
"#
),
24.0,
f64
);
}
#[test]
fn gen_int_eq() {
assert_evals_to!(
indoc!(
r#"
4 == 4
"#
),
true,
bool
);
}
#[test]
fn gen_int_neq() {
assert_evals_to!(
indoc!(
r#"
4 != 5
"#
),
true,
bool
);
}
#[test]
fn gen_add_i64() {
assert_evals_to!(
indoc!(
r#"
1 + 2 + 3
"#
),
6,
i64
);
}
#[test]
fn gen_sub_f64() {
assert_evals_to!(
indoc!(
r#"
1.5 - 2.4 - 3
"#
),
-3.9,
f64
);
}
#[test]
fn gen_sub_i64() {
assert_evals_to!(
indoc!(
r#"
1 - 2 - 3
"#
),
-4,
i64
);
}
#[test]
fn gen_mul_i64() {
assert_evals_to!(
indoc!(
r#"
2 * 4 * 6
"#
),
48,
i64
);
}
#[test]
fn gen_div_i64() {
assert_evals_to!(
indoc!(
r#"
when 1000 // 10 is
Ok val -> val
Err _ -> -1
"#
),
100,
i64
);
}
#[test]
fn gen_div_by_zero_i64() {
assert_evals_to!(
indoc!(
r#"
when 1000 // 0 is
Err DivByZero -> 99
_ -> -24
"#
),
99,
i64
);
}
#[test]
fn gen_rem_i64() {
assert_evals_to!(
indoc!(
r#"
when Num.rem 8 3 is
Ok val -> val
Err _ -> -1
"#
),
2,
i64
);
}
#[test]
fn gen_rem_div_by_zero_i64() {
assert_evals_to!(
indoc!(
r#"
when Num.rem 8 0 is
Err DivByZero -> 4
Ok _ -> -23
"#
),
4,
i64
);
}
#[test]
fn gen_is_zero_i64() {
assert_evals_to!("Num.isZero 0", true, bool);
assert_evals_to!("Num.isZero 1", false, bool);
}
#[test]
fn gen_is_positive_i64() {
assert_evals_to!("Num.isPositive 0", false, bool);
assert_evals_to!("Num.isPositive 1", true, bool);
assert_evals_to!("Num.isPositive -5", false, bool);
}
#[test]
fn gen_is_negative_i64() {
assert_evals_to!("Num.isNegative 0", false, bool);
assert_evals_to!("Num.isNegative 3", false, bool);
assert_evals_to!("Num.isNegative -2", true, bool);
}
#[test]
fn gen_is_positive_f64() {
assert_evals_to!("Num.isPositive 0.0", false, bool);
assert_evals_to!("Num.isPositive 4.7", true, bool);
assert_evals_to!("Num.isPositive -8.5", false, bool);
}
#[test]
fn gen_is_negative_f64() {
assert_evals_to!("Num.isNegative 0.0", false, bool);
assert_evals_to!("Num.isNegative 9.9", false, bool);
assert_evals_to!("Num.isNegative -4.4", true, bool);
}
#[test]
fn gen_is_zero_f64() {
assert_evals_to!("Num.isZero 0", true, bool);
assert_evals_to!("Num.isZero 0_0", true, bool);
assert_evals_to!("Num.isZero 0.0", true, bool);
assert_evals_to!("Num.isZero 1", false, bool);
}
#[test]
fn gen_is_odd() {
assert_evals_to!("Num.isOdd 4", false, bool);
assert_evals_to!("Num.isOdd 5", true, bool);
}
#[test]
fn gen_is_even() {
assert_evals_to!("Num.isEven 6", true, bool);
assert_evals_to!("Num.isEven 7", false, bool);
}
#[test]
fn sin() {
assert_evals_to!("Num.sin 0", 0.0, f64);
assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64);
}
#[test]
fn cos() {
assert_evals_to!("Num.cos 0", 1.0, f64);
assert_evals_to!("Num.cos 3.14159265359", -1.0, f64);
}
#[test]
fn tan() {
assert_evals_to!("Num.tan 0", 0.0, f64);
assert_evals_to!("Num.tan 1", 1.557407724654902, f64);
}
#[test]
fn lt_i64() {
assert_evals_to!("1 < 2", true, bool);
assert_evals_to!("1 < 1", false, bool);
assert_evals_to!("2 < 1", false, bool);
assert_evals_to!("0 < 0", false, bool);
}
#[test]
fn lte_i64() {
assert_evals_to!("1 <= 1", true, bool);
assert_evals_to!("2 <= 1", false, bool);
assert_evals_to!("1 <= 2", true, bool);
assert_evals_to!("0 <= 0", true, bool);
}
#[test]
fn gt_i64() {
assert_evals_to!("2 > 1", true, bool);
assert_evals_to!("2 > 2", false, bool);
assert_evals_to!("1 > 1", false, bool);
assert_evals_to!("0 > 0", false, bool);
}
#[test]
fn gte_i64() {
assert_evals_to!("1 >= 1", true, bool);
assert_evals_to!("1 >= 2", false, bool);
assert_evals_to!("2 >= 1", true, bool);
assert_evals_to!("0 >= 0", true, bool);
}
#[test]
fn lt_f64() {
assert_evals_to!("1.1 < 1.2", true, bool);
assert_evals_to!("1.1 < 1.1", false, bool);
assert_evals_to!("1.2 < 1.1", false, bool);
assert_evals_to!("0.0 < 0.0", false, bool);
}
#[test]
fn lte_f64() {
assert_evals_to!("1.1 <= 1.1", true, bool);
assert_evals_to!("1.2 <= 1.1", false, bool);
assert_evals_to!("1.1 <= 1.2", true, bool);
assert_evals_to!("0.0 <= 0.0", true, bool);
}
#[test]
fn gt_f64() {
assert_evals_to!("2.2 > 1.1", true, bool);
assert_evals_to!("2.2 > 2.2", false, bool);
assert_evals_to!("1.1 > 2.2", false, bool);
assert_evals_to!("0.0 > 0.0", false, bool);
}
#[test]
fn gte_f64() {
assert_evals_to!("1.1 >= 1.1", true, bool);
assert_evals_to!("1.1 >= 1.2", false, bool);
assert_evals_to!("1.2 >= 1.1", true, bool);
assert_evals_to!("0.0 >= 0.0", true, bool);
}
#[test]
fn gen_order_of_arithmetic_ops() {
assert_evals_to!(
indoc!(
r#"
1 + 3 * 7 - 2
"#
),
20,
i64
);
}
#[test]
fn gen_order_of_arithmetic_ops_complex_float() {
assert_evals_to!(
indoc!(
r#"
3 - 48 * 2.0
"#
),
-93.0,
f64
);
}
#[test]
fn if_guard_bind_variable() {
assert_evals_to!(
indoc!(
r#"
when 10 is
x if x == 5 -> 0
_ -> 42
"#
),
42,
i64
);
assert_evals_to!(
indoc!(
r#"
when 10 is
x if x == 10 -> 42
_ -> 0
"#
),
42,
i64
);
}
#[test]
fn tail_call_elimination() {
assert_evals_to!(
indoc!(
r#"
sum = \n, accum ->
when n is
0 -> accum
_ -> sum (n - 1) (n + accum)
sum 1_000_000 0
"#
),
500000500000,
i64
);
}
#[test]
fn int_negate() {
assert_evals_to!("Num.neg 123", -123, i64);
}
#[test]
fn gen_basic_fn() {
assert_evals_to!(
indoc!(
r#"
always42 : Num.Num Num.Integer -> Num.Num Num.Integer
always42 = \num -> 42
always42 5
"#
),
42,
i64
);
}
#[test]
fn int_to_float() {
assert_evals_to!("Num.toFloat 0x9", 9.0, f64);
}
#[test]
fn num_to_float() {
assert_evals_to!("Num.toFloat 9", 9.0, f64);
}
#[test]
fn float_to_float() {
assert_evals_to!("Num.toFloat 0.5", 0.5, f64);
}
}

View file

@ -13,7 +13,7 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod gen_primitives { mod gen_primitives {
use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut}; use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut};
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::execution_engine::JitFunction; use inkwell::execution_engine::JitFunction;
@ -298,7 +298,7 @@ mod gen_primitives {
} }
#[test] #[test]
fn apply_unnamed_fn() { fn apply_unnamed_identity() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -406,30 +406,27 @@ mod gen_primitives {
#[test] #[test]
fn gen_chained_defs() { fn gen_chained_defs() {
with_larger_debug_stack(|| { assert_evals_to!(
assert_evals_to!( indoc!(
indoc!( r#"
r#" x = i1
x = i1 i3 = i2
i3 = i2 i1 = 1337
i1 = 1337 i2 = i1
i2 = i1 y = 12.4
y = 12.4
i3
i3 "#
"# ),
), 1337,
1337, i64
i64 );
);
})
} }
#[test] #[test]
fn gen_nested_defs() { fn gen_nested_defs() {
with_larger_debug_stack(|| { assert_evals_to!(
assert_evals_to!( indoc!(
indoc!( r#"
r#"
x = 5 x = 5
answer = answer =
@ -460,10 +457,9 @@ mod gen_primitives {
answer answer
"# "#
), ),
1337, 1337,
i64 i64
); );
})
} }
} }

View file

@ -13,7 +13,7 @@ mod helpers;
#[cfg(test)] #[cfg(test)]
mod gen_tags { mod gen_tags {
use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut}; use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut};
use bumpalo::Bump; use bumpalo::Bump;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::execution_engine::JitFunction; use inkwell::execution_engine::JitFunction;
@ -213,10 +213,9 @@ mod gen_tags {
#[test] #[test]
fn even_odd() { fn even_odd() {
with_larger_debug_stack(|| { assert_evals_to!(
assert_evals_to!( indoc!(
indoc!( r#"
r#"
even = \n -> even = \n ->
when n is when n is
0 -> True 0 -> True
@ -231,11 +230,10 @@ mod gen_tags {
odd 5 && even 42 odd 5 && even 42
"# "#
), ),
true, true,
bool bool
); );
})
} }
#[test] #[test]

View file

@ -39,7 +39,7 @@ macro_rules! assert_llvm_evals_to {
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let layout = Layout::new(&arena, content, &subs, ptr_bytes) let layout = Layout::new(&arena, content, &subs, ptr_bytes)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); .unwrap_or_else(|err| panic!("Code gen error in NON-OPTIMIZED test: could not convert to layout. Err was {:?}", err));
let execution_engine = let execution_engine =
module module
.create_jit_execution_engine(OptimizationLevel::None) .create_jit_execution_engine(OptimizationLevel::None)
@ -73,40 +73,48 @@ macro_rules! assert_llvm_evals_to {
pointer_size: ptr_bytes, pointer_size: ptr_bytes,
jump_counter: arena.alloc(0), jump_counter: arena.alloc(0),
}; };
let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs);
// Put this module's ident_ids back in the interns, so we can use them in Env. let mut headers = Vec::with_capacity(procs.pending_specializations.len());
env.interns.all_ident_ids.insert(home, ident_ids); let mut layout_cache = roc_mono::layout::LayoutCache::default();
let mut headers = Vec::with_capacity(procs.len()); let (mut specializations, runtime_errors) =
let (mut proc_map, runtime_errors) = procs.into_map(); roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(runtime_errors, roc_collections::all::MutSet::default()); assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
// Put this module's ident_ids back in the interns, so we can use them in env.
// This must happen *after* building the headers, because otherwise there's
// a conflicting mutable borrow on ident_ids.
env.interns.all_ident_ids.insert(home, ident_ids);
// Add all the Proc headers to the module. // Add all the Proc headers to the module.
// We have to do this in a separate pass first, // We have to do this in a separate pass first,
// because their bodies may reference each other. // because their bodies may reference each other.
for (symbol, mut procs_by_layout) in proc_map.drain() { for ((symbol, layout), proc) in specializations.drain() {
for (layout, proc) in procs_by_layout.drain() { let (fn_val, arg_basic_types) =
let (fn_val, arg_basic_types) = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types)); headers.push((proc, fn_val, arg_basic_types));
}
} }
// Build each proc using its header info. // Build each proc using its header info.
for (proc, fn_val, arg_basic_types) in headers { for (proc, fn_val, arg_basic_types) in headers {
// NOTE: This is here to be uncommented in case verification fails.
// (This approach means we don't have to defensively clone name here.)
//
// println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types); build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) { if fn_val.verify(true) {
fpm.run_on(&fn_val); fpm.run_on(&fn_val);
} else { } else {
// NOTE: If this fails, uncomment the above println to debug. eprintln!(
panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!"); "\n\nFunction {:?} failed LLVM verification in NON-OPTIMIZED build. Its content was:\n", fn_val.get_name().to_str().unwrap()
);
fn_val.print_to_stderr();
panic!(
"The preceding code was from {:?}, which failed LLVM verification in NON-OPTIMIZED build.", fn_val.get_name().to_str().unwrap()
);
} }
} }
@ -137,7 +145,7 @@ macro_rules! assert_llvm_evals_to {
if main_fn.verify(true) { if main_fn.verify(true) {
fpm.run_on(&main_fn); fpm.run_on(&main_fn);
} else { } else {
panic!("Function {} failed LLVM verification.", main_fn_name); panic!("Main function {} failed LLVM verification in NON-OPTIMIZED build. Uncomment things nearby to see more details.", main_fn_name);
} }
// Verify the module // Verify the module
@ -203,7 +211,7 @@ macro_rules! assert_opt_evals_to {
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let layout = Layout::new(&arena, content, &subs, ptr_bytes) let layout = Layout::new(&arena, content, &subs, ptr_bytes)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); .unwrap_or_else(|err| panic!("Code gen error in OPTIMIZED test: could not convert to layout. Err was {:?}", err));
let execution_engine = let execution_engine =
module module
@ -240,38 +248,45 @@ macro_rules! assert_opt_evals_to {
}; };
let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs);
// Put this module's ident_ids back in the interns, so we can use them in Env. let mut headers = Vec::with_capacity(procs.pending_specializations.len());
env.interns.all_ident_ids.insert(home, ident_ids); let mut layout_cache = roc_mono::layout::LayoutCache::default();
let mut headers = Vec::with_capacity(procs.len()); let (mut specializations, runtime_errors) =
let (mut proc_map, runtime_errors) = procs.into_map(); roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(runtime_errors, roc_collections::all::MutSet::default()); assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
// Put this module's ident_ids back in the interns, so we can use them in env.
// This must happen *after* building the headers, because otherwise there's
// a conflicting mutable borrow on ident_ids.
env.interns.all_ident_ids.insert(home, ident_ids);
// Add all the Proc headers to the module. // Add all the Proc headers to the module.
// We have to do this in a separate pass first, // We have to do this in a separate pass first,
// because their bodies may reference each other. // because their bodies may reference each other.
for (symbol, mut procs_by_layout) in proc_map.drain() { for ((symbol, layout), proc) in specializations.drain() {
for (layout, proc) in procs_by_layout.drain() { let (fn_val, arg_basic_types) =
let (fn_val, arg_basic_types) = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types)); headers.push((proc, fn_val, arg_basic_types));
}
} }
// Build each proc using its header info. // Build each proc using its header info.
for (proc, fn_val, arg_basic_types) in headers { for (proc, fn_val, arg_basic_types) in headers {
// NOTE: This is here to be uncommented in case verification fails.
// (This approach means we don't have to defensively clone name here.)
//
// println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types); build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) { if fn_val.verify(true) {
fpm.run_on(&fn_val); fpm.run_on(&fn_val);
} else { } else {
// NOTE: If this fails, uncomment the above println to debug. eprintln!(
panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!"); "\n\nFunction {:?} failed LLVM verification in OPTIMIZED build. Its content was:\n", fn_val.get_name().to_str().unwrap()
);
fn_val.print_to_stderr();
panic!(
"The preceding code was from {:?}, which failed LLVM verification in OPTIMIZED build.", fn_val.get_name().to_str().unwrap()
);
} }
} }
@ -302,161 +317,7 @@ macro_rules! assert_opt_evals_to {
if main_fn.verify(true) { if main_fn.verify(true) {
fpm.run_on(&main_fn); fpm.run_on(&main_fn);
} else { } else {
panic!("Function {} failed LLVM verification.", main_fn_name); panic!("main function {} failed LLVM verification in OPTIMIZED build. Uncomment nearby statements to see more details.", main_fn_name);
}
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("Errors defining module: {:?}", errors);
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
unsafe {
let main: JitFunction<unsafe extern "C" fn() -> $ty> = execution_engine
.get_function(main_fn_name)
.ok()
.ok_or(format!("Unable to JIT compile `{}`", main_fn_name))
.expect("errored");
assert_eq!($transform(main.call()), $expected);
}
};
}
#[macro_export]
macro_rules! emit_expr {
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
let arena = Bump::new();
let (loc_expr, _output, problems, subs, var, constraint, home, interns) = uniq_expr($src);
let errors = problems.into_iter().filter(|problem| {
use roc_problem::can::Problem::*;
// Ignore "unused" problems
match problem {
UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => false,
_ => true,
}
}).collect::<Vec<roc_problem::can::Problem>>();
assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors);
let mut unify_problems = Vec::new();
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
assert_eq!(unify_problems, Vec::new(), "Encountered one or more type mismatches: {:?}", unify_problems);
let context = Context::create();
let module = context.create_module("app");
let builder = context.create_builder();
let opt_level = if cfg!(debug_assertions) {
roc_gen::llvm::build::OptLevel::Normal
} else {
roc_gen::llvm::build::OptLevel::Optimize
};
let fpm = PassManager::create(&module);
roc_gen::llvm::build::add_passes(&fpm, opt_level);
fpm.initialize();
// Compute main_fn_type before moving subs to Env
let layout = Layout::new(&arena, content, &subs, ptr_bytes)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let execution_engine =
module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
let main_fn_type = basic_type_from_layout(&arena, &context, &layout, ptr_bytes)
.fn_type(&[], false);
let main_fn_name = "$Test.main";
// Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
context: &context,
interns,
module: arena.alloc(module),
ptr_bytes
};
let mut procs = Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
// Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_env = roc_mono::expr::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
pointer_size: ptr_bytes,
jump_counter: arena.alloc(0),
};
let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs);
// Put this module's ident_ids back in the interns, so we can use them in Env.
env.interns.all_ident_ids.insert(home, ident_ids);
let mut headers = Vec::with_capacity(procs.len());
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for (symbol, opt_proc) in procs.as_map().into_iter() {
if let Some(proc) = opt_proc {
let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc);
headers.push((proc, fn_val, arg_basic_types));
}
}
// Build each proc using its header info.
for (proc, fn_val, arg_basic_types) in headers {
// NOTE: This is here to be uncommented in case verification fails.
// (This approach means we don't have to defensively clone name here.)
//
// println!("\n\nBuilding and then verifying function {}\n\n", name);
build_proc(&env, proc, &procs, fn_val, arg_basic_types);
if fn_val.verify(true) {
fpm.run_on(&fn_val);
} else {
// NOTE: If this fails, uncomment the above println to debug.
panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!");
}
}
// Add main to the module.
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
main_fn.set_call_conventions($crate::helpers::eval::MAIN_CALLING_CONVENTION);
// Add main's body
let basic_block = context.append_basic_block(main_fn, "entry");
builder.position_at_end(basic_block);
let ret = roc_gen::llvm::build::build_expr(
&env,
&ImMap::default(),
main_fn,
&main_body,
&mut Procs::default(),
);
builder.build_return(Some(&ret));
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
if main_fn.verify(true) {
fpm.run_on(&main_fn);
} else {
panic!("Function {} failed LLVM verification.", main_fn_name);
} }
// Verify the module // Verify the module

View file

@ -6,14 +6,12 @@ pub mod eval;
use self::bumpalo::Bump; use self::bumpalo::Bump;
use roc_builtins::unique::uniq_stdlib; use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
use roc_can::def::Def;
use roc_can::env::Env; use roc_can::env::Env;
use roc_can::expected::Expected; use roc_can::expected::Expected;
use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::expr::{canonicalize_expr, Expr, Output};
use roc_can::operator; use roc_can::operator;
use roc_can::pattern::Pattern;
use roc_can::scope::Scope; use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; use roc_collections::all::{ImMap, MutMap, SendMap};
use roc_constrain::expr::constrain_expr; use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import}; use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_module::ident::Ident; use roc_module::ident::Ident;
@ -26,34 +24,15 @@ use roc_region::all::{Located, Region};
use roc_solve::solve; use roc_solve::solve;
use roc_types::subs::{Content, Subs, VarStore, Variable}; use roc_types::subs::{Content, Subs, VarStore, Variable};
use roc_types::types::Type; use roc_types::types::Type;
use std::hash::Hash;
use std::path::{Path, PathBuf};
pub fn test_home() -> ModuleId { pub fn test_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"Test".into()) ModuleIds::default().get_or_insert(&"Test".into())
} }
pub fn infer_expr(
subs: Subs,
problems: &mut Vec<roc_solve::solve::TypeError>,
constraint: &Constraint,
expr_var: Variable,
) -> (Content, Subs) {
let env = solve::Env {
aliases: MutMap::default(),
vars_by_symbol: SendMap::default(),
};
let (solved, _) = solve::run(&env, problems, subs, constraint);
let content = solved.inner().get_without_compacting(expr_var).content;
(content, solved.into_inner())
}
/// Used in the with_larger_debug_stack() function, for tests that otherwise /// Used in the with_larger_debug_stack() function, for tests that otherwise
/// run out of stack space in debug builds (but don't in --release builds) /// run out of stack space in debug builds (but don't in --release builds)
#[allow(dead_code)] #[allow(dead_code)]
const EXPANDED_STACK_SIZE: usize = 4 * 1024 * 1024; const EXPANDED_STACK_SIZE: usize = 8 * 1024 * 1024;
/// Without this, some tests pass in `cargo test --release` but fail without /// Without this, some tests pass in `cargo test --release` but fail without
/// the --release flag because they run out of stack space. This increases /// the --release flag because they run out of stack space. This increases
@ -90,12 +69,23 @@ where
run_test() run_test()
} }
#[allow(dead_code)] pub fn infer_expr(
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> { subs: Subs,
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) problems: &mut Vec<roc_solve::solve::TypeError>,
constraint: &Constraint,
expr_var: Variable,
) -> (Content, Subs) {
let env = solve::Env {
aliases: MutMap::default(),
vars_by_symbol: SendMap::default(),
};
let (solved, _) = solve::run(&env, problems, subs, constraint);
let content = solved.inner().get_without_compacting(expr_var).content;
(content, solved.into_inner())
} }
#[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> { pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(&input, Attempting::Module); let state = State::new(&input, Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0); let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
@ -106,12 +96,10 @@ pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast
.map_err(|(fail, _)| fail) .map_err(|(fail, _)| fail)
} }
#[allow(dead_code)]
pub fn can_expr(expr_str: &str) -> CanExprOut { pub fn can_expr(expr_str: &str) -> CanExprOut {
can_expr_with(&Bump::new(), test_home(), expr_str) can_expr_with(&Bump::new(), test_home(), expr_str)
} }
#[allow(dead_code)]
pub fn uniq_expr( pub fn uniq_expr(
expr_str: &str, expr_str: &str,
) -> ( ) -> (
@ -129,7 +117,6 @@ pub fn uniq_expr(
uniq_expr_with(&Bump::new(), expr_str, declared_idents) uniq_expr_with(&Bump::new(), expr_str, declared_idents)
} }
#[allow(dead_code)]
pub fn uniq_expr_with( pub fn uniq_expr_with(
arena: &Bump, arena: &Bump,
expr_str: &str, expr_str: &str,
@ -241,29 +228,16 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
&loc_expr.value, &loc_expr.value,
); );
let mut with_builtins = loc_expr.value;
// Add builtin defs (e.g. List.get) directly to the canonical Expr, // Add builtin defs (e.g. List.get) directly to the canonical Expr,
// since we aren't using modules here. // since we aren't using modules here.
let mut with_builtins = loc_expr.value;
let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store); let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store);
for (symbol, expr) in builtin_defs { for (symbol, def) in builtin_defs {
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol) if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
{ {
with_builtins = Expr::LetNonRec( with_builtins = Expr::LetNonRec(
Box::new(Def { Box::new(def),
loc_pattern: Located {
region: Region::zero(),
value: Pattern::Identifier(symbol),
},
loc_expr: Located {
region: Region::zero(),
value: expr,
},
expr_var: var_store.fresh(),
pattern_vars: SendMap::default(),
annotation: None,
}),
Box::new(Located { Box::new(Located {
region: Region::zero(), region: Region::zero(),
value: with_builtins, value: with_builtins,
@ -340,162 +314,3 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
constraint, constraint,
} }
} }
#[allow(dead_code)]
pub fn mut_map_from_pairs<K, V, I>(pairs: I) -> MutMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq,
{
let mut answer = MutMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
#[allow(dead_code)]
pub fn im_map_from_pairs<K, V, I>(pairs: I) -> ImMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq + Clone,
V: Clone,
{
let mut answer = ImMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
#[allow(dead_code)]
pub fn send_set_from<V, I>(elems: I) -> SendSet<V>
where
I: IntoIterator<Item = V>,
V: Hash + Eq + Clone,
{
let mut answer = SendSet::default();
for elem in elems {
answer.insert(elem);
}
answer
}
#[allow(dead_code)]
pub fn fixtures_dir<'a>() -> PathBuf {
Path::new("tests").join("fixtures").join("build")
}
#[allow(dead_code)]
pub fn builtins_dir<'a>() -> PathBuf {
PathBuf::new().join("builtins")
}
// Check constraints
//
// Keep track of the used (in types or expectations) variables, and the declared variables (in
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
// and no variables that are used but not declared are allowed.
//
// There is one exception: the initial variable (that stores the type of the whole expression) is
// never declared, but is used.
#[allow(dead_code)]
pub fn assert_correct_variable_usage(constraint: &Constraint) {
// variables declared in constraint (flex_vars or rigid_vars)
// and variables actually used in constraints
let (declared, used) = variable_usage(constraint);
let used: ImSet<Variable> = used.clone().into();
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
for var in declared.flex_vars.clone() {
decl.insert(var);
}
let diff = used.clone().relative_complement(decl);
// NOTE: this checks whether we're using variables that are not declared. For recursive type
// definitions, their rigid types are declared twice, which is correct!
if !diff.is_empty() {
println!("VARIABLE USAGE PROBLEM");
println!("used: {:?}", &used);
println!("rigids: {:?}", &declared.rigid_vars);
println!("flexs: {:?}", &declared.flex_vars);
println!("difference: {:?}", &diff);
panic!("variable usage problem (see stdout for details)");
}
}
#[derive(Default)]
pub struct SeenVariables {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
let mut declared = SeenVariables::default();
let mut used = ImSet::default();
variable_usage_help(con, &mut declared, &mut used);
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
let mut used_vec: Vec<Variable> = used.into_iter().collect();
used_vec.sort();
declared.rigid_vars.sort();
declared.flex_vars.sort();
(declared, used_vec)
}
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
use Constraint::*;
match con {
True | SaveTheEnvironment => (),
Eq(tipe, expectation, _, _) => {
for v in tipe.variables() {
used.insert(v);
}
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Lookup(_, expectation, _) => {
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Pattern(_, _, tipe, pexpectation) => {
for v in tipe.variables() {
used.insert(v);
}
for v in pexpectation.get_type_ref().variables() {
used.insert(v);
}
}
Let(letcon) => {
declared.rigid_vars.extend(letcon.rigid_vars.clone());
declared.flex_vars.extend(letcon.flex_vars.clone());
variable_usage_help(&letcon.defs_constraint, declared, used);
variable_usage_help(&letcon.ret_constraint, declared, used);
}
And(constraints) => {
for sub in constraints {
variable_usage_help(sub, declared, used);
}
}
}
}

View file

@ -5,7 +5,7 @@ use roc_can::def::Declaration;
use roc_can::module::{canonicalize_module_defs, Module}; use roc_can::module::{canonicalize_module_defs, Module};
use roc_collections::all::{default_hasher, MutMap, MutSet}; use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_constrain::module::{ use roc_constrain::module::{
constrain_imported_aliases, constrain_imported_values, load_builtin_aliases, Import, constrain_imports, load_builtin_aliases, pre_constrain_imports, ConstrainableImports,
}; };
use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule}; use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule};
use roc_module::ident::{Ident, ModuleName}; use roc_module::ident::{Ident, ModuleName};
@ -16,9 +16,8 @@ use roc_parse::parser::{Fail, Parser, State};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_solve::module::SolvedModule; use roc_solve::module::SolvedModule;
use roc_solve::solve; use roc_solve::solve;
use roc_types::solved_types::{Solved, SolvedType}; use roc_types::solved_types::Solved;
use roc_types::subs::{Subs, VarStore, Variable}; use roc_types::subs::{Subs, VarStore, Variable};
use roc_types::types;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fs::read_to_string; use std::fs::read_to_string;
use std::io; use std::io;
@ -806,97 +805,22 @@ fn spawn_solve_module(
exposed_types: &mut SubsByModule, exposed_types: &mut SubsByModule,
stdlib: &StdLib, stdlib: &StdLib,
) { ) {
// Get the constriants for this module's imports. We do this on the main thread let home = module.module_id;
// Get the constraints for this module's imports. We do this on the main thread
// to avoid having to lock the map of exposed types, or to clone it // to avoid having to lock the map of exposed types, or to clone it
// (which would be more expensive for the main thread). // (which would be more expensive for the main thread).
let home = module.module_id; let ConstrainableImports {
let mut imported_symbols = Vec::with_capacity(module.references.len()); imported_symbols,
let mut imported_aliases = MutMap::default(); imported_aliases,
let mut unused_imports = imported_modules; // We'll remove these as we encounter them. unused_imports,
} = pre_constrain_imports(
// Translate referenced symbols into constraints. We do this on the main home,
// thread because we need exclusive access to the exposed_types map, in order &module.references,
// to get the necessary constraint info for any aliases we imported. We also imported_modules,
// resolve builtin types now, so we can use a refernce to stdlib instead of exposed_types,
// having to either clone it or recreate it from scratch on the other thread. stdlib,
for &symbol in module.references.iter() { );
let module_id = symbol.module_id();
// We used this one, so clearly it is not unused!
unused_imports.remove(&module_id);
if module_id.is_builtin() {
// For builtin modules, we create imports from the
// hardcoded builtin map.
match stdlib.types.get(&symbol) {
Some((solved_type, region)) => {
let loc_symbol = Located {
value: symbol,
region: *region,
};
imported_symbols.push(Import {
loc_symbol,
solved_type: solved_type.clone(),
});
}
None => {
let is_valid_alias = stdlib.applies.contains(&symbol)
// This wasn't a builtin value or Apply; maybe it was a builtin alias.
|| stdlib.aliases.contains_key(&symbol);
if !is_valid_alias {
panic!(
"Could not find {:?} in builtin types {:?} or aliases {:?}",
symbol, stdlib.types, stdlib.aliases
);
}
}
}
} else if module_id != home {
// We already have constraints for our own symbols.
let region = Region::zero(); // TODO this should be the region where this symbol was declared in its home module. Look that up!
let loc_symbol = Located {
value: symbol,
region,
};
match exposed_types.get(&module_id) {
Some(ExposedModuleTypes::Valid(solved_types, new_aliases)) => {
let solved_type = solved_types.get(&symbol).unwrap_or_else(|| {
panic!(
"Could not find {:?} in solved_types {:?}",
loc_symbol.value, solved_types
)
});
// TODO should this be a union?
for (k, v) in new_aliases.clone() {
imported_aliases.insert(k, v);
}
imported_symbols.push(Import {
loc_symbol,
solved_type: solved_type.clone(),
});
}
Some(ExposedModuleTypes::Invalid) => {
// If that module was invalid, use True constraints
// for everything imported from it.
imported_symbols.push(Import {
loc_symbol,
solved_type: SolvedType::Erroneous(types::Problem::InvalidModule),
});
}
None => {
panic!(
"Could not find module {:?} in exposed_types {:?}",
module_id, exposed_types
);
}
}
}
}
for unused_import in unused_imports { for unused_import in unused_imports {
todo!( todo!(
@ -920,10 +844,12 @@ fn spawn_solve_module(
// Finish constraining the module by wrapping the existing Constraint // Finish constraining the module by wrapping the existing Constraint
// in the ones we just computed. We can do this off the main thread. // in the ones we just computed. We can do this off the main thread.
// TODO what to do with the introduced rigids? let constraint = constrain_imports(
let (_introduced_rigids, constraint) = imported_symbols,
constrain_imported_values(imported_symbols, constraint, &mut var_store); imported_aliases,
let constraint = constrain_imported_aliases(imported_aliases, constraint, &mut var_store); constraint,
&mut var_store,
);
let mut constraint = load_builtin_aliases(aliases, constraint, &mut var_store); let mut constraint = load_builtin_aliases(aliases, constraint, &mut var_store);
// Turn Apply into Alias // Turn Apply into Alias

View file

@ -2,15 +2,15 @@ interface WithBuiltins
exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ] exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ]
imports [ Dep1, Dep2.{ two } ] imports [ Dep1, Dep2.{ two } ]
floatTest = Float.highest floatTest = Num.maxFloat
divisionFn = Float.div divisionFn = Num.div
x = 5.0 x = 5.0
divisionTest = Float.highest / x divisionTest = Num.maxFloat / x
intTest = Int.highest intTest = Num.maxInt
constantNum = 5 constantNum = 5

View file

@ -2,15 +2,15 @@ interface WithBuiltins
exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ] exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ]
imports [ Dep1, Dep2.{ two } ] imports [ Dep1, Dep2.{ two } ]
floatTest = Float.highest floatTest = Num.maxFloat
divisionFn = Float.div divisionFn = Num.div
x = 5.0 x = 5.0
divisionTest = Float.highest / x divisionTest = Num.maxFloat / x
intTest = Int.highest intTest = Num.maxInt
constantNum = 5 constantNum = 5

View file

@ -112,6 +112,7 @@ mod test_load {
); );
} }
} }
Builtin(_) => {}
cycle @ InvalidCycle(_, _) => { cycle @ InvalidCycle(_, _) => {
panic!("Unexpected cyclic def in module declarations: {:?}", cycle); panic!("Unexpected cyclic def in module declarations: {:?}", cycle);
} }
@ -204,12 +205,12 @@ mod test_load {
loaded_module, loaded_module,
hashmap! { hashmap! {
"floatTest" => "Float", "floatTest" => "Float",
"divisionFn" => "Float, Float -> Float", "divisionFn" => "Float, Float -> Result Float [ DivByZero ]*",
"divisionTest" => "Float", "divisionTest" => "Result Float [ DivByZero ]*",
"intTest" => "Int", "intTest" => "Int",
"x" => "Float", "x" => "Float",
"constantNum" => "Num *", "constantNum" => "Num *",
"divDep1ByDep2" => "Float", "divDep1ByDep2" => "Result Float [ DivByZero ]*",
"fromDep2" => "Float", "fromDep2" => "Float",
}, },
); );

View file

@ -107,6 +107,7 @@ mod test_uniq_load {
); );
} }
} }
Builtin(_) => {}
cycle @ InvalidCycle(_, _) => { cycle @ InvalidCycle(_, _) => {
panic!("Unexpected cyclic def in module declarations: {:?}", cycle); panic!("Unexpected cyclic def in module declarations: {:?}", cycle);
} }
@ -199,12 +200,12 @@ mod test_uniq_load {
loaded_module, loaded_module,
hashmap! { hashmap! {
"floatTest" => "Attr Shared Float", "floatTest" => "Attr Shared Float",
"divisionFn" => "Attr Shared (Attr * Float, Attr * Float -> Attr * Float)", "divisionFn" => "Attr Shared (Attr * Float, Attr * Float -> Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*)))",
"divisionTest" => "Attr * Float", "divisionTest" => "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
"intTest" => "Attr * Int", "intTest" => "Attr * Int",
"x" => "Attr * Float", "x" => "Attr * Float",
"constantNum" => "Attr * (Num (Attr * *))", "constantNum" => "Attr * (Num (Attr * *))",
"divDep1ByDep2" => "Attr * Float", "divDep1ByDep2" => "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
"fromDep2" => "Attr * Float", "fromDep2" => "Attr * Float",
}, },
); );
@ -272,6 +273,8 @@ mod test_uniq_load {
let loaded_module = let loaded_module =
load_fixture("interface_with_deps", "Primary", subs_by_module).await; load_fixture("interface_with_deps", "Primary", subs_by_module).await;
// the inferred signature for withDefault is wrong, part of the alias in alias issue.
// "withDefault" => "Attr * (Attr * (Res.Res (Attr a b) (Attr * *)), Attr a b -> Attr a b)",
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
@ -284,7 +287,7 @@ mod test_uniq_load {
"w" => "Attr * (Dep1.Identity (Attr * {}))", "w" => "Attr * (Dep1.Identity (Attr * {}))",
"succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))", "succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))",
"yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))", "yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))",
"withDefault" => "Attr * (Attr (* | a | b) (Res.Res (Attr a c) (Attr b *)), Attr a c -> Attr a c)", "withDefault" => "Attr * (Attr (* | b | c) (Res.Res (Attr b a) (Attr c *)), Attr b a -> Attr b a)",
}, },
); );
}); });
@ -300,8 +303,8 @@ mod test_uniq_load {
loaded_module, loaded_module,
hashmap! { hashmap! {
"withDefault" =>"Attr * (Attr (* | b | c) (Res (Attr b a) (Attr c err)), Attr b a -> Attr b a)", "withDefault" =>"Attr * (Attr (* | b | c) (Res (Attr b a) (Attr c err)), Attr b a -> Attr b a)",
"map" => "Attr * (Attr (* | c | d) (Res (Attr d a) (Attr c err)), Attr * (Attr d a -> Attr e b) -> Attr * (Res (Attr e b) (Attr c err)))", "map" => "Attr * (Attr (* | c | d) (Res (Attr c a) (Attr d err)), Attr * (Attr c a -> Attr e b) -> Attr * (Res (Attr e b) (Attr d err)))",
"andThen" => "Attr * (Attr (* | c | d) (Res (Attr d a) (Attr c err)), Attr * (Attr d a -> Attr e (Res (Attr f b) (Attr c err))) -> Attr e (Res (Attr f b) (Attr c err)))" "andThen" => "Attr * (Attr (* | c | d) (Res (Attr c a) (Attr d err)), Attr * (Attr c a -> Attr f (Res (Attr e b) (Attr d err))) -> Attr f (Res (Attr e b) (Attr d err)))",
}, },
); );
}); });

View file

@ -51,14 +51,13 @@ impl TagName {
impl ModuleName { impl ModuleName {
// NOTE: After adding one of these, go to `impl ModuleId` and // NOTE: After adding one of these, go to `impl ModuleId` and
// add a corresponding ModuleId to there! // add a corresponding ModuleId to there!
pub const FLOAT: &'static str = "Float";
pub const BOOL: &'static str = "Bool"; pub const BOOL: &'static str = "Bool";
pub const INT: &'static str = "Int";
pub const STR: &'static str = "Str"; pub const STR: &'static str = "Str";
pub const NUM: &'static str = "Num";
pub const LIST: &'static str = "List"; pub const LIST: &'static str = "List";
pub const MAP: &'static str = "Map"; pub const MAP: &'static str = "Map";
pub const SET: &'static str = "Set"; pub const SET: &'static str = "Set";
pub const NUM: &'static str = "Num"; pub const RESULT: &'static str = "Result";
pub fn as_str(&self) -> &str { pub fn as_str(&self) -> &str {
&*self.0 &*self.0

View file

@ -12,6 +12,7 @@
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
pub mod ident; pub mod ident;
pub mod low_level;
pub mod operator; pub mod operator;
pub mod symbol; pub mod symbol;

View file

@ -0,0 +1,36 @@
/// Low-level operations that get translated directly into e.g. LLVM instructions.
/// These are always wrapped when exposed to end users, and can only make it
/// into an Expr when added directly by can::builtins
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LowLevel {
ListLen,
ListGetUnsafe,
ListSet,
ListSetInPlace,
ListSingle,
ListRepeat,
ListReverse,
ListAppend,
ListPush,
NumAdd,
NumSub,
NumMul,
NumGt,
NumGte,
NumLt,
NumLte,
NumDivUnchecked,
NumRemUnchecked,
NumAbs,
NumNeg,
NumSin,
NumCos,
NumSqrtUnchecked,
NumRound,
NumToFloat,
Eq,
NotEq,
And,
Or,
Not,
}

View file

@ -58,7 +58,7 @@ impl Symbol {
.get_name(self.module_id()) .get_name(self.module_id())
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!( panic!(
"module_string could not find IdentIds for {:?} in interns {:?}", "module_string could not find IdentIds for module {:?} in {:?}",
self.module_id(), self.module_id(),
interns interns
) )
@ -71,7 +71,7 @@ impl Symbol {
.get(&self.module_id()) .get(&self.module_id())
.unwrap_or_else(|| { .unwrap_or_else(|| {
panic!( panic!(
"ident_string could not find IdentIds for {:?} in interns {:?}", "ident_string could not find IdentIds for module {:?} in {:?}",
self.module_id(), self.module_id(),
interns interns
) )
@ -177,7 +177,7 @@ lazy_static! {
std::sync::Mutex::new(roc_collections::all::MutMap::default()); std::sync::Mutex::new(roc_collections::all::MutMap::default());
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub struct Interns { pub struct Interns {
pub module_ids: ModuleIds, pub module_ids: ModuleIds,
pub all_ident_ids: MutMap<ModuleId, IdentIds>, pub all_ident_ids: MutMap<ModuleId, IdentIds>,
@ -575,91 +575,55 @@ define_builtins! {
0 ATTR: "#Attr" => { 0 ATTR: "#Attr" => {
0 UNDERSCORE: "_" // the _ used in pattern matches. This is Symbol 0. 0 UNDERSCORE: "_" // the _ used in pattern matches. This is Symbol 0.
1 ATTR_ATTR: "Attr" // the #Attr.Attr type alias, used in uniqueness types. 1 ATTR_ATTR: "Attr" // the #Attr.Attr type alias, used in uniqueness types.
2 ARG_1: "#arg1"
3 ARG_2: "#arg2"
4 ARG_3: "#arg3"
5 ARG_4: "#arg4"
6 ARG_5: "#arg5"
7 ARG_6: "#arg6"
8 ARG_7: "#arg7"
9 ARG_8: "#arg8"
} }
1 NUM: "Num" => { 1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias 0 NUM_NUM: "Num" imported // the Num.Num type alias
1 NUM_AT_NUM: "@Num" // the Num.@Num private tag 1 NUM_AT_NUM: "@Num" // the Num.@Num private tag
2 NUM_ABS: "abs" 2 NUM_INT: "Int" imported // the Int.Int type alias
3 NUM_NEG: "neg" 3 NUM_INTEGER: "Integer" imported // Int : Num Integer
4 NUM_ADD: "add" 4 NUM_AT_INTEGER: "@Integer" // the Int.@Integer private tag
5 NUM_SUB: "sub" 5 NUM_FLOAT: "Float" imported // the Float.Float type alias
6 NUM_MUL: "mul" 6 NUM_FLOATINGPOINT: "FloatingPoint" imported // Float : Num FloatingPoint
7 NUM_LT: "isLt" 7 NUM_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag
8 NUM_LTE: "isLte" 8 NUM_MAX_INT: "maxInt"
9 NUM_GT: "isGt" 9 NUM_MIN_INT: "minInt"
10 NUM_GTE: "isGte" 10 NUM_MAX_FLOAT: "maxFloat"
11 NUM_TO_FLOAT: "toFloat" 11 NUM_MIN_FLOAT: "minFloat"
12 NUM_ABS: "abs"
13 NUM_NEG: "neg"
14 NUM_ADD: "add"
15 NUM_SUB: "sub"
16 NUM_MUL: "mul"
17 NUM_LT: "isLt"
18 NUM_LTE: "isLte"
19 NUM_GT: "isGt"
20 NUM_GTE: "isGte"
21 NUM_TO_FLOAT: "toFloat"
22 NUM_SIN: "sin"
23 NUM_COS: "cos"
24 NUM_TAN: "tan"
25 NUM_IS_ZERO: "isZero"
26 NUM_IS_EVEN: "isEven"
27 NUM_IS_ODD: "isOdd"
28 NUM_IS_POSITIVE: "isPositive"
29 NUM_IS_NEGATIVE: "isNegative"
30 NUM_REM: "rem"
31 NUM_DIV_FLOAT: "div"
32 NUM_DIV_INT: "divFloor"
33 NUM_MOD_INT: "modInt"
34 NUM_MOD_FLOAT: "modFloat"
35 NUM_SQRT: "sqrt"
36 NUM_ROUND: "round"
} }
2 INT: "Int" => { 2 BOOL: "Bool" => {
0 INT_INT: "Int" imported // the Int.Int type alias
1 INT_INTEGER: "Integer" imported // Int : Num Integer
2 INT_AT_INTEGER: "@Integer" // the Int.@Integer private tag
3 INT_DIV: "div"
4 INT_MOD: "mod"
5 INT_HIGHEST: "highest"
6 INT_LOWEST: "lowest"
7 INT_ADD: "#add"
8 INT_SUB: "#sub"
9 INT_EQ_I64: "#eqi64" // Equality on 64-bit integers, the standard in Roc
10 INT_EQ_I1: "#eqi1" // Equality on boolean (theoretically i1) values
11 INT_EQ_I8: "#eqi8" // Equality on byte (theoretically i8) values
12 INT_DIV_UNSAFE: "divUnsafe" // TODO remove once we can code gen Result
13 INT_LT: "#lt"
14 INT_LTE: "#lte"
15 INT_GT: "#gt"
16 INT_GTE: "#gte"
17 INT_DIV_ARG_NUMERATOR: "div#numerator" // The first argument to `//`, the numerator
18 INT_DIV_ARG_DENOMINATOR: "div#denominator" // The first argument to `//`, the denominator
19 INT_NEQ_I64: "#neqi64"
20 INT_NEQ_I1: "#neqi1"
21 INT_NEQ_I8: "#neqi8"
22 INT_ABS: "abs"
23 INT_ABS_ARG: "abs#arg"
24 INT_REM_UNSAFE: "remUnsafe"
25 INT_REM: "rem"
26 INT_REM_ARG_0: "rem#arg0"
27 INT_REM_ARG_1: "rem#arg1"
28 INT_IS_ODD: "isOdd"
29 INT_IS_ODD_ARG: "isOdd#arg"
30 INT_IS_EVEN: "isEven"
31 INT_IS_EVEN_ARG: "isEven#arg"
32 INT_IS_ZERO: "isZero"
33 INT_IS_ZERO_ARG: "isZero#arg"
34 INT_IS_POSITIVE: "isPositive"
35 INT_IS_POSITIVE_ARG: "isPositive#arg"
36 INT_IS_NEGATIVE: "isNegative"
37 INT_IS_NEGATIVE_ARG: "isNegative#arg"
}
3 FLOAT: "Float" => {
0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias
1 FLOAT_FLOATINGPOINT: "FloatingPoint" imported // Float : Num FloatingPoint
2 FLOAT_AT_FLOATINGPOINT: "@FloatingPoint" // the Float.@FloatingPoint private tag
3 FLOAT_DIV: "div"
4 FLOAT_MOD: "mod"
5 FLOAT_SQRT: "sqrt"
6 FLOAT_HIGHEST: "highest"
7 FLOAT_LOWEST: "lowest"
8 FLOAT_ADD: "#add"
9 FLOAT_SUB: "#sub"
10 FLOAT_EQ: "eq"
11 FLOAT_ROUND: "round"
12 FLOAT_LT: "#lt"
13 FLOAT_LTE: "#lte"
14 FLOAT_GT: "gt"
15 FLOAT_GTE: "#gte"
16 FLOAT_ABS: "abs"
17 FLOAT_IS_POSITIVE: "isPositive"
18 FLOAT_IS_POSITIVE_ARG: "isPositive#arg"
19 FLOAT_IS_NEGATIVE: "isNegative"
20 FLOAT_IS_NEGATIVE_ARG: "isNegative#arg"
21 FLOAT_IS_ZERO: "isZero"
22 FLOAT_IS_ZERO_ARG: "isZero#arg"
23 FLOAT_SIN: "sin"
24 FLOAT_COS: "cos"
25 FLOAT_TAN: "tan"
26 FLOAT_TAN_ARG: "tan#arg"
}
4 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
1 BOOL_AND: "and" 1 BOOL_AND: "and"
2 BOOL_OR: "or" 2 BOOL_OR: "or"
@ -668,40 +632,35 @@ define_builtins! {
5 BOOL_EQ: "isEq" 5 BOOL_EQ: "isEq"
6 BOOL_NEQ: "isNotEq" 6 BOOL_NEQ: "isNotEq"
} }
5 STR: "Str" => { 3 STR: "Str" => {
0 STR_STR: "Str" imported // the Str.Str type alias 0 STR_STR: "Str" imported // the Str.Str type alias
1 STR_AT_STR: "@Str" // the Str.@Str private tag 1 STR_AT_STR: "@Str" // the Str.@Str private tag
2 STR_ISEMPTY: "isEmpty" 2 STR_ISEMPTY: "isEmpty"
3 STR_APPEND: "append" 3 STR_APPEND: "append"
} }
6 LIST: "List" => { 4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias 0 LIST_LIST: "List" imported // the List.List type alias
1 LIST_AT_LIST: "@List" // the List.@List private tag 1 LIST_AT_LIST: "@List" // the List.@List private tag
2 LIST_IS_EMPTY: "isEmpty" 2 LIST_IS_EMPTY: "isEmpty"
3 LIST_GET: "get" 3 LIST_GET: "get"
4 LIST_GET_ARG_LIST: "get#list" 4 LIST_SET: "set"
5 LIST_GET_ARG_INDEX: "get#index" 5 LIST_PUSH: "push"
6 LIST_SET: "set" 6 LIST_MAP: "map"
7 LIST_SET_IN_PLACE: "#setInPlace" 7 LIST_LEN: "len"
8 LIST_PUSH: "push" 8 LIST_FOLDL: "foldl"
9 LIST_MAP: "map" 9 LIST_FOLDR: "foldr"
10 LIST_LEN: "len" 10 LIST_CONCAT: "concat"
11 LIST_FOLDL: "foldl" 11 LIST_FIRST: "first"
12 LIST_FOLDR: "foldr" 12 LIST_SINGLE: "single"
13 LIST_GET_UNSAFE: "getUnsafe" 13 LIST_REPEAT: "repeat"
14 LIST_CONCAT: "concat" 14 LIST_REVERSE: "reverse"
15 LIST_FIRST: "first" 15 LIST_APPEND: "append"
16 LIST_FIRST_ARG: "first#list"
17 LIST_SINGLE: "single"
18 LIST_REPEAT: "repeat"
19 LIST_REVERSE: "reverse"
20 LIST_APPEND: "append"
} }
7 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias
1 RESULT_MAP: "map" 1 RESULT_MAP: "map"
} }
8 MAP: "Map" => { 6 MAP: "Map" => {
0 MAP_MAP: "Map" imported // the Map.Map type alias 0 MAP_MAP: "Map" imported // the Map.Map type alias
1 MAP_AT_MAP: "@Map" // the Map.@Map private tag 1 MAP_AT_MAP: "@Map" // the Map.@Map private tag
2 MAP_EMPTY: "empty" 2 MAP_EMPTY: "empty"
@ -709,7 +668,7 @@ define_builtins! {
4 MAP_GET: "get" 4 MAP_GET: "get"
5 MAP_INSERT: "insert" 5 MAP_INSERT: "insert"
} }
9 SET: "Set" => { 7 SET: "Set" => {
0 SET_SET: "Set" imported // the Set.Set type alias 0 SET_SET: "Set" imported // the Set.Set type alias
1 SET_AT_SET: "@Set" // the Set.@Set private tag 1 SET_AT_SET: "@Set" // the Set.@Set private tag
2 SET_EMPTY: "empty" 2 SET_EMPTY: "empty"
@ -721,5 +680,5 @@ define_builtins! {
8 SET_DIFF: "diff" 8 SET_DIFF: "diff"
} }
num_modules: 10 // Keep this count up to date by hand! (Rust macros can't do arithmetic.) num_modules: 8 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro)
} }

View file

@ -1,15 +1,14 @@
use crate::expr::Env; use crate::expr::Env;
use crate::expr::Expr; use crate::expr::Expr;
use crate::expr::Pattern; use crate::expr::Pattern;
use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use crate::expr::specialize_equality;
use crate::layout::Builtin; use crate::layout::Builtin;
use crate::layout::Layout; use crate::layout::Layout;
use crate::pattern::{Ctor, RenderAs, TagId, Union}; use crate::pattern::{Ctor, RenderAs, TagId, Union};
use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::TagName;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
/// COMPILE CASES /// COMPILE CASES
@ -1030,14 +1029,14 @@ fn test_to_equality<'a>(
let lhs = Expr::Byte(test_byte); let lhs = Expr::Byte(test_byte);
let rhs = path_to_expr(env, cond_symbol, &path, &cond_layout); let rhs = path_to_expr(env, cond_symbol, &path, &cond_layout);
tests.push((lhs, rhs, Layout::Builtin(Builtin::Byte))); tests.push((lhs, rhs, Layout::Builtin(Builtin::Int8)));
} }
Test::IsBit(test_bit) => { Test::IsBit(test_bit) => {
let lhs = Expr::Bool(test_bit); let lhs = Expr::Bool(test_bit);
let rhs = path_to_expr(env, cond_symbol, &path, &cond_layout); let rhs = path_to_expr(env, cond_symbol, &path, &cond_layout);
tests.push((lhs, rhs, Layout::Builtin(Builtin::Bool))); tests.push((lhs, rhs, Layout::Builtin(Builtin::Int1)));
} }
Test::IsStr(test_str) => { Test::IsStr(test_str) => {
@ -1059,7 +1058,7 @@ fn test_to_equality<'a>(
let lhs = Expr::Bool(true); let lhs = Expr::Bool(true);
let rhs = Expr::Store(stores, env.arena.alloc(expr)); let rhs = Expr::Store(stores, env.arena.alloc(expr));
tests.push((lhs, rhs, Layout::Builtin(Builtin::Bool))); tests.push((lhs, rhs, Layout::Builtin(Builtin::Int1)));
} }
} }
} }
@ -1122,9 +1121,9 @@ fn decide_to_branching<'a>(
let condition = boolean_all(env.arena, tests); let condition = boolean_all(env.arena, tests);
let branch_symbol = env.unique_symbol(); let branch_symbol = env.unique_symbol();
let stores = [(branch_symbol, Layout::Builtin(Builtin::Bool), condition)]; let stores = [(branch_symbol, Layout::Builtin(Builtin::Int1), condition)];
let cond_layout = Layout::Builtin(Builtin::Bool); let cond_layout = Layout::Builtin(Builtin::Int1);
( (
env.arena.alloc(stores), env.arena.alloc(stores),
@ -1203,16 +1202,18 @@ fn boolean_all<'a>(arena: &'a Bump, tests: Vec<(Expr<'a>, Expr<'a>, Layout<'a>)>
let mut expr = Expr::Bool(true); let mut expr = Expr::Bool(true);
for (lhs, rhs, layout) in tests.into_iter().rev() { for (lhs, rhs, layout) in tests.into_iter().rev() {
let test = specialize_equality(arena, lhs, rhs, layout.clone()); let test = Expr::RunLowLevel(
LowLevel::Eq,
bumpalo::vec![in arena; (lhs, layout.clone()), (rhs, layout.clone())].into_bump_slice(),
);
expr = Expr::CallByName { expr = Expr::RunLowLevel(
name: Symbol::BOOL_AND, LowLevel::And,
layout, arena.alloc([
args: arena.alloc([ (test, Layout::Builtin(Builtin::Int1)),
(test, Layout::Builtin(Builtin::Bool)), (expr, Layout::Builtin(Builtin::Int1)),
(expr, Layout::Builtin(Builtin::Bool)),
]), ]),
}; );
} }
expr expr

View file

@ -1,9 +1,10 @@
use crate::layout::{Builtin, Layout, LayoutCache}; use crate::layout::{list_layout_from_elem, Builtin, Layout, LayoutCache, LayoutProblem};
use crate::pattern::{Ctor, Guard, RenderAs, TagId}; use crate::pattern::{Ctor, Guard, RenderAs, TagId};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::{default_hasher, MutMap, MutSet}; use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{Content, FlatType, Subs, Variable}; use roc_types::subs::{Content, FlatType, Subs, Variable};
@ -13,10 +14,17 @@ use std::hash::Hash;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct PartialProc<'a> { pub struct PartialProc<'a> {
pub annotation: Variable, pub annotation: Variable,
pub patterns: Vec<'a, Symbol>, pub pattern_symbols: Vec<'a, Symbol>,
pub body: roc_can::expr::Expr, pub body: roc_can::expr::Expr,
} }
#[derive(Clone, Debug, PartialEq)]
pub struct PendingSpecialization<'a> {
pub fn_var: Variable,
pub ret_var: Variable,
pub pattern_vars: Vec<'a, Variable>,
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Proc<'a> { pub struct Proc<'a> {
pub name: Symbol, pub name: Symbol,
@ -28,12 +36,9 @@ pub struct Proc<'a> {
#[derive(Clone, Debug, PartialEq, Default)] #[derive(Clone, Debug, PartialEq, Default)]
pub struct Procs<'a> { pub struct Procs<'a> {
pub user_defined: MutMap<Symbol, PartialProc<'a>>, pub partial_procs: MutMap<Symbol, PartialProc<'a>>,
pub module_thunks: MutSet<Symbol>, pub module_thunks: MutSet<Symbol>,
runtime_errors: MutSet<Symbol>, pub pending_specializations: MutMap<Symbol, MutMap<Layout<'a>, PendingSpecialization<'a>>>,
specializations: MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>,
pending_specializations: MutMap<Symbol, Layout<'a>>,
builtin: MutSet<Symbol>,
} }
impl<'a> Procs<'a> { impl<'a> Procs<'a> {
@ -46,14 +51,17 @@ impl<'a> Procs<'a> {
loc_body: Located<roc_can::expr::Expr>, loc_body: Located<roc_can::expr::Expr>,
ret_var: Variable, ret_var: Variable,
) { ) {
let (_, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body); let (_, pattern_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body);
// a named closure // a named closure. Since these aren't specialized by the surrounding
self.user_defined.insert( // context, we can't add pending specializations for them yet.
// (If we did, all named polymorphic functions would immediately error
// on trying to convert a flex var to a Layout.)
self.partial_procs.insert(
name, name,
PartialProc { PartialProc {
annotation, annotation,
patterns: arg_symbols, pattern_symbols,
body: body.value, body: body.value,
}, },
); );
@ -70,44 +78,64 @@ impl<'a> Procs<'a> {
loc_body: Located<roc_can::expr::Expr>, loc_body: Located<roc_can::expr::Expr>,
ret_var: Variable, ret_var: Variable,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
) -> Result<Layout<'a>, ()> { ) -> Layout<'a> {
let (arg_vars, arg_symbols, body) = patterns_to_when(env, loc_args, ret_var, loc_body); let (pattern_vars, pattern_symbols, body) =
patterns_to_when(env, loc_args, ret_var, loc_body);
// an anonymous closure. These will always be specialized already // an anonymous closure. These will always be specialized already
// by the surrounding context // by the surrounding context, so we can add pending specializations
// for them immediately.
let layout = layout_cache
.from_var(env.arena, annotation, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
match specialize_proc_body( let pending = PendingSpecialization {
env,
self,
annotation,
ret_var, ret_var,
fn_var: annotation,
pattern_vars,
};
self.add_pending_specialization(symbol, layout.clone(), pending);
debug_assert!(!self.partial_procs.contains_key(&symbol), "Procs was told to insert a value for symbol {:?}, but there was already an entry for that key! Procs should never attempt to insert duplicates.", symbol);
self.partial_procs.insert(
symbol, symbol,
&arg_vars, PartialProc {
&arg_symbols, annotation,
annotation, pattern_symbols,
body.value, body: body.value,
layout_cache, },
) { );
Ok(proc) => {
let layout = layout_cache
.from_var(env.arena, annotation, env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
self.insert_specialization(symbol, layout.clone(), proc); layout
Ok(layout)
}
Err(()) => {
self.runtime_errors.insert(symbol);
Err(())
}
}
} }
fn insert_specialization(&mut self, symbol: Symbol, layout: Layout<'a>, proc: Proc<'a>) { fn add_pending_specialization(
&mut self,
symbol: Symbol,
layout: Layout<'a>,
pending: PendingSpecialization<'a>,
) {
let all_pending = self
.pending_specializations
.entry(symbol)
.or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher()));
all_pending.insert(layout, pending);
}
}
#[derive(Default)]
pub struct Specializations<'a> {
by_symbol: MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>,
runtime_errors: MutSet<Symbol>,
}
impl<'a> Specializations<'a> {
pub fn insert(&mut self, symbol: Symbol, layout: Layout<'a>, proc: Proc<'a>) {
let procs_by_layout = self let procs_by_layout = self
.specializations .by_symbol
.entry(symbol) .entry(symbol)
.or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher())); .or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher()));
@ -117,16 +145,26 @@ impl<'a> Procs<'a> {
!procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc) !procs_by_layout.contains_key(&layout) || procs_by_layout.get(&layout) == Some(&proc)
); );
// We shouldn't already have a runtime error recorded for this symbol
debug_assert!(!self.runtime_errors.contains(&symbol));
procs_by_layout.insert(layout, proc); procs_by_layout.insert(layout, proc);
} }
fn get_user_defined(&self, symbol: Symbol) -> Option<&PartialProc<'a>> { pub fn runtime_error(&mut self, symbol: Symbol) {
self.user_defined.get(&symbol) // We shouldn't already have a normal proc recorded for this symbol
debug_assert!(!self.by_symbol.contains_key(&symbol));
self.runtime_errors.insert(symbol);
}
pub fn into_owned(self) -> (MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>, MutSet<Symbol>) {
(self.by_symbol, self.runtime_errors)
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
let runtime_errors: usize = self.runtime_errors.len(); let runtime_errors: usize = self.runtime_errors.len();
let specializations: usize = self.specializations.len(); let specializations: usize = self.by_symbol.len();
runtime_errors + specializations runtime_errors + specializations
} }
@ -134,26 +172,6 @@ impl<'a> Procs<'a> {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.len() == 0 self.len() == 0
} }
fn insert_builtin(&mut self, symbol: Symbol) {
self.builtin.insert(symbol);
}
pub fn into_map(self) -> (MutMap<Symbol, MutMap<Layout<'a>, Proc<'a>>>, MutSet<Symbol>) {
let mut specializations = self.specializations;
for symbol in self.builtin.iter() {
// Builtins should only ever be stored as empty maps.
debug_assert!(
!specializations.contains_key(&symbol)
|| specializations.get(&symbol).unwrap().is_empty()
);
specializations.insert(*symbol, MutMap::default());
}
(specializations, self.runtime_errors)
}
} }
pub struct Env<'a, 'i> { pub struct Env<'a, 'i> {
@ -207,6 +225,7 @@ pub enum Expr<'a> {
args: &'a [(Expr<'a>, Layout<'a>)], args: &'a [(Expr<'a>, Layout<'a>)],
}, },
CallByPointer(&'a Expr<'a>, &'a [Expr<'a>], Layout<'a>), CallByPointer(&'a Expr<'a>, &'a [Expr<'a>], Layout<'a>),
RunLowLevel(LowLevel, &'a [(Expr<'a>, Layout<'a>)]),
// Exactly two conditional branches, e.g. if/else // Exactly two conditional branches, e.g. if/else
Cond { Cond {
@ -259,6 +278,7 @@ pub enum Expr<'a> {
elem_layout: Layout<'a>, elem_layout: Layout<'a>,
elems: &'a [Expr<'a>], elems: &'a [Expr<'a>],
}, },
EmptyArray,
RuntimeError(&'a str), RuntimeError(&'a str),
} }
@ -289,7 +309,7 @@ enum IntOrFloat {
/// Given the `a` in `Num a`, determines whether it's an int or a float /// Given the `a` in `Num a`, determines whether it's an int or a float
fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat {
match subs.get_without_compacting(var).content { match subs.get_without_compacting(var).content {
Content::Alias(Symbol::INT_INTEGER, args, _) => { Content::Alias(Symbol::NUM_INTEGER, args, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
IntOrFloat::IntType IntOrFloat::IntType
} }
@ -297,7 +317,7 @@ fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat {
// If this was still a (Num *), assume compiling it to an Int // If this was still a (Num *), assume compiling it to an Int
IntOrFloat::IntType IntOrFloat::IntType
} }
Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
IntOrFloat::FloatType IntOrFloat::FloatType
} }
@ -316,34 +336,6 @@ fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat {
} }
} }
/// Given a `Num a`, determines whether it's an int or a float
fn num_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat {
match subs.get_without_compacting(var).content {
Content::Alias(Symbol::NUM_NUM, args, _) => {
debug_assert!(args.len() == 1);
num_argument_to_int_or_float(subs, args[0].1)
}
Content::Alias(Symbol::INT_INT, _, _) => IntOrFloat::IntType,
Content::Alias(Symbol::FLOAT_FLOAT, _, _) => IntOrFloat::FloatType,
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => {
debug_assert!(attr_args.len() == 2);
// Recurse on the second argument
num_to_int_or_float(subs, attr_args[1])
}
other => {
panic!(
"Input variable is not a Num, but {:?} is a {:?}",
var, other
);
}
}
}
/// turn record/tag patterns into a when expression, e.g. /// turn record/tag patterns into a when expression, e.g.
/// ///
/// foo = \{ x } -> body /// foo = \{ x } -> body
@ -446,6 +438,12 @@ fn pattern_to_when<'a>(
(env.unique_symbol(), Located::at_zero(RuntimeError(error))) (env.unique_symbol(), Located::at_zero(RuntimeError(error)))
} }
MalformedPattern(problem, region) => {
// create the runtime error here, instead of delegating to When.
let error = roc_problem::can::RuntimeError::MalformedPattern(*problem, *region);
(env.unique_symbol(), Located::at_zero(RuntimeError(error)))
}
AppliedTag { .. } | RecordDestructure { .. } => { AppliedTag { .. } | RecordDestructure { .. } => {
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
@ -491,9 +489,9 @@ fn from_can<'a>(
Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)), Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)),
Var(symbol) => { Var(symbol) => {
if procs.module_thunks.contains(&symbol) { if procs.module_thunks.contains(&symbol) {
let partial_proc = procs.get_user_defined(symbol).unwrap(); let partial_proc = procs.partial_procs.get(&symbol).unwrap();
let fn_var = partial_proc.annotation; let fn_var = partial_proc.annotation;
let ret_var = partial_proc.annotation; let ret_var = fn_var; // These are the same for a thunk.
// This is a top-level declaration, which will code gen to a 0-arity thunk. // This is a top-level declaration, which will code gen to a 0-arity thunk.
call_by_name( call_by_name(
@ -516,15 +514,26 @@ fn from_can<'a>(
Closure(ann, name, _, loc_args, boxed_body) => { Closure(ann, name, _, loc_args, boxed_body) => {
let (loc_body, ret_var) = *boxed_body; let (loc_body, ret_var) = *boxed_body;
let layout =
procs.insert_anonymous(env, name, ann, loc_args, loc_body, ret_var, layout_cache);
match procs.insert_anonymous(env, name, ann, loc_args, loc_body, ret_var, layout_cache) Expr::FunctionPointer(name, layout)
{ }
Ok(layout) => Expr::FunctionPointer(name, layout),
Err(()) => { RunLowLevel { op, args, .. } => {
// TODO make this message better let op = optimize_low_level(env.subs, op, &args);
Expr::RuntimeErrorFunction("This function threw a runtime error.") let mut mono_args = Vec::with_capacity_in(args.len(), env.arena);
}
for (arg_var, arg_expr) in args {
let arg = from_can(env, arg_expr, procs, layout_cache);
let layout = layout_cache
.from_var(env.arena, arg_var, env.subs, env.pointer_size)
.unwrap_or_else(|err| todo!("TODO turn fn_var into a RuntimeError {:?}", err));
mono_args.push((arg, layout));
} }
Expr::RunLowLevel(op, mono_args.into_bump_slice())
} }
Call(boxed, loc_args, _) => { Call(boxed, loc_args, _) => {
@ -534,70 +543,15 @@ fn from_can<'a>(
Expr::Load(proc_name) => { Expr::Load(proc_name) => {
// Some functions can potentially mutate in-place. // Some functions can potentially mutate in-place.
// If we have one of those, switch to the in-place version if appropriate. // If we have one of those, switch to the in-place version if appropriate.
match specialize_builtin_functions( call_by_name(
env, env,
proc_name, procs,
loc_args.as_slice(), fn_var,
ret_var, ret_var,
proc_name,
loc_args,
layout_cache, layout_cache,
) { )
Symbol::LIST_SET => {
let subs = &env.subs;
// The first arg is the one with the List in it.
// List.set : List elem, Int, elem -> List elem
let (list_arg_var, _) = loc_args.get(0).unwrap();
let content = subs.get_without_compacting(*list_arg_var).content;
match content {
Content::Structure(FlatType::Apply(
Symbol::ATTR_ATTR,
attr_args,
)) => {
debug_assert!(attr_args.len() == 2);
// If the first argument (the List) is unique,
// then we can safely upgrade to List.set_in_place
let attr_arg_content =
subs.get_without_compacting(attr_args[0]).content;
let new_name = if attr_arg_content.is_unique(subs) {
Symbol::LIST_SET_IN_PLACE
} else {
Symbol::LIST_SET
};
call_by_name(
env,
procs,
fn_var,
ret_var,
new_name,
loc_args,
layout_cache,
)
}
_ => call_by_name(
env,
procs,
fn_var,
ret_var,
proc_name,
loc_args,
layout_cache,
),
}
}
specialized_proc_symbol => call_by_name(
env,
procs,
fn_var,
ret_var,
specialized_proc_symbol,
loc_args,
layout_cache,
),
}
} }
ptr => { ptr => {
// Call by pointer - the closure was anonymous, e.g. // Call by pointer - the closure was anonymous, e.g.
@ -651,6 +605,7 @@ fn from_can<'a>(
final_else, final_else,
} => { } => {
let mut expr = from_can(env, final_else.value, procs, layout_cache); let mut expr = from_can(env, final_else.value, procs, layout_cache);
let arena = env.arena;
let ret_layout = layout_cache let ret_layout = layout_cache
.from_var(env.arena, branch_var, env.subs, env.pointer_size) .from_var(env.arena, branch_var, env.subs, env.pointer_size)
@ -675,8 +630,8 @@ fn from_can<'a>(
}; };
expr = Expr::Store( expr = Expr::Store(
env.arena bumpalo::vec![in arena; (branch_symbol, Layout::Builtin(Builtin::Int1), cond)]
.alloc(vec![(branch_symbol, Layout::Builtin(Builtin::Bool), cond)]), .into_bump_slice(),
env.arena.alloc(cond_expr), env.arena.alloc(cond_expr),
); );
} }
@ -691,16 +646,16 @@ fn from_can<'a>(
} => { } => {
let arena = env.arena; let arena = env.arena;
let btree = crate::layout::record_fields_btree( let sorted_fields = crate::layout::sort_record_fields(
env.arena, env.arena,
record_var, record_var,
env.subs, env.subs,
env.pointer_size, env.pointer_size,
); );
let mut field_tuples = Vec::with_capacity_in(btree.len(), arena); let mut field_tuples = Vec::with_capacity_in(sorted_fields.len(), arena);
for (label, layout) in btree { for (label, layout) in sorted_fields {
let field = fields.remove(&label).unwrap(); let field = fields.remove(&label).unwrap();
let expr = from_can(env, field.loc_expr.value, procs, layout_cache); let expr = from_can(env, field.loc_expr.value, procs, layout_cache);
@ -800,7 +755,7 @@ fn from_can<'a>(
} => { } => {
let arena = env.arena; let arena = env.arena;
let btree = crate::layout::record_fields_btree( let sorted_fields = crate::layout::sort_record_fields(
env.arena, env.arena,
record_var, record_var,
env.subs, env.subs,
@ -808,9 +763,9 @@ fn from_can<'a>(
); );
let mut index = None; let mut index = None;
let mut field_layouts = Vec::with_capacity_in(btree.len(), env.arena); let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
for (current, (label, field_layout)) in btree.into_iter().enumerate() { for (current, (label, field_layout)) in sorted_fields.into_iter().enumerate() {
field_layouts.push(field_layout); field_layouts.push(field_layout);
if label == field { if label == field {
@ -834,28 +789,30 @@ fn from_can<'a>(
} => { } => {
let arena = env.arena; let arena = env.arena;
let subs = &env.subs; let subs = &env.subs;
let elem_content = subs.get_without_compacting(elem_var).content;
let elem_layout = match elem_content { match list_layout_from_elem(arena, subs, elem_var, env.pointer_size) {
// We have to special-case the empty list, because trying to Ok(Layout::Builtin(Builtin::EmptyList)) => Expr::EmptyArray,
// compute a layout for an unbound var won't work. Ok(Layout::Builtin(Builtin::List(elem_layout))) => {
Content::FlexVar(_) => Layout::Builtin(Builtin::EmptyList), let mut elems = Vec::with_capacity_in(loc_elems.len(), arena);
_ => match layout_cache.from_var(arena, elem_var, env.subs, env.pointer_size) {
Ok(layout) => layout.clone(), for loc_elem in loc_elems {
Err(()) => { elems.push(from_can(env, loc_elem.value, procs, layout_cache));
panic!("TODO gracefully handle List with invalid element layout");
} }
},
};
let mut elems = Vec::with_capacity_in(loc_elems.len(), arena); Expr::Array {
elem_layout: elem_layout.clone(),
for loc_elem in loc_elems { elems: elems.into_bump_slice(),
elems.push(from_can(env, loc_elem.value, procs, layout_cache)); }
} }
Ok(_) => {
Expr::Array { unreachable!();
elem_layout, }
elems: elems.into_bump_slice(), Err(problem) => {
todo!(
"gracefully handle List with element layout problem: {:?}",
problem
);
}
} }
} }
Accessor { .. } => todo!("record accessor"), Accessor { .. } => todo!("record accessor"),
@ -1337,99 +1294,45 @@ fn call_by_name<'a>(
loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>, loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
) -> Expr<'a> { ) -> Expr<'a> {
// create specialized procedure to call // Register a pending_specialization for this function
// If we need to specialize the body, this will get populated with the info
// we need to do that. This is defined outside the procs.get_user_defined(...) call
// because if we tried to specialize the body inside that match, we would
// get a borrow checker error about trying to borrow `procs` as mutable
// while there is still an active immutable borrow.
#[allow(clippy::type_complexity)]
let opt_specialize_body: Option<(Variable, roc_can::expr::Expr, Vec<'a, Symbol>)>;
match layout_cache.from_var(env.arena, fn_var, env.subs, env.pointer_size) { match layout_cache.from_var(env.arena, fn_var, env.subs, env.pointer_size) {
Ok(layout) => { Ok(layout) => {
match procs.get_user_defined(proc_name) { // Build the CallByName node
Some(partial_proc) => { let arena = env.arena;
match procs let mut args = Vec::with_capacity_in(loc_args.len(), arena);
.specializations let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena);
.get(&proc_name)
.and_then(|procs_by_layout| procs_by_layout.get(&layout))
{
Some(_) => {
// a specialization with this layout already exists.
opt_specialize_body = None;
}
None => {
if procs.pending_specializations.get(&proc_name) == Some(&layout) {
// If we're already in the process of specializing this, don't
// try to specialize it further; otherwise, we'll loop forever.
opt_specialize_body = None;
} else {
opt_specialize_body = Some((
partial_proc.annotation,
partial_proc.body.clone(),
partial_proc.patterns.clone(),
));
}
}
}
}
None => {
opt_specialize_body = None;
// This happens for built-in symbols (they are never defined as a Closure)
procs.insert_builtin(proc_name);
}
};
if let Some((annotation, body, loc_patterns)) = opt_specialize_body {
// register proc, so specialization doesn't loop infinitely
procs
.pending_specializations
.insert(proc_name, layout.clone());
let arg_vars = loc_args.iter().map(|v| v.0).collect::<std::vec::Vec<_>>();
match specialize_proc_body(
env,
procs,
fn_var,
ret_var,
proc_name,
&arg_vars,
&loc_patterns,
annotation,
body,
layout_cache,
) {
Ok(proc) => {
procs.insert_specialization(proc_name, layout.clone(), proc);
}
Err(()) => {
procs.runtime_errors.insert(proc_name);
}
}
}
// generate actual call
let mut args = Vec::with_capacity_in(loc_args.len(), env.arena);
for (var, loc_arg) in loc_args { for (var, loc_arg) in loc_args {
let layout = layout_cache pattern_vars.push(var);
.from_var(&env.arena, var, &env.subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err));
args.push((from_can(env, loc_arg.value, procs, layout_cache), layout)); match layout_cache.from_var(&env.arena, var, &env.subs, env.pointer_size) {
Ok(layout) => {
args.push((from_can(env, loc_arg.value, procs, layout_cache), layout));
}
Err(_) => {
// One of this function's arguments code gens to a runtime error,
// so attempting to call it will immediately crash.
return Expr::RuntimeError("");
}
}
} }
let pending = PendingSpecialization {
pattern_vars,
ret_var,
fn_var,
};
// register the pending specialization, so this gets code genned later
procs.add_pending_specialization(proc_name, layout.clone(), pending);
Expr::CallByName { Expr::CallByName {
name: proc_name, name: proc_name,
layout, layout,
args: args.into_bump_slice(), args: args.into_bump_slice(),
} }
} }
Err(()) => { Err(_) => {
// This function code gens to a runtime error, // This function code gens to a runtime error,
// so attempting to call it will immediately crash. // so attempting to call it will immediately crash.
Expr::RuntimeError("") Expr::RuntimeError("")
@ -1437,30 +1340,100 @@ fn call_by_name<'a>(
} }
} }
#[allow(clippy::too_many_arguments)] pub fn specialize_all<'a>(
fn specialize_proc_body<'a>( env: &mut Env<'a, '_>,
mut procs: Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
) -> (MutMap<(Symbol, Layout<'a>), Proc<'a>>, MutSet<Symbol>) {
let mut answer =
HashMap::with_capacity_and_hasher(procs.pending_specializations.len(), default_hasher());
let mut runtime_errors = MutSet::default();
let mut is_finished = procs.pending_specializations.is_empty();
// TODO replace this synchronous loop with a work-stealing queue which
// processes each entry in pending_specializations in parallel, one
// module at a time (because the &mut env will need exclusive access to
// that module's IdentIds; the only reason Env is &mut in specialize is
// that we need to generate unique symbols and register them in them module's
// IdentIds).
while !is_finished {
let Procs {
partial_procs,
module_thunks,
mut pending_specializations,
} = procs;
procs = Procs {
partial_procs,
module_thunks,
pending_specializations: MutMap::default(),
};
for (name, mut by_layout) in pending_specializations.drain() {
for (layout, pending) in by_layout.drain() {
// If we've already seen this (Symbol, Layout) combination before,
// don't try to specialize it again. If we do, we'll loop forever!
if !answer.contains_key(&(name, layout.clone())) {
// TODO should pending_procs hold a Rc<Proc>?
let partial_proc = procs
.partial_procs
.get(&name)
.unwrap_or_else(|| panic!("Could not find partial_proc for {:?}", name))
.clone();
match specialize(env, &mut procs, name, layout_cache, pending, partial_proc) {
Ok(proc) => {
answer.insert((name, layout), proc);
}
Err(_) => {
runtime_errors.insert(name);
}
}
}
}
}
is_finished = procs.pending_specializations.is_empty();
}
(answer, runtime_errors)
}
fn specialize<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
fn_var: Variable,
ret_var: Variable,
proc_name: Symbol, proc_name: Symbol,
loc_args: &[Variable],
pattern_symbols: &[Symbol],
annotation: Variable,
body: roc_can::expr::Expr,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
) -> Result<Proc<'a>, ()> { pending: PendingSpecialization<'a>,
partial_proc: PartialProc<'a>,
) -> Result<Proc<'a>, LayoutProblem> {
let PendingSpecialization {
ret_var,
fn_var,
pattern_vars,
} = pending;
let PartialProc {
annotation,
pattern_symbols,
body,
} = partial_proc;
// unify the called function with the specialized signature, then specialize the function body // unify the called function with the specialized signature, then specialize the function body
let snapshot = env.subs.snapshot(); let snapshot = env.subs.snapshot();
let unified = roc_unify::unify::unify(env.subs, annotation, fn_var); let unified = roc_unify::unify::unify(env.subs, annotation, fn_var);
debug_assert!(matches!(unified, roc_unify::unify::Unified::Success(_))); debug_assert!(matches!(unified, roc_unify::unify::Unified::Success(_)));
let specialized_body = from_can(env, body, procs, layout_cache); let specialized_body = from_can(env, body, procs, layout_cache);
// reset subs, so we don't get type errors when specializing for a different signature // reset subs, so we don't get type errors when specializing for a different signature
env.subs.rollback_to(snapshot); env.subs.rollback_to(snapshot);
let mut proc_args = Vec::with_capacity_in(loc_args.len(), &env.arena); let mut proc_args = Vec::with_capacity_in(pattern_vars.len(), &env.arena);
for (arg_var, arg_name) in loc_args.iter().zip(pattern_symbols.iter()) { debug_assert!(pattern_vars.len() == pattern_symbols.len());
for (arg_var, arg_name) in pattern_vars.iter().zip(pattern_symbols.iter()) {
let layout = layout_cache.from_var(&env.arena, *arg_var, env.subs, env.pointer_size)?; let layout = layout_cache.from_var(&env.arena, *arg_var, env.subs, env.pointer_size)?;
proc_args.push((layout, *arg_name)); proc_args.push((layout, *arg_name));
@ -1545,7 +1518,10 @@ fn from_can_pattern<'a>(
StrLiteral(v) => Pattern::StrLiteral(v.clone()), StrLiteral(v) => Pattern::StrLiteral(v.clone()),
Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()), Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()),
UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region), UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region),
MalformedPattern(_problem, region) => {
// TODO preserve malformed problem information here?
Pattern::UnsupportedPattern(*region)
}
NumLiteral(var, num) => match num_argument_to_int_or_float(env.subs, *var) { NumLiteral(var, num) => match num_argument_to_int_or_float(env.subs, *var) {
IntOrFloat::IntType => Pattern::IntLiteral(*num), IntOrFloat::IntType => Pattern::IntLiteral(*num),
IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64), IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64),
@ -1708,16 +1684,16 @@ fn from_can_pattern<'a>(
let mut it = destructs.iter(); let mut it = destructs.iter();
let mut opt_destruct = it.next(); let mut opt_destruct = it.next();
let btree = crate::layout::record_fields_btree( let sorted_fields = crate::layout::sort_record_fields(
env.arena, env.arena,
*whole_var, *whole_var,
env.subs, env.subs,
env.pointer_size, env.pointer_size,
); );
let mut field_layouts = Vec::with_capacity_in(btree.len(), env.arena); let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
for (label, field_layout) in btree.into_iter() { for (label, field_layout) in sorted_fields.into_iter() {
if let Some(destruct) = opt_destruct { if let Some(destruct) = opt_destruct {
if destruct.value.label == label { if destruct.value.label == label {
opt_destruct = it.next(); opt_destruct = it.next();
@ -1772,101 +1748,40 @@ fn from_can_record_destruct<'a>(
} }
} }
pub fn specialize_equality<'a>( /// Potentially translate LowLevel operations into more efficient ones based on
arena: &'a Bump, /// uniqueness type info.
lhs: Expr<'a>, ///
rhs: Expr<'a>, /// For example, turning LowLevel::ListSet to LowLevel::ListSetInPlace if the
layout: Layout<'a>, /// list is Unique.
) -> Expr<'a> { fn optimize_low_level(
let name = match &layout { subs: &Subs,
Layout::Builtin(builtin) => match builtin { op: LowLevel,
Builtin::Int64 => Symbol::INT_EQ_I64, args: &[(Variable, roc_can::expr::Expr)],
Builtin::Float64 => Symbol::FLOAT_EQ, ) -> LowLevel {
Builtin::Byte => Symbol::INT_EQ_I8, match op {
Builtin::Bool => Symbol::INT_EQ_I1, LowLevel::ListSet => {
other => todo!("Cannot yet compare for equality {:?}", other), // The first arg is the one with the List in it.
}, // List.set : List elem, Int, elem -> List elem
other => todo!("Cannot yet compare for equality {:?}", other), let list_arg_var = args[0].0;
}; let content = subs.get_without_compacting(list_arg_var).content;
Expr::CallByName { match content {
name, Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, attr_args)) => {
layout: layout.clone(), debug_assert_eq!(attr_args.len(), 2);
args: arena.alloc([(lhs, layout.clone()), (rhs, layout)]),
}
}
fn specialize_builtin_functions<'a>( // If the first argument (the List) is unique,
env: &mut Env<'a, '_>, // then we can safely upgrade to List.set_in_place
symbol: Symbol, let attr_arg_content = subs.get_without_compacting(attr_args[0]).content;
loc_args: &[(Variable, Located<roc_can::expr::Expr>)],
ret_var: Variable,
layout_cache: &mut LayoutCache<'a>,
) -> Symbol {
use IntOrFloat::*;
if !symbol.is_builtin() { if attr_arg_content.is_unique(subs) {
// return unchanged LowLevel::ListSetInPlace
symbol } else {
} else { LowLevel::ListSet
match symbol { }
Symbol::NUM_ADD => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_ADD,
IntType => Symbol::INT_ADD,
},
Symbol::NUM_SUB => match num_to_int_or_float(env.subs, ret_var) {
FloatType => Symbol::FLOAT_SUB,
IntType => Symbol::INT_SUB,
},
Symbol::NUM_LTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LTE,
IntType => Symbol::INT_LTE,
},
Symbol::NUM_LT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_LT,
IntType => Symbol::INT_LT,
},
Symbol::NUM_GTE => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GTE,
IntType => Symbol::INT_GTE,
},
Symbol::NUM_GT => match num_to_int_or_float(env.subs, loc_args[0].0) {
FloatType => Symbol::FLOAT_GT,
IntType => Symbol::INT_GT,
},
// TODO make this work for more than just int/float
Symbol::BOOL_EQ => {
match layout_cache.from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_EQ_I64,
Builtin::Float64 => Symbol::FLOAT_EQ,
Builtin::Bool => Symbol::INT_EQ_I1,
Builtin::Byte => Symbol::INT_EQ_I8,
_ => panic!("Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
} }
_ => op,
} }
Symbol::BOOL_NEQ => {
match layout_cache.from_var(env.arena, loc_args[0].0, env.subs, env.pointer_size) {
Ok(Layout::Builtin(builtin)) => match builtin {
Builtin::Int64 => Symbol::INT_NEQ_I64,
Builtin::Bool => Symbol::INT_NEQ_I1,
Builtin::Byte => Symbol::INT_NEQ_I8,
_ => panic!("Not-Equality not implemented for {:?}", builtin),
},
Ok(complex) => panic!(
"TODO support equality on complex layouts like {:?}",
complex
),
Err(()) => panic!("Invalid layout"),
}
}
_ => symbol,
} }
_ => op,
} }
} }

View file

@ -4,10 +4,18 @@ use roc_collections::all::MutMap;
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, FlatType, Subs, Variable}; use roc_types::subs::{Content, FlatType, Subs, Variable};
use std::collections::BTreeMap;
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize; pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize;
/// If a (Num *) gets translated to a Layout, this is the numeric type it defaults to.
const DEFAULT_NUM_BUILTIN: Builtin<'_> = Builtin::Int64;
#[derive(Debug, Clone)]
pub enum LayoutProblem {
UnresolvedTypeVar,
Erroneous,
}
/// Types for code gen must be monomorphic. No type variables allowed! /// Types for code gen must be monomorphic. No type variables allowed!
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Layout<'a> { pub enum Layout<'a> {
@ -21,10 +29,16 @@ pub enum Layout<'a> {
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Builtin<'a> { pub enum Builtin<'a> {
Int128,
Int64, Int64,
Int32,
Int16,
Int8,
Int1,
Float128,
Float64, Float64,
Bool, Float32,
Byte, Float16,
Str, Str,
Map(&'a Layout<'a>, &'a Layout<'a>), Map(&'a Layout<'a>, &'a Layout<'a>),
Set(&'a Layout<'a>), Set(&'a Layout<'a>),
@ -41,23 +55,18 @@ impl<'a> Layout<'a> {
content: Content, content: Content,
subs: &Subs, subs: &Subs,
pointer_size: u32, pointer_size: u32,
) -> Result<Self, ()> { ) -> Result<Self, LayoutProblem> {
use roc_types::subs::Content::*; use roc_types::subs::Content::*;
match content { match content {
var @ FlexVar(_) | var @ RigidVar(_) => { FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar),
panic!(
"Layout::new encountered an unresolved {:?} - subs was {:?}",
var, subs
);
}
Structure(flat_type) => layout_from_flat_type(arena, flat_type, subs, pointer_size), Structure(flat_type) => layout_from_flat_type(arena, flat_type, subs, pointer_size),
Alias(Symbol::INT_INT, args, _) => { Alias(Symbol::NUM_INT, args, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int64)) Ok(Layout::Builtin(Builtin::Int64))
} }
Alias(Symbol::FLOAT_FLOAT, args, _) => { Alias(Symbol::NUM_FLOAT, args, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
} }
@ -67,7 +76,7 @@ impl<'a> Layout<'a> {
subs, subs,
pointer_size, pointer_size,
), ),
Error => Err(()), Error => Err(LayoutProblem::Erroneous),
} }
} }
@ -79,7 +88,7 @@ impl<'a> Layout<'a> {
var: Variable, var: Variable,
subs: &Subs, subs: &Subs,
pointer_size: u32, pointer_size: u32,
) -> Result<Self, ()> { ) -> Result<Self, LayoutProblem> {
let content = subs.get_without_compacting(var).content; let content = subs.get_without_compacting(var).content;
Self::new(arena, content, subs, pointer_size) Self::new(arena, content, subs, pointer_size)
@ -140,7 +149,7 @@ impl<'a> Layout<'a> {
/// Avoid recomputing Layout from Variable multiple times. /// Avoid recomputing Layout from Variable multiple times.
#[derive(Default)] #[derive(Default)]
pub struct LayoutCache<'a> { pub struct LayoutCache<'a> {
layouts: MutMap<Variable, Result<Layout<'a>, ()>>, layouts: MutMap<Variable, Result<Layout<'a>, LayoutProblem>>,
} }
impl<'a> LayoutCache<'a> { impl<'a> LayoutCache<'a> {
@ -153,7 +162,7 @@ impl<'a> LayoutCache<'a> {
var: Variable, var: Variable,
subs: &Subs, subs: &Subs,
pointer_size: u32, pointer_size: u32,
) -> Result<Layout<'a>, ()> { ) -> Result<Layout<'a>, LayoutProblem> {
// Store things according to the root Variable, to avoid duplicate work. // Store things according to the root Variable, to avoid duplicate work.
let var = subs.get_root_key_without_compacting(var); let var = subs.get_root_key_without_compacting(var);
@ -169,10 +178,16 @@ impl<'a> LayoutCache<'a> {
} }
impl<'a> Builtin<'a> { impl<'a> Builtin<'a> {
const I128_SIZE: u32 = std::mem::size_of::<i128>() as u32;
const I64_SIZE: u32 = std::mem::size_of::<i64>() as u32; const I64_SIZE: u32 = std::mem::size_of::<i64>() as u32;
const I32_SIZE: u32 = std::mem::size_of::<i32>() as u32;
const I16_SIZE: u32 = std::mem::size_of::<i16>() as u32;
const I8_SIZE: u32 = std::mem::size_of::<i8>() as u32;
const I1_SIZE: u32 = std::mem::size_of::<bool>() as u32;
const F128_SIZE: u32 = 16;
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32; const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
const BOOL_SIZE: u32 = std::mem::size_of::<bool>() as u32; const F32_SIZE: u32 = std::mem::size_of::<f32>() as u32;
const BYTE_SIZE: u32 = std::mem::size_of::<u8>() as u32; const F16_SIZE: u32 = 2;
/// Number of machine words in an empty one of these /// Number of machine words in an empty one of these
pub const STR_WORDS: u32 = 2; pub const STR_WORDS: u32 = 2;
@ -180,7 +195,7 @@ impl<'a> Builtin<'a> {
pub const SET_WORDS: u32 = Builtin::MAP_WORDS; // Set is an alias for Map with {} for value pub const SET_WORDS: u32 = Builtin::MAP_WORDS; // Set is an alias for Map with {} for value
pub const LIST_WORDS: u32 = 2; pub const LIST_WORDS: u32 = 2;
/// Layout of collection wrapper for List and Str - a struct of (pointre, length). /// Layout of collection wrapper for List and Str - a struct of (pointer, length).
/// ///
/// We choose this layout (with pointer first) because it's how /// We choose this layout (with pointer first) because it's how
/// Rust slices are laid out, meaning we can cast to/from them for free. /// Rust slices are laid out, meaning we can cast to/from them for free.
@ -191,10 +206,16 @@ impl<'a> Builtin<'a> {
use Builtin::*; use Builtin::*;
match self { match self {
Int128 => Builtin::I128_SIZE,
Int64 => Builtin::I64_SIZE, Int64 => Builtin::I64_SIZE,
Int32 => Builtin::I32_SIZE,
Int16 => Builtin::I16_SIZE,
Int8 => Builtin::I8_SIZE,
Int1 => Builtin::I1_SIZE,
Float128 => Builtin::F128_SIZE,
Float64 => Builtin::F64_SIZE, Float64 => Builtin::F64_SIZE,
Bool => Builtin::BOOL_SIZE, Float32 => Builtin::F32_SIZE,
Byte => Builtin::BYTE_SIZE, Float16 => Builtin::F16_SIZE,
Str | EmptyStr => Builtin::STR_WORDS * pointer_size, Str | EmptyStr => Builtin::STR_WORDS * pointer_size,
Map(_, _) | EmptyMap => Builtin::MAP_WORDS * pointer_size, Map(_, _) | EmptyMap => Builtin::MAP_WORDS * pointer_size,
Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size, Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size,
@ -206,7 +227,8 @@ impl<'a> Builtin<'a> {
use Builtin::*; use Builtin::*;
match self { match self {
Int64 | Float64 | Bool | Byte | EmptyStr | EmptyMap | EmptyList | EmptySet => true, Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Float128 | Float64 | Float32
| Float16 | EmptyStr | EmptyMap | EmptyList | EmptySet => true,
Str | Map(_, _) | Set(_) | List(_) => false, Str | Map(_, _) | Set(_) | List(_) => false,
} }
} }
@ -217,23 +239,23 @@ fn layout_from_flat_type<'a>(
flat_type: FlatType, flat_type: FlatType,
subs: &Subs, subs: &Subs,
pointer_size: u32, pointer_size: u32,
) -> Result<Layout<'a>, ()> { ) -> Result<Layout<'a>, LayoutProblem> {
use roc_types::subs::FlatType::*; use roc_types::subs::FlatType::*;
match flat_type { match flat_type {
Apply(symbol, args) => { Apply(symbol, args) => {
match symbol { match symbol {
Symbol::INT_INT => { Symbol::NUM_INT => {
debug_assert!(args.is_empty()); debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Int64)) Ok(Layout::Builtin(Builtin::Int64))
} }
Symbol::FLOAT_FLOAT => { Symbol::NUM_FLOAT => {
debug_assert!(args.is_empty()); debug_assert_eq!(args.len(), 0);
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
} }
Symbol::NUM_NUM => { Symbol::NUM_NUM | Symbol::NUM_AT_NUM => {
// Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer // Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer
debug_assert!(args.len() == 1); debug_assert_eq!(args.len(), 1);
let var = args.iter().next().unwrap(); let var = args.iter().next().unwrap();
let content = subs.get_without_compacting(*var).content; let content = subs.get_without_compacting(*var).content;
@ -241,20 +263,9 @@ fn layout_from_flat_type<'a>(
layout_from_num_content(content) layout_from_num_content(content)
} }
Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)), Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)),
Symbol::LIST_LIST => { Symbol::LIST_LIST => list_layout_from_elem(arena, subs, args[0], pointer_size),
use roc_types::subs::Content::*;
match subs.get_without_compacting(args[0]).content {
FlexVar(_) | RigidVar(_) => Ok(Layout::Builtin(Builtin::EmptyList)),
content => {
let elem_layout = Layout::new(arena, content, subs, pointer_size)?;
Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout))))
}
}
}
Symbol::ATTR_ATTR => { Symbol::ATTR_ATTR => {
debug_assert!(args.len() == 2); debug_assert_eq!(args.len(), 2);
// The first argument is the uniqueness info; // The first argument is the uniqueness info;
// that doesn't affect layout, so we don't need it here. // that doesn't affect layout, so we don't need it here.
@ -290,26 +301,44 @@ fn layout_from_flat_type<'a>(
Record(fields, ext_var) => { Record(fields, ext_var) => {
debug_assert!(ext_var_is_empty_record(subs, ext_var)); debug_assert!(ext_var_is_empty_record(subs, ext_var));
let btree = fields // Sort the fields by label
.into_iter() let mut sorted_fields = Vec::with_capacity_in(fields.len(), arena);
.collect::<BTreeMap<Lowercase, Variable>>();
let mut layouts = Vec::with_capacity_in(btree.len(), arena); for tuple in fields {
sorted_fields.push(tuple);
}
sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
// Determine the layouts of the fields, maintaining sort order
let mut layouts = Vec::with_capacity_in(sorted_fields.len(), arena);
for (_, field_var) in sorted_fields {
use LayoutProblem::*;
for (_, field_var) in btree {
let field_content = subs.get_without_compacting(field_var).content; let field_content = subs.get_without_compacting(field_var).content;
let field_layout = match Layout::new(arena, field_content, subs, pointer_size) {
Ok(layout) => layout, match Layout::new(arena, field_content, subs, pointer_size) {
Err(()) => { Ok(layout) => {
// Drop any zero-sized fields like {}
if layout.stack_size(pointer_size) != 0 {
layouts.push(layout);
}
}
Err(UnresolvedTypeVar) | Err(Erroneous) => {
// Invalid field! // Invalid field!
panic!("TODO gracefully handle record with invalid field.var"); panic!("TODO gracefully handle record with invalid field.var");
} }
}; }
layouts.push(field_layout);
} }
Ok(Layout::Struct(layouts.into_bump_slice())) if layouts.len() == 1 {
// If the record has only one field that isn't zero-sized,
// unwrap it.
Ok(layouts.pop().unwrap())
} else {
Ok(Layout::Struct(layouts.into_bump_slice()))
}
} }
TagUnion(tags, ext_var) => { TagUnion(tags, ext_var) => {
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var)); debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
@ -325,36 +354,43 @@ fn layout_from_flat_type<'a>(
Boolean(_) => { Boolean(_) => {
panic!("TODO make Layout for Boolean"); panic!("TODO make Layout for Boolean");
} }
Erroneous(_) => Err(()), Erroneous(_) => Err(LayoutProblem::Erroneous),
EmptyRecord => Ok(Layout::Struct(&[])), EmptyRecord => Ok(Layout::Struct(&[])),
} }
} }
pub fn record_fields_btree<'a>( pub fn sort_record_fields<'a>(
arena: &'a Bump, arena: &'a Bump,
var: Variable, var: Variable,
subs: &Subs, subs: &Subs,
pointer_size: u32, pointer_size: u32,
) -> BTreeMap<Lowercase, Layout<'a>> { ) -> Vec<'a, (Lowercase, Layout<'a>)> {
let mut fields_map = MutMap::default(); let mut fields_map = MutMap::default();
match roc_types::pretty_print::chase_ext_record(subs, var, &mut fields_map) { match roc_types::pretty_print::chase_ext_record(subs, var, &mut fields_map) {
Ok(()) | Err((_, Content::FlexVar(_))) => { Ok(()) | Err((_, Content::FlexVar(_))) => {
// collect into btreemap to sort // Sort the fields by label
fields_map let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), arena);
.into_iter()
.map(|(label, var)| { for (label, var) in fields_map {
( let layout = Layout::from_var(arena, var, subs, pointer_size)
label, .expect("invalid layout from var");
Layout::from_var(arena, var, subs, pointer_size)
.expect("invalid layout from var"), // Drop any zero-sized fields like {}
) if layout.stack_size(pointer_size) != 0 {
}) sorted_fields.push((label, layout));
.collect::<BTreeMap<Lowercase, Layout<'a>>>() }
}
sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
sorted_fields
} }
Err(other) => panic!("invalid content in record variable: {:?}", other), Err(other) => panic!("invalid content in record variable: {:?}", other),
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum UnionVariant<'a> { pub enum UnionVariant<'a> {
Never, Never,
Unit, Unit,
@ -375,7 +411,7 @@ pub fn union_sorted_tags<'a>(
Ok(()) | Err((_, Content::FlexVar(_))) => { Ok(()) | Err((_, Content::FlexVar(_))) => {
union_sorted_tags_help(arena, tags_vec, subs, pointer_size) union_sorted_tags_help(arena, tags_vec, subs, pointer_size)
} }
Err(other) => panic!("invalid content in record variable: {:?}", other), Err(other) => panic!("invalid content in tag union variable: {:?}", other),
} }
} }
@ -385,10 +421,7 @@ fn union_sorted_tags_help<'a>(
subs: &Subs, subs: &Subs,
pointer_size: u32, pointer_size: u32,
) -> UnionVariant<'a> { ) -> UnionVariant<'a> {
// for this union be be an enum, none of the tags may have any arguments // sort up front; make sure the ordering stays intact!
let has_no_arguments = tags_vec.iter().all(|(_, args)| args.is_empty());
// sort up-front, make sure the ordering stays intact!
tags_vec.sort(); tags_vec.sort();
match tags_vec.len() { match tags_vec.len() {
@ -396,72 +429,104 @@ fn union_sorted_tags_help<'a>(
// trying to instantiate a type with no values // trying to instantiate a type with no values
UnionVariant::Never UnionVariant::Never
} }
1 if has_no_arguments => {
// a unit type
UnionVariant::Unit
}
2 if has_no_arguments => {
// type can be stored in a boolean
// tags_vec is sorted,
let ttrue = tags_vec.remove(1).0;
let ffalse = tags_vec.remove(0).0;
UnionVariant::BoolUnion { ffalse, ttrue }
}
3..=MAX_ENUM_SIZE if has_no_arguments => {
// type can be stored in a byte
// needs the sorted tag names to determine the tag_id
let mut tag_names = Vec::with_capacity_in(tags_vec.len(), arena);
for (label, _) in tags_vec {
tag_names.push(label);
}
UnionVariant::ByteUnion(tag_names)
}
1 => { 1 => {
// special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int
let (tag_name, arguments) = tags_vec.remove(0); let (tag_name, arguments) = tags_vec.remove(0);
// just one tag in the union (but with arguments) can be a struct // just one tag in the union (but with arguments) can be a struct
let mut layouts = Vec::with_capacity_in(tags_vec.len(), arena); let mut layouts = Vec::with_capacity_in(tags_vec.len(), arena);
// special-case NUM_AT_NUM: if its argument is a FlexVar, make it Int
match tag_name { match tag_name {
TagName::Private(Symbol::NUM_AT_NUM) => { TagName::Private(Symbol::NUM_AT_NUM) => {
layouts.push(unwrap_num_tag(subs, arguments[0]).expect("invalid num layout")); layouts.push(unwrap_num_tag(subs, arguments[0]).expect("invalid num layout"));
} }
_ => { _ => {
for var in arguments.iter() { for var in arguments {
let layout = Layout::from_var(arena, *var, subs, pointer_size) match Layout::from_var(arena, var, subs, pointer_size) {
.expect("invalid layout from var"); Ok(layout) => {
layouts.push(layout); // Drop any zero-sized arguments like {}
if layout.stack_size(pointer_size) != 0 {
layouts.push(layout);
}
}
Err(LayoutProblem::UnresolvedTypeVar) => {
// If we encounter an unbound type var (e.g. `Ok *`)
// then it's zero-sized; drop the argument.
}
Err(LayoutProblem::Erroneous) => {
// An erroneous type var will code gen to a runtime
// error, so we don't need to store any data for it.
}
}
} }
} }
} }
UnionVariant::Unwrapped(layouts)
}
_ => { if layouts.is_empty() {
UnionVariant::Unit
} else {
UnionVariant::Unwrapped(layouts)
}
}
num_tags => {
// default path // default path
let mut result = Vec::with_capacity_in(tags_vec.len(), arena); let mut answer = Vec::with_capacity_in(tags_vec.len(), arena);
let mut has_any_arguments = false;
for (tag_name, arguments) in tags_vec { for (tag_name, arguments) in tags_vec {
// resverse space for the tag discriminant // reserve space for the tag discriminant
let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena); let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, arena);
// add the tag discriminant // add the tag discriminant (size currently always hardcoded to i64)
arg_layouts.push(Layout::Builtin(Builtin::Int64)); arg_layouts.push(Layout::Builtin(Builtin::Int64));
for var in arguments { for var in arguments {
let layout = Layout::from_var(arena, var, subs, pointer_size) match Layout::from_var(arena, var, subs, pointer_size) {
.expect("invalid layout from var"); Ok(layout) => {
arg_layouts.push(layout); // Drop any zero-sized arguments like {}
if layout.stack_size(pointer_size) != 0 {
has_any_arguments = true;
arg_layouts.push(layout);
}
}
Err(LayoutProblem::UnresolvedTypeVar) => {
// If we encounter an unbound type var (e.g. `Ok *`)
// then it's zero-sized; drop the argument.
}
Err(LayoutProblem::Erroneous) => {
// An erroneous type var will code gen to a runtime
// error, so we don't need to store any data for it.
}
}
} }
result.push((tag_name, arg_layouts.into_bump_slice())); answer.push((tag_name, arg_layouts.into_bump_slice()));
}
match num_tags {
2 if !has_any_arguments => {
// type can be stored in a boolean
// tags_vec is sorted, and answer is sorted the same way
let ttrue = answer.remove(1).0;
let ffalse = answer.remove(0).0;
UnionVariant::BoolUnion { ffalse, ttrue }
}
3..=MAX_ENUM_SIZE if !has_any_arguments => {
// type can be stored in a byte
// needs the sorted tag names to determine the tag_id
let mut tag_names = Vec::with_capacity_in(answer.len(), arena);
for (tag_name, _) in answer {
tag_names.push(tag_name);
}
UnionVariant::ByteUnion(tag_names)
}
_ => UnionVariant::Wrapped(answer),
} }
UnionVariant::Wrapped(result)
} }
} }
} }
@ -475,35 +540,43 @@ pub fn layout_from_tag_union<'a>(
use UnionVariant::*; use UnionVariant::*;
let tags_vec: std::vec::Vec<_> = tags.into_iter().collect(); let tags_vec: std::vec::Vec<_> = tags.into_iter().collect();
let first_tag = tags_vec[0].clone();
let variant = union_sorted_tags_help(arena, tags_vec, subs, pointer_size);
match variant { if tags_vec[0].0 != TagName::Private(Symbol::NUM_AT_NUM) {
Never => panic!("TODO gracefully handle trying to instantiate Never"), let variant = union_sorted_tags_help(arena, tags_vec, subs, pointer_size);
Unit => Layout::Struct(&[]),
BoolUnion { .. } => Layout::Builtin(Builtin::Bool),
ByteUnion(_) => Layout::Builtin(Builtin::Byte),
Unwrapped(field_layouts) => match first_tag.0 {
TagName::Private(Symbol::NUM_AT_NUM) => {
let arguments = first_tag.1;
debug_assert!(arguments.len() == 1);
let var = arguments.iter().next().unwrap();
unwrap_num_tag(subs, *var).expect("invalid Num argument") match variant {
Never => panic!("TODO gracefully handle trying to instantiate Never"),
Unit => Layout::Struct(&[]),
BoolUnion { .. } => Layout::Builtin(Builtin::Int1),
ByteUnion(_) => Layout::Builtin(Builtin::Int8),
Unwrapped(mut field_layouts) => {
if field_layouts.len() == 1 {
field_layouts.pop().unwrap()
} else {
Layout::Struct(field_layouts.into_bump_slice())
}
} }
_ => Layout::Struct(field_layouts.into_bump_slice()), Wrapped(tags) => {
}, let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
Wrapped(tags) => {
let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena);
for (_, tag_layout) in tags { for (_, tag_layout) in tags {
tag_layouts.push(tag_layout); tag_layouts.push(tag_layout);
}
Layout::Union(tag_layouts.into_bump_slice())
} }
Layout::Union(tag_layouts.into_bump_slice())
} }
} else {
let arguments = &tags_vec[0].1;
debug_assert_eq!(arguments.len(), 1);
let var = arguments.iter().next().unwrap();
unwrap_num_tag(subs, *var).expect("invalid Num argument")
} }
} }
#[cfg(debug_assertions)]
fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool { fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool {
// the ext_var is empty // the ext_var is empty
let mut ext_fields = std::vec::Vec::new(); let mut ext_fields = std::vec::Vec::new();
@ -513,6 +586,13 @@ fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool {
} }
} }
#[cfg(not(debug_assertions))]
fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool {
// This should only ever be used in debug_assert! macros
unreachable!();
}
#[cfg(debug_assertions)]
fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool { fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool {
// the ext_var is empty // the ext_var is empty
let mut ext_fields = MutMap::default(); let mut ext_fields = MutMap::default();
@ -522,20 +602,27 @@ fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool {
} }
} }
fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, ()> { #[cfg(not(debug_assertions))]
fn ext_var_is_empty_record(_: &Subs, _: Variable) -> bool {
// This should only ever be used in debug_assert! macros
unreachable!();
}
fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, LayoutProblem> {
use roc_types::subs::Content::*; use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*; use roc_types::subs::FlatType::*;
match content { match content {
var @ FlexVar(_) | var @ RigidVar(_) => { FlexVar(_) | RigidVar(_) => {
panic!( // If a Num makes it all the way through type checking with an unbound
"Layout::from_num_content encountered an unresolved {:?}", // type variable, then assume it's a 64-bit integer.
var //
); // (e.g. for (5 + 5) assume both 5s are 64-bit integers.)
Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN))
} }
Structure(Apply(symbol, args)) => match symbol { Structure(Apply(symbol, args)) => match symbol {
Symbol::INT_INTEGER => Ok(Layout::Builtin(Builtin::Int64)), Symbol::NUM_INTEGER => Ok(Layout::Builtin(Builtin::Int64)),
Symbol::FLOAT_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)), Symbol::NUM_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)),
_ => { _ => {
panic!( panic!(
"Invalid Num.Num type application: {:?}", "Invalid Num.Num type application: {:?}",
@ -543,44 +630,71 @@ fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, ()> {
); );
} }
}, },
Alias(_, _, _) => {
todo!("TODO recursively resolve type aliases in num_from_content");
}
Structure(_) => { Structure(_) => {
panic!("Invalid Num.Num type application: {:?}", content); panic!("Invalid Num.Num type application: {:?}", content);
} }
Alias(_, _, _) => { Error => Err(LayoutProblem::Erroneous),
panic!("TODO recursively resolve type aliases in num_from_content");
}
Error => Err(()),
} }
} }
fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, ()> { fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, LayoutProblem> {
match subs.get_without_compacting(var).content { match subs.get_without_compacting(var).content {
Content::Structure(flat_type) => match flat_type { Content::Structure(flat_type) => match flat_type {
FlatType::Apply(Symbol::ATTR_ATTR, args) => { FlatType::Apply(Symbol::ATTR_ATTR, args) => {
debug_assert!(args.len() == 2); debug_assert_eq!(args.len(), 2);
let arg_var = args.get(1).unwrap(); let arg_var = args.get(1).unwrap();
unwrap_num_tag(subs, *arg_var) unwrap_num_tag(subs, *arg_var)
} }
_ => { _ => {
panic!("TODO handle Num.@Num flat_type {:?}", flat_type); todo!("TODO handle Num.@Num flat_type {:?}", flat_type);
} }
}, },
Content::Alias(Symbol::INT_INTEGER, args, _) => { Content::Alias(Symbol::NUM_INTEGER, args, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Int64)) Ok(Layout::Builtin(Builtin::Int64))
} }
Content::Alias(Symbol::FLOAT_FLOATINGPOINT, args, _) => { Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
} }
Content::FlexVar(_) => { Content::FlexVar(_) | Content::RigidVar(_) => {
// If this was still a (Num *) then default to compiling it to i64 // If this was still a (Num *) then default to compiling it to i64
Ok(Layout::Builtin(Builtin::Int64)) Ok(Layout::Builtin(DEFAULT_NUM_BUILTIN))
} }
other => { other => {
panic!("TODO non structure Num.@Num flat_type {:?}", other); todo!("TODO non structure Num.@Num flat_type {:?}", other);
}
}
}
pub fn list_layout_from_elem<'a>(
arena: &'a Bump,
subs: &Subs,
var: Variable,
pointer_size: u32,
) -> Result<Layout<'a>, LayoutProblem> {
match subs.get_without_compacting(var).content {
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, args)) => {
debug_assert_eq!(args.len(), 2);
let arg_var = args.get(1).unwrap();
list_layout_from_elem(arena, subs, *arg_var, pointer_size)
}
Content::FlexVar(_) | Content::RigidVar(_) => {
// If this was still a (List *) then it must have been an empty list
Ok(Layout::Builtin(Builtin::EmptyList))
}
content => {
let elem_layout = Layout::new(arena, content, subs, pointer_size)?;
// This is a normal list.
Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout))))
} }
} }
} }

View file

@ -46,25 +46,6 @@ pub fn infer_expr(
(content, solved.into_inner()) (content, solved.into_inner())
} }
/// Used in the with_larger_debug_stack() function, for tests that otherwise
/// run out of stack space in debug builds (but don't in --release builds)
#[allow(dead_code)]
const EXPANDED_STACK_SIZE: usize = 4 * 1024 * 1024;
/// In --release builds, don't increase the stack size. Run the test normally.
/// This way, we find out if any of our tests are blowing the stack even after
/// optimizations in release builds.
#[cfg(not(debug_assertions))]
#[inline(always)]
pub fn with_larger_debug_stack<F>(run_test: F)
where
F: FnOnce() -> (),
F: Send,
F: 'static,
{
run_test()
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> { pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
@ -217,6 +198,33 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
&loc_expr.value, &loc_expr.value,
); );
// Add the builtins' defs.
let mut with_builtins = loc_expr.value;
// Add builtin defs (e.g. List.get) directly to the canonical Expr,
// since we aren't using modules here.
let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store);
for (symbol, def) in builtin_defs {
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
{
with_builtins = roc_can::expr::Expr::LetNonRec(
Box::new(def),
Box::new(Located {
region: Region::zero(),
value: with_builtins,
}),
var_store.fresh(),
SendMap::default(),
);
}
}
let loc_expr = Located {
region: loc_expr.region,
value: with_builtins,
};
let constraint = constrain_expr( let constraint = constrain_expr(
&roc_constrain::expr::Env { &roc_constrain::expr::Env {
rigids: ImMap::default(), rigids: ImMap::default(),

View file

@ -1,7 +1,5 @@
#[macro_use] #[macro_use]
extern crate pretty_assertions; extern crate pretty_assertions;
// #[macro_use]
// extern crate indoc;
extern crate bumpalo; extern crate bumpalo;
extern crate roc_mono; extern crate roc_mono;
@ -17,11 +15,14 @@ mod test_mono {
use roc_mono::expr::Expr::{self, *}; use roc_mono::expr::Expr::{self, *};
use roc_mono::expr::Procs; use roc_mono::expr::Procs;
use roc_mono::layout; use roc_mono::layout;
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout, LayoutCache};
use roc_types::subs::Subs; use roc_types::subs::Subs;
// HELPERS // HELPERS
const I64_LAYOUT: Layout<'static> = Layout::Builtin(Builtin::Int64);
const F64_LAYOUT: Layout<'static> = Layout::Builtin(Builtin::Float64);
fn compiles_to(src: &str, expected: Expr<'_>) { fn compiles_to(src: &str, expected: Expr<'_>) {
compiles_to_with_interns(src, |_| expected) compiles_to_with_interns(src, |_| expected)
} }
@ -40,6 +41,7 @@ mod test_mono {
mut interns, mut interns,
.. ..
} = can_expr(src); } = can_expr(src);
let subs = Subs::new(var_store.into()); let subs = Subs::new(var_store.into());
let mut unify_problems = Vec::new(); let mut unify_problems = Vec::new();
let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
@ -64,6 +66,11 @@ mod test_mono {
}; };
let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs);
let (_, runtime_errors) =
roc_mono::expr::specialize_all(&mut mono_env, procs, &mut LayoutCache::default());
assert_eq!(runtime_errors, roc_collections::all::MutSet::default());
// Put this module's ident_ids back in the interns // Put this module's ident_ids back in the interns
interns.all_ident_ids.insert(home, ident_ids); interns.all_ident_ids.insert(home, ident_ids);
@ -85,7 +92,7 @@ mod test_mono {
compiles_to( compiles_to(
"3.0 + 4", "3.0 + 4",
CallByName { CallByName {
name: Symbol::FLOAT_ADD, name: Symbol::NUM_ADD,
layout: Layout::FunctionPointer( layout: Layout::FunctionPointer(
&[ &[
Layout::Builtin(Builtin::Float64), Layout::Builtin(Builtin::Float64),
@ -106,7 +113,7 @@ mod test_mono {
compiles_to( compiles_to(
"0xDEADBEEF + 4", "0xDEADBEEF + 4",
CallByName { CallByName {
name: Symbol::INT_ADD, name: Symbol::NUM_ADD,
layout: Layout::FunctionPointer( layout: Layout::FunctionPointer(
&[ &[
Layout::Builtin(Builtin::Int64), Layout::Builtin(Builtin::Int64),
@ -128,7 +135,7 @@ mod test_mono {
compiles_to( compiles_to(
"3 + 5", "3 + 5",
CallByName { CallByName {
name: Symbol::INT_ADD, name: Symbol::NUM_ADD,
layout: Layout::FunctionPointer( layout: Layout::FunctionPointer(
&[ &[
Layout::Builtin(Builtin::Int64), Layout::Builtin(Builtin::Int64),
@ -201,13 +208,13 @@ mod test_mono {
Store( Store(
&[( &[(
gen_symbol_0, gen_symbol_0,
Layout::Builtin(layout::Builtin::Bool), Layout::Builtin(layout::Builtin::Int1),
Expr::Bool(true), Expr::Bool(true),
)], )],
&Cond { &Cond {
cond_symbol: gen_symbol_0, cond_symbol: gen_symbol_0,
branch_symbol: gen_symbol_0, branch_symbol: gen_symbol_0,
cond_layout: Builtin(Bool), cond_layout: Builtin(Int1),
pass: (&[] as &[_], &Expr::Str("bar")), pass: (&[] as &[_], &Expr::Str("bar")),
fail: (&[] as &[_], &Expr::Str("foo")), fail: (&[] as &[_], &Expr::Str("foo")),
ret_layout: Builtin(Str), ret_layout: Builtin(Str),
@ -239,26 +246,26 @@ mod test_mono {
Store( Store(
&[( &[(
gen_symbol_0, gen_symbol_0,
Layout::Builtin(layout::Builtin::Bool), Layout::Builtin(layout::Builtin::Int1),
Expr::Bool(true), Expr::Bool(true),
)], )],
&Cond { &Cond {
cond_symbol: gen_symbol_0, cond_symbol: gen_symbol_0,
branch_symbol: gen_symbol_0, branch_symbol: gen_symbol_0,
cond_layout: Builtin(Bool), cond_layout: Builtin(Int1),
pass: (&[] as &[_], &Expr::Str("bar")), pass: (&[] as &[_], &Expr::Str("bar")),
fail: ( fail: (
&[] as &[_], &[] as &[_],
&Store( &Store(
&[( &[(
gen_symbol_1, gen_symbol_1,
Layout::Builtin(layout::Builtin::Bool), Layout::Builtin(layout::Builtin::Int1),
Expr::Bool(false), Expr::Bool(false),
)], )],
&Cond { &Cond {
cond_symbol: gen_symbol_1, cond_symbol: gen_symbol_1,
branch_symbol: gen_symbol_1, branch_symbol: gen_symbol_1,
cond_layout: Builtin(Bool), cond_layout: Builtin(Int1),
pass: (&[] as &[_], &Expr::Str("foo")), pass: (&[] as &[_], &Expr::Str("foo")),
fail: (&[] as &[_], &Expr::Str("baz")), fail: (&[] as &[_], &Expr::Str("baz")),
ret_layout: Builtin(Str), ret_layout: Builtin(Str),
@ -297,13 +304,13 @@ mod test_mono {
Store( Store(
&[( &[(
gen_symbol_0, gen_symbol_0,
Layout::Builtin(layout::Builtin::Bool), Layout::Builtin(layout::Builtin::Int1),
Expr::Bool(true), Expr::Bool(true),
)], )],
&Cond { &Cond {
cond_symbol: gen_symbol_0, cond_symbol: gen_symbol_0,
branch_symbol: gen_symbol_0, branch_symbol: gen_symbol_0,
cond_layout: Builtin(Bool), cond_layout: Builtin(Int1),
pass: (&[] as &[_], &Expr::Str("bar")), pass: (&[] as &[_], &Expr::Str("bar")),
fail: (&[] as &[_], &Expr::Str("foo")), fail: (&[] as &[_], &Expr::Str("foo")),
ret_layout: Builtin(Str), ret_layout: Builtin(Str),
@ -340,41 +347,78 @@ mod test_mono {
fn polymorphic_identity() { fn polymorphic_identity() {
compiles_to( compiles_to(
r#" r#"
id = \x -> x id = \x -> x
id { x: id 0x4 } id { x: id 0x4, y: 0.1 }
"#, "#,
{ {
use self::Builtin::*;
let home = test_home(); let home = test_home();
let gen_symbol_0 = Interns::from_index(home, 0); let gen_symbol_0 = Interns::from_index(home, 0);
let struct_layout = Layout::Struct(&[I64_LAYOUT, F64_LAYOUT]);
CallByName { CallByName {
name: gen_symbol_0, name: gen_symbol_0,
layout: Layout::FunctionPointer( layout: Layout::FunctionPointer(
&[Layout::Struct(&[Layout::Builtin(Builtin::Int64)])], &[struct_layout.clone()],
&Layout::Struct(&[Layout::Builtin(Builtin::Int64)]), &struct_layout.clone(),
), ),
args: &[( args: &[(
Struct(&[( Struct(&[
CallByName { (
name: gen_symbol_0, CallByName {
layout: Layout::FunctionPointer( name: gen_symbol_0,
&[Layout::Builtin(Builtin::Int64)], layout: Layout::FunctionPointer(&[I64_LAYOUT], &I64_LAYOUT),
&Layout::Builtin(Builtin::Int64), args: &[(Int(4), I64_LAYOUT)],
), },
args: &[(Int(4), Layout::Builtin(Int64))], I64_LAYOUT,
}, ),
Layout::Builtin(Int64), (Float(0.1), F64_LAYOUT),
)]), ]),
Layout::Struct(&[Layout::Builtin(Int64)]), struct_layout,
)], )],
} }
}, },
) )
} }
// #[test]
// fn list_get_unique() {
// compiles_to(
// r#"
// unique = [ 2, 4 ]
// List.get unique 1
// "#,
// {
// use self::Builtin::*;
// let home = test_home();
// let gen_symbol_0 = Interns::from_index(home, 0);
// let list_layout = Layout::Builtin(Builtin::List(&I64_LAYOUT));
// CallByName {
// name: gen_symbol_0,
// layout: Layout::FunctionPointer(&[list_layout.clone()], &list_layout.clone()),
// args: &[(
// Struct(&[(
// CallByName {
// name: gen_symbol_0,
// layout: Layout::FunctionPointer(
// &[Layout::Builtin(Builtin::Int64)],
// &Layout::Builtin(Builtin::Int64),
// ),
// args: &[(Int(4), Layout::Builtin(Int64))],
// },
// Layout::Builtin(Int64),
// )]),
// Layout::Struct(&[Layout::Builtin(Int64)]),
// )],
// }
// },
// )
// }
// needs LetRec to be converted to mono // needs LetRec to be converted to mono
// #[test] // #[test]
// fn polymorphic_recursive() { // fn polymorphic_recursive() {
@ -443,7 +487,7 @@ mod test_mono {
let home = test_home(); let home = test_home();
let var_x = interns.symbol(home, "x".into()); let var_x = interns.symbol(home, "x".into());
let stores = [(var_x, Layout::Builtin(Builtin::Bool), Bool(true))]; let stores = [(var_x, Layout::Builtin(Builtin::Int1), Bool(true))];
let load = Load(var_x); let load = Load(var_x);
@ -467,7 +511,7 @@ mod test_mono {
let home = test_home(); let home = test_home();
let var_x = interns.symbol(home, "x".into()); let var_x = interns.symbol(home, "x".into());
let stores = [(var_x, Layout::Builtin(Builtin::Bool), Bool(false))]; let stores = [(var_x, Layout::Builtin(Builtin::Int1), Bool(false))];
let load = Load(var_x); let load = Load(var_x);
@ -493,7 +537,7 @@ mod test_mono {
let var_x = interns.symbol(home, "x".into()); let var_x = interns.symbol(home, "x".into());
// orange gets index (and therefore tag_id) 1 // orange gets index (and therefore tag_id) 1
let stores = [(var_x, Layout::Builtin(Builtin::Byte), Byte(2))]; let stores = [(var_x, Layout::Builtin(Builtin::Int8), Byte(2))];
let load = Load(var_x); let load = Load(var_x);
@ -504,15 +548,12 @@ mod test_mono {
#[test] #[test]
fn set_unique_int_list() { fn set_unique_int_list() {
compiles_to("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", { compiles_to("List.get (List.set [ 12, 9, 7, 3 ] 1 42) 1", {
CallByName { CallByName {
name: Symbol::LIST_GET_UNSAFE, name: Symbol::LIST_GET,
layout: Layout::FunctionPointer( layout: Layout::FunctionPointer(
&[ &[Layout::Builtin(Builtin::List(&I64_LAYOUT)), I64_LAYOUT],
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), &Layout::Union(&[&[I64_LAYOUT], &[I64_LAYOUT, I64_LAYOUT]]),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::Int64),
), ),
args: &vec![ args: &vec![
( (
@ -520,31 +561,27 @@ mod test_mono {
name: Symbol::LIST_SET, name: Symbol::LIST_SET,
layout: Layout::FunctionPointer( layout: Layout::FunctionPointer(
&[ &[
Layout::Builtin(Builtin::List(&Layout::Builtin( Layout::Builtin(Builtin::List(&I64_LAYOUT)),
Builtin::Int64, I64_LAYOUT,
))), I64_LAYOUT,
Layout::Builtin(Builtin::Int64),
Layout::Builtin(Builtin::Int64),
], ],
&Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), &Layout::Builtin(Builtin::List(&I64_LAYOUT)),
), ),
args: &vec![ args: &vec![
( (
Array { Array {
elem_layout: Layout::Builtin(Builtin::Int64), elem_layout: I64_LAYOUT,
elems: &vec![Int(12), Int(9), Int(7), Int(3)], elems: &vec![Int(12), Int(9), Int(7), Int(3)],
}, },
Layout::Builtin(Builtin::List(&Layout::Builtin( Layout::Builtin(Builtin::List(&I64_LAYOUT)),
Builtin::Int64,
))),
), ),
(Int(1), Layout::Builtin(Builtin::Int64)), (Int(1), I64_LAYOUT),
(Int(42), Layout::Builtin(Builtin::Int64)), (Int(42), I64_LAYOUT),
], ],
}, },
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))), Layout::Builtin(Builtin::List(&I64_LAYOUT)),
), ),
(Int(1), Layout::Builtin(Builtin::Int64)), (Int(1), I64_LAYOUT),
], ],
} }
}); });

View file

@ -1,298 +0,0 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate roc_mono;
mod helpers;
// Test optimizations
#[cfg(test)]
mod test_opt {
use crate::helpers::{infer_expr, uniq_expr};
use bumpalo::Bump;
use roc_module::symbol::Symbol;
use roc_mono::expr::Expr::{self, *};
use roc_mono::expr::Procs;
use roc_mono::layout::{Builtin, Layout};
// HELPERS
#[derive(Debug, Default, PartialEq, Eq)]
struct CallProblems {
missing: Vec<Symbol>,
unexpected: Vec<Symbol>,
}
fn contains_named_calls(src: &str, mut calls: Vec<Symbol>) {
let arena = Bump::new();
let (loc_expr, _, _problems, subs, var, constraint, home, mut interns) = uniq_expr(src);
let mut unify_problems = Vec::new();
let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
// Compile and add all the Procs before adding main
let mut procs = Procs::default();
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
// assume 64-bit pointers
let pointer_size = std::mem::size_of::<u64>() as u32;
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
let mut mono_env = roc_mono::expr::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
pointer_size,
jump_counter: arena.alloc(0),
};
let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs);
let unexpected_calls = extract_named_calls(&mono_expr, &mut calls);
let expected = CallProblems::default();
let actual = CallProblems {
missing: calls,
unexpected: unexpected_calls,
};
assert_eq!(expected, actual);
}
fn extract_named_calls(expr: &Expr<'_>, calls: &mut Vec<Symbol>) -> Vec<Symbol> {
let mut unexpected_calls = Vec::new();
// The calls must be sorted so we can binary_search them for matches.
calls.sort();
extract_named_calls_help(expr, calls, &mut unexpected_calls);
unexpected_calls
}
fn extract_named_calls_help(
expr: &Expr<'_>,
calls: &mut Vec<Symbol>,
unexpected_calls: &mut Vec<Symbol>,
) {
match expr {
Int(_)
| Float(_)
| Str(_)
| Bool(_)
| Byte(_)
| Load(_)
| FunctionPointer(_, _)
| RuntimeError(_)
| RuntimeErrorFunction(_) => (),
Store(paths, sub_expr) => {
for (_, _, path_expr) in paths.iter() {
extract_named_calls_help(path_expr, calls, unexpected_calls);
}
extract_named_calls_help(sub_expr, calls, unexpected_calls);
}
CallByPointer(sub_expr, args, _) => {
extract_named_calls_help(sub_expr, calls, unexpected_calls);
for arg in args.iter() {
extract_named_calls_help(arg, calls, unexpected_calls);
}
}
CallByName {
name,
layout: _,
args,
} => {
// Search for the symbol. If we found it, check it off the list.
// If we didn't find it, add it to the list of unexpected calls.
match calls.binary_search(name) {
Ok(index) => {
calls.remove(index);
}
Err(_) => {
unexpected_calls.push(*name);
}
}
for (arg, _) in args.iter() {
extract_named_calls_help(arg, calls, unexpected_calls);
}
}
Cond {
cond_symbol: _,
branch_symbol: _,
cond_layout: _,
pass,
fail,
ret_layout: _,
} => {
extract_named_calls_help(pass.1, calls, unexpected_calls);
extract_named_calls_help(fail.1, calls, unexpected_calls);
}
Switch {
cond,
cond_layout: _,
branches,
default_branch,
ret_layout: _,
} => {
extract_named_calls_help(cond, calls, unexpected_calls);
extract_named_calls_help(default_branch.1, calls, unexpected_calls);
for (_, _, branch_expr) in branches.iter() {
extract_named_calls_help(branch_expr, calls, unexpected_calls);
}
}
Tag {
tag_layout: _,
tag_name: _,
tag_id: _,
union_size: _,
arguments,
} => {
for (tag_expr, _) in arguments.iter() {
extract_named_calls_help(tag_expr, calls, unexpected_calls);
}
}
Struct(fields) => {
for (field, _) in fields.iter() {
extract_named_calls_help(field, calls, unexpected_calls);
}
}
AccessAtIndex {
index: _,
field_layouts: _,
expr: sub_expr,
is_unwrapped: _,
} => {
extract_named_calls_help(sub_expr, calls, unexpected_calls);
}
Array {
elem_layout: _,
elems,
} => {
for elem in elems.iter() {
extract_named_calls_help(elem, calls, unexpected_calls);
}
}
}
}
fn compiles_to(src: &str, expected: Expr<'_>) {
let arena = Bump::new();
let (loc_expr, _, _problems, subs, var, constraint, home, mut interns) = uniq_expr(src);
let mut unify_problems = Vec::new();
let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
// Compile and add all the Procs before adding main
let mut procs = Procs::default();
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
// assume 64-bit pointers
let pointer_size = std::mem::size_of::<u64>() as u32;
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
let mut mono_env = roc_mono::expr::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
pointer_size,
jump_counter: arena.alloc(0),
};
let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs);
assert_eq!(mono_expr, expected);
}
#[test]
fn int_literal() {
compiles_to("5", Int(5));
}
#[test]
fn float_literal() {
compiles_to("0.5", Float(0.5));
}
#[test]
fn set_unique_int_list() {
// This should optimize List.set to List.set_in_place
compiles_to(
"List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1",
CallByName {
name: Symbol::LIST_GET_UNSAFE,
layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::Int64),
),
args: &vec![
(
CallByName {
name: Symbol::LIST_SET_IN_PLACE,
layout: Layout::FunctionPointer(
&[
Layout::Builtin(Builtin::List(&Layout::Builtin(
Builtin::Int64,
))),
Layout::Builtin(Builtin::Int64),
Layout::Builtin(Builtin::Int64),
],
&Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
),
args: &vec![
(
Array {
elem_layout: Layout::Builtin(Builtin::Int64),
elems: &vec![Int(12), Int(9), Int(7), Int(3)],
},
Layout::Builtin(Builtin::List(&Layout::Builtin(
Builtin::Int64,
))),
),
(Int(1), Layout::Builtin(Builtin::Int64)),
(Int(42), Layout::Builtin(Builtin::Int64)),
],
},
Layout::Builtin(Builtin::List(&Layout::Builtin(Builtin::Int64))),
),
(Int(1), Layout::Builtin(Builtin::Int64)),
],
},
);
}
#[test]
fn set_shared_int_list() {
// This should *NOT* optimize List.set to List.set_in_place
contains_named_calls(
indoc!(
r#"
shared = [ 2, 4 ]
# This should not mutate the original
x = List.set shared 1 0
{ x, y: List.getUnsafe shared 1 }
"#
),
vec![Symbol::LIST_SET, Symbol::LIST_GET_UNSAFE],
);
}
}

View file

@ -346,6 +346,7 @@ pub enum Base {
Octal, Octal,
Binary, Binary,
Hex, Hex,
Decimal,
} }
impl<'a> Pattern<'a> { impl<'a> Pattern<'a> {

View file

@ -660,11 +660,8 @@ fn parse_def_expr<'a>(
region: loc_first_pattern.region, region: loc_first_pattern.region,
}; };
// Add the first def to the end of the defs. (It's fine that we // for formatting reasons, we must insert the first def first!
// reorder the first one to the end, because canonicalize will defs.insert(0, arena.alloc(loc_first_def));
// sort all these defs based on their mutual dependencies anyway. Only
// their regions will ever be visible to the user.)
defs.push(arena.alloc(loc_first_def));
Ok((Expr::Defs(defs, arena.alloc(loc_ret)), state)) Ok((Expr::Defs(defs, arena.alloc(loc_ret)), state))
}, },

View file

@ -1414,10 +1414,7 @@ mod test_parse {
newline.into_bump_slice(), newline.into_bump_slice(),
); );
let loc_def2 = &*arena.alloc(Located::new(2, 2, 0, 5, def2)); let loc_def2 = &*arena.alloc(Located::new(2, 2, 0, 5, def2));
// NOTE: The first def always gets reordered to the end (because it let defs = bumpalo::vec![in &arena; loc_def1, loc_def2];
// gets added by .push(), since that's more efficient and since
// canonicalization is going to re-sort these all anyway.)
let defs = bumpalo::vec![in &arena; loc_def2, loc_def1];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
let loc_ret = Located::new(4, 4, 0, 2, ret); let loc_ret = Located::new(4, 4, 0, 2, ret);
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];
@ -1467,10 +1464,7 @@ mod test_parse {
newline.into_bump_slice(), newline.into_bump_slice(),
); );
let loc_def2 = &*arena.alloc(Located::new(2, 2, 0, 5, def2)); let loc_def2 = &*arena.alloc(Located::new(2, 2, 0, 5, def2));
// NOTE: The first def always gets reordered to the end (because it let defs = bumpalo::vec![in &arena; loc_def1, loc_def2 ];
// gets added by .push(), since that's more efficient and since
// canonicalization is going to re-sort these all anyway.)
let defs = bumpalo::vec![in &arena; loc_def2, loc_def1];
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
let loc_ret = Located::new(4, 4, 0, 2, ret); let loc_ret = Located::new(4, 4, 0, 2, ret);
let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")]; let reset_indentation = bumpalo::vec![in &arena; LineComment(" leading comment")];

View file

@ -3,6 +3,7 @@ use roc_collections::all::MutSet;
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::operator::BinOp; use roc_module::operator::BinOp;
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_parse::ast::Base;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -50,6 +51,10 @@ pub enum Problem {
annotation_pattern: Region, annotation_pattern: Region,
def_pattern: Region, def_pattern: Region,
}, },
InvalidAliasRigid {
alias_name: Symbol,
region: Region,
},
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -57,6 +62,36 @@ pub enum PrecedenceProblem {
BothNonAssociative(Region, Located<BinOp>, Located<BinOp>), BothNonAssociative(Region, Located<BinOp>, Located<BinOp>),
} }
/// Enum to store the various types of errors that can cause parsing an integer to fail.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IntErrorKind {
/// Value being parsed is empty.
///
/// Among other causes, this variant will be constructed when parsing an empty string.
/// In roc, this can happen with non-base-10 literals, e.g. `0x` or `0b` without any digits
Empty,
/// Contains an invalid digit.
///
/// Among other causes, this variant will be constructed when parsing a string that
/// contains a letter.
InvalidDigit,
/// Integer is too large to store in target integer type.
Overflow,
/// Integer is too small to store in target integer type.
Underflow,
}
/// Enum to store the various types of errors that can cause parsing a float to fail.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FloatErrorKind {
/// Probably an invalid digit
Error,
/// the literal is too small for f64
NegativeInfinity,
/// the literal is too large for f64
PositiveInfinity,
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum RuntimeError { pub enum RuntimeError {
Shadowing { Shadowing {
@ -65,7 +100,8 @@ pub enum RuntimeError {
}, },
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region), UnsupportedPattern(Region),
UnrecognizedFunctionName(Located<InlinableString>), // Example: when 1 is 1.X -> 32
MalformedPattern(MalformedPatternProblem, Region),
LookupNotInScope(Located<InlinableString>, MutSet<Box<str>>), LookupNotInScope(Located<InlinableString>, MutSet<Box<str>>),
ValueNotExposed { ValueNotExposed {
module_name: InlinableString, module_name: InlinableString,
@ -80,17 +116,19 @@ pub enum RuntimeError {
InvalidPrecedence(PrecedenceProblem, Region), InvalidPrecedence(PrecedenceProblem, Region),
MalformedIdentifier(Box<str>, Region), MalformedIdentifier(Box<str>, Region),
MalformedClosure(Region), MalformedClosure(Region),
FloatOutsideRange(Box<str>), InvalidFloat(FloatErrorKind, Region, Box<str>),
IntOutsideRange(Box<str>), InvalidInt(IntErrorKind, Base, Region, Box<str>),
InvalidHex(std::num::ParseIntError, Box<str>), CircularDef(Vec<Symbol>, Vec<(Region /* pattern */, Region /* expr */)>),
InvalidOctal(std::num::ParseIntError, Box<str>),
InvalidBinary(std::num::ParseIntError, Box<str>),
QualifiedPatternIdent(InlinableString),
CircularDef(
Vec<Located<Ident>>,
Vec<(Region /* pattern */, Region /* expr */)>,
),
/// When the author specifies a type annotation but no implementation /// When the author specifies a type annotation but no implementation
NoImplementation, NoImplementation,
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum MalformedPatternProblem {
MalformedInt,
MalformedFloat,
MalformedBase(Base),
Unknown,
QualifiedIdentifier,
}

View file

@ -78,6 +78,17 @@ impl Region {
Self::zero() Self::zero()
} }
} }
pub fn lines_between(&self, other: &Region) -> u32 {
if self.end_line <= other.start_line {
other.start_line - self.end_line
} else if self.start_line >= other.end_line {
self.start_line - other.end_line
} else {
// intersection
0
}
}
} }
#[test] #[test]

View file

@ -1,6 +1,6 @@
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::Region; use roc_region::all::Region;
use std::path::PathBuf; use std::path::PathBuf;
@ -247,6 +247,21 @@ pub fn can_problem<'b>(
alloc.region(Region::span_across(annotation_pattern, def_pattern)), alloc.region(Region::span_across(annotation_pattern, def_pattern)),
alloc.reflow("Is it a typo? If not, put either a newline or comment between them."), alloc.reflow("Is it a typo? If not, put either a newline or comment between them."),
]), ]),
Problem::InvalidAliasRigid { alias_name, region } => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This pattern in the definition of "),
alloc.symbol_unqualified(alias_name),
alloc.reflow(" is not what I expect:"),
]),
alloc.region(region),
alloc.concat(vec![
alloc.reflow("Only type variables like "),
alloc.type_variable("a".into()),
alloc.reflow(" or "),
alloc.type_variable("value".into()),
alloc.reflow(" can occur in this position."),
]),
]),
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error), Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
}; };
@ -283,13 +298,13 @@ fn pretty_runtime_error<'b>(
RuntimeError::LookupNotInScope(loc_name, options) => { RuntimeError::LookupNotInScope(loc_name, options) => {
not_found(alloc, loc_name.region, &loc_name.value, "value", options) not_found(alloc, loc_name.region, &loc_name.value, "value", options)
} }
RuntimeError::CircularDef(mut idents, regions) => { RuntimeError::CircularDef(mut symbols, regions) => {
let first = idents.remove(0); let first = symbols.remove(0);
if idents.is_empty() { if symbols.is_empty() {
alloc alloc
.reflow("The ") .reflow("The ")
.append(alloc.ident(first.value)) .append(alloc.symbol_unqualified(first))
.append(alloc.reflow( .append(alloc.reflow(
" value is defined directly in terms of itself, causing an infinite loop.", " value is defined directly in terms of itself, causing an infinite loop.",
)) ))
@ -299,62 +314,205 @@ fn pretty_runtime_error<'b>(
alloc.stack(vec![ alloc.stack(vec![
alloc alloc
.reflow("The ") .reflow("The ")
.append(alloc.ident(first.value.clone())) .append(alloc.symbol_unqualified(first))
.append( .append(
alloc.reflow(" definition is causing a very tricky infinite loop:"), alloc.reflow(" definition is causing a very tricky infinite loop:"),
), ),
alloc.region(regions[0].0), alloc.region(regions[0].0),
alloc alloc
.reflow("The ") .reflow("The ")
.append(alloc.ident(first.value.clone())) .append(alloc.symbol_unqualified(first))
.append(alloc.reflow( .append(alloc.reflow(
" value depends on itself through the following chain of definitions:", " value depends on itself through the following chain of definitions:",
)), )),
crate::report::cycle( crate::report::cycle(
alloc, alloc,
4, 4,
alloc.ident(first.value), alloc.symbol_unqualified(first),
idents symbols
.into_iter() .into_iter()
.map(|ident| alloc.ident(ident.value)) .map(|s| alloc.symbol_unqualified(s))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
), ),
// TODO hint? // TODO hint?
]) ])
} }
} }
other => { RuntimeError::MalformedPattern(problem, region) => {
// // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! use roc_parse::ast::Base;
// UnsupportedPattern(Region), use roc_problem::can::MalformedPatternProblem::*;
// UnrecognizedFunctionName(Located<InlinableString>),
// SymbolNotExposed { let name = match problem {
// module_name: InlinableString, MalformedInt => " integer ",
// ident: InlinableString, MalformedFloat => " float ",
// region: Region, MalformedBase(Base::Hex) => " hex integer ",
// }, MalformedBase(Base::Binary) => " binary integer ",
// ModuleNotImported { MalformedBase(Base::Octal) => " octal integer ",
// module_name: InlinableString, MalformedBase(Base::Decimal) => " integer ",
// ident: InlinableString, Unknown => " ",
// region: Region, QualifiedIdentifier => " qualified ",
// }, };
// InvalidPrecedence(PrecedenceProblem, Region),
// MalformedIdentifier(Box<str>, Region), let hint = match problem {
// MalformedClosure(Region), MalformedInt | MalformedFloat | MalformedBase(_) => alloc
// FloatOutsideRange(Box<str>), .hint()
// IntOutsideRange(Box<str>), .append(alloc.reflow("Learn more about number literals at TODO")),
// InvalidHex(std::num::ParseIntError, Box<str>), Unknown => alloc.nil(),
// InvalidOctal(std::num::ParseIntError, Box<str>), QualifiedIdentifier => alloc.hint().append(
// InvalidBinary(std::num::ParseIntError, Box<str>), alloc.reflow("In patterns, only private and global tags can be qualified"),
// QualifiedPatternIdent(InlinableString), ),
// CircularDef( };
// Vec<Located<Ident>>,
// Vec<(Region /* pattern */, Region /* expr */)>, alloc.stack(vec![
// ), alloc.concat(vec![
// alloc.reflow("This"),
// /// When the author specifies a type annotation but no implementation alloc.text(name),
// NoImplementation, alloc.reflow("pattern is malformed:"),
todo!("TODO implement run time error reporting for {:?}", other) ]),
alloc.region(region),
hint,
])
} }
RuntimeError::UnsupportedPattern(_) => {
todo!("unsupported patterns are currently not parsed!")
}
RuntimeError::ValueNotExposed { .. } => todo!("value not exposed"),
RuntimeError::ModuleNotImported { .. } => todo!("module not imported"),
RuntimeError::InvalidPrecedence(_, _) => {
// do nothing, reported with PrecedenceProblem
unreachable!()
}
RuntimeError::MalformedIdentifier(_, _) => {
todo!("malformed identifier, currently gives a parse error and thus is unreachable")
}
RuntimeError::MalformedClosure(_) => todo!(""),
RuntimeError::InvalidFloat(sign @ FloatErrorKind::PositiveInfinity, region, _raw_str)
| RuntimeError::InvalidFloat(sign @ FloatErrorKind::NegativeInfinity, region, _raw_str) => {
let hint = alloc
.hint()
.append(alloc.reflow("Learn more about number literals at TODO"));
let big_or_small = if let FloatErrorKind::PositiveInfinity = sign {
"big"
} else {
"small"
};
alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This float literal is too "),
alloc.text(big_or_small),
alloc.reflow(":"),
]),
alloc.region(region),
alloc.concat(vec![
alloc.reflow("Roc uses signed 64-bit floating points, allowing values between"),
alloc.text(format!("{:e}", f64::MIN)),
alloc.reflow(" and "),
alloc.text(format!("{:e}", f64::MAX)),
]),
hint,
])
}
RuntimeError::InvalidFloat(FloatErrorKind::Error, region, _raw_str) => {
let hint = alloc
.hint()
.append(alloc.reflow("Learn more about number literals at TODO"));
alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This float literal contains an invalid digit:"),
]),
alloc.region(region),
alloc.concat(vec![
alloc.reflow("Floating point literals can only contain the digits 0-9, or use scientific notation 10e4"),
]),
hint,
])
}
RuntimeError::InvalidInt(error @ IntErrorKind::InvalidDigit, base, region, _raw_str)
| RuntimeError::InvalidInt(error @ IntErrorKind::Empty, base, region, _raw_str) => {
use roc_parse::ast::Base::*;
let (problem, contains) = if let IntErrorKind::InvalidDigit = error {
(
"an invalid digit",
alloc.reflow(" can only contain the digits "),
)
} else {
(
"no digits",
alloc.reflow(" must contain at least one of the digits "),
)
};
let name = match base {
Decimal => "integer",
Octal => "octal integer",
Hex => "hex integer",
Binary => "binary integer",
};
let plurals = match base {
Decimal => "Integer literals",
Octal => "Octal (base-8) integer literals",
Hex => "Hexadecimal (base-16) integer literals",
Binary => "Binary (base-2) integer literals",
};
let charset = match base {
Decimal => "0-9",
Octal => "0-7",
Hex => "0-9, a-f and A-F",
Binary => "0 and 1",
};
let hint = alloc
.hint()
.append(alloc.reflow("Learn more about number literals at TODO"));
alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This "),
alloc.text(name),
alloc.reflow(" literal contains "),
alloc.text(problem),
alloc.text(":"),
]),
alloc.region(region),
alloc.concat(vec![
alloc.text(plurals),
contains,
alloc.text(charset),
alloc.text("."),
]),
hint,
])
}
RuntimeError::InvalidInt(error_kind @ IntErrorKind::Underflow, _base, region, _raw_str)
| RuntimeError::InvalidInt(error_kind @ IntErrorKind::Overflow, _base, region, _raw_str) => {
let big_or_small = if let IntErrorKind::Underflow = error_kind {
"small"
} else {
"big"
};
let hint = alloc
.hint()
.append(alloc.reflow("Learn more about number literals at TODO"));
alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This integer literal is too "),
alloc.text(big_or_small),
alloc.reflow(":"),
]),
alloc.region(region),
alloc.reflow("Roc uses signed 64-bit integers, allowing values between 9_223_372_036_854_775_808 and 9_223_372_036_854_775_807."),
hint,
])
}
RuntimeError::NoImplementation => todo!("no implementation, unreachable"),
} }
} }

View file

@ -765,6 +765,13 @@ fn to_expr_report<'b>(
None, None,
) )
} }
Reason::LowLevelOpArg { op, arg_index } => {
panic!(
"Compiler bug: argument #{} to low-level operation {:?} was the wrong type!",
arg_index.ordinal(),
op
);
}
Reason::FloatLiteral | Reason::IntLiteral | Reason::NumLiteral => { Reason::FloatLiteral | Reason::IntLiteral | Reason::NumLiteral => {
unreachable!("I don't think these can be reached") unreachable!("I don't think these can be reached")
} }
@ -892,6 +899,12 @@ fn add_category<'b>(
alloc.text(" call produces:"), alloc.text(" call produces:"),
]), ]),
CallResult(None) => alloc.concat(vec![this_is, alloc.text(":")]), CallResult(None) => alloc.concat(vec![this_is, alloc.text(":")]),
LowLevelOpResult(op) => {
panic!(
"Compiler bug: invalid return type from low-level op {:?}",
op
);
}
Uniqueness => alloc.concat(vec![ Uniqueness => alloc.concat(vec![
this_is, this_is,
@ -1478,24 +1491,24 @@ fn to_diff<'b>(
let right = to_doc(alloc, Parens::Unnecessary, type2); let right = to_doc(alloc, Parens::Unnecessary, type2);
let is_int = |t: &ErrorType| match t { let is_int = |t: &ErrorType| match t {
ErrorType::Type(Symbol::INT_INT, _) => true, ErrorType::Type(Symbol::NUM_INT, _) => true,
ErrorType::Alias(Symbol::INT_INT, _, _) => true, ErrorType::Alias(Symbol::NUM_INT, _, _) => true,
ErrorType::Type(Symbol::NUM_NUM, args) => match &args.get(0) { ErrorType::Type(Symbol::NUM_NUM, args) => match &args.get(0) {
Some(ErrorType::Type(Symbol::INT_INTEGER, _)) => true, Some(ErrorType::Type(Symbol::NUM_INTEGER, _)) => true,
Some(ErrorType::Alias(Symbol::INT_INTEGER, _, _)) => true, Some(ErrorType::Alias(Symbol::NUM_INTEGER, _, _)) => true,
_ => false, _ => false,
}, },
ErrorType::Alias(Symbol::NUM_NUM, args, _) => match &args.get(0) { ErrorType::Alias(Symbol::NUM_NUM, args, _) => match &args.get(0) {
Some((_, ErrorType::Type(Symbol::INT_INTEGER, _))) => true, Some((_, ErrorType::Type(Symbol::NUM_INTEGER, _))) => true,
Some((_, ErrorType::Alias(Symbol::INT_INTEGER, _, _))) => true, Some((_, ErrorType::Alias(Symbol::NUM_INTEGER, _, _))) => true,
_ => false, _ => false,
}, },
_ => false, _ => false,
}; };
let is_float = |t: &ErrorType| match t { let is_float = |t: &ErrorType| match t {
ErrorType::Type(Symbol::FLOAT_FLOAT, _) => true, ErrorType::Type(Symbol::NUM_FLOAT, _) => true,
ErrorType::Alias(Symbol::FLOAT_FLOAT, _, _) => true, ErrorType::Alias(Symbol::NUM_FLOAT, _, _) => true,
_ => false, _ => false,
}; };
@ -2284,7 +2297,7 @@ fn type_problem_to_pretty<'b>(
alloc.reflow(" with "), alloc.reflow(" with "),
alloc.symbol_qualified(Symbol::NUM_TO_FLOAT), alloc.symbol_qualified(Symbol::NUM_TO_FLOAT),
alloc.reflow(" and "), alloc.reflow(" and "),
alloc.symbol_qualified(Symbol::FLOAT_ROUND), alloc.symbol_qualified(Symbol::NUM_ROUND),
alloc.reflow("."), alloc.reflow("."),
])), ])),

View file

@ -132,10 +132,7 @@ pub fn can_expr_with(
let loc_expr = match parse_loc_with(&arena, expr_str) { let loc_expr = match parse_loc_with(&arena, expr_str) {
Ok(e) => e, Ok(e) => e,
Err(fail) => { Err(fail) => {
let interns = Interns { let interns = Interns::default();
module_ids: ModuleIds::default(),
all_ident_ids: MutMap::default(),
};
return Err(ParseErrOut { return Err(ParseErrOut {
fail, fail,

View file

@ -1058,7 +1058,7 @@ mod test_reporting {
Int Int
Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`. Hint: Convert between Int and Float with `Num.toFloat` and `Num.round`.
"# "#
), ),
) )
@ -1094,7 +1094,7 @@ mod test_reporting {
Int Int
Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`. Hint: Convert between Int and Float with `Num.toFloat` and `Num.round`.
"# "#
), ),
) )
@ -1129,7 +1129,7 @@ mod test_reporting {
Int -> Int Int -> Int
Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`. Hint: Convert between Int and Float with `Num.toFloat` and `Num.round`.
"# "#
), ),
) )
@ -1462,7 +1462,132 @@ mod test_reporting {
{ x : Int } { x : Int }
Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`. Hint: Convert between Int and Float with `Num.toFloat` and `Num.round`.
"#
),
)
}
#[test]
fn malformed_int_pattern() {
report_problem_as(
indoc!(
r#"
when 1 is
100A -> 3
_ -> 4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This integer pattern is malformed:
2 100A -> 3
^^^^
Hint: Learn more about number literals at TODO
"#
),
)
}
#[test]
fn malformed_float_pattern() {
report_problem_as(
indoc!(
r#"
when 1 is
2.X -> 3
_ -> 4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This float pattern is malformed:
2 2.X -> 3
^^^
Hint: Learn more about number literals at TODO
"#
),
)
}
#[test]
fn malformed_hex_pattern() {
report_problem_as(
indoc!(
r#"
when 1 is
0xZ -> 3
_ -> 4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This hex integer pattern is malformed:
2 0xZ -> 3
^^^
Hint: Learn more about number literals at TODO
"#
),
)
}
#[test]
fn malformed_oct_pattern() {
report_problem_as(
indoc!(
r#"
when 1 is
0o9 -> 3
_ -> 4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This octal integer pattern is malformed:
2 0o9 -> 3
^^^
Hint: Learn more about number literals at TODO
"#
),
)
}
#[test]
fn malformed_bin_pattern() {
report_problem_as(
indoc!(
r#"
when 1 is
0b4 -> 3
_ -> 4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This binary integer pattern is malformed:
2 0b4 -> 3
^^^
Hint: Learn more about number literals at TODO
"# "#
), ),
) )
@ -1702,6 +1827,7 @@ mod test_reporting {
#[test] #[test]
fn circular_definition_self() { fn circular_definition_self() {
// invalid recursion
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
@ -1723,6 +1849,7 @@ mod test_reporting {
#[test] #[test]
fn circular_definition() { fn circular_definition() {
// invalid mutual recursion
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
@ -1947,7 +2074,7 @@ mod test_reporting {
Num Integer Num Integer
Hint: Convert between Int and Float with `Num.toFloat` and `Float.round`. Hint: Convert between Int and Float with `Num.toFloat` and `Num.round`.
"# "#
), ),
) )
@ -2409,11 +2536,11 @@ mod test_reporting {
} }
#[test] #[test]
fn circular_alias() { fn cyclic_alias() {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
Foo : { x: Bar } Foo : { x : Bar }
Bar : { y : Foo } Bar : { y : Foo }
f : Foo f : Foo
@ -2426,18 +2553,18 @@ mod test_reporting {
r#" r#"
-- CYCLIC ALIAS ---------------------------------------------------------------- -- CYCLIC ALIAS ----------------------------------------------------------------
The `Bar` alias is recursive in an invalid way: The `Foo` alias is recursive in an invalid way:
2 Bar : { y : Foo } 1 Foo : { x : Bar }
^^^^^^^^^^^ ^^^^^^^^^^^
The `Bar` alias depends on itself through the following chain of The `Foo` alias depends on itself through the following chain of
definitions: definitions:
Bar
Foo Foo
Bar
Recursion in aliases is only allowed if recursion happens behind a Recursion in aliases is only allowed if recursion happens behind a
@ -2712,6 +2839,57 @@ mod test_reporting {
) )
} }
#[test]
fn annotation_newline_body_is_fine() {
report_problem_as(
indoc!(
r#"
bar : Int
foo = \x -> x
foo bar
"#
),
indoc!(""),
)
}
#[test]
fn invalid_alias_rigid_var_pattern() {
report_problem_as(
indoc!(
r#"
MyAlias 1 : Int
4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This pattern in the definition of `MyAlias` is not what I expect:
1 MyAlias 1 : Int
^
Only type variables like `a` or `value` can occur in this position.
-- SYNTAX PROBLEM --------------------------------------------------------------
`MyAlias` is not used anywhere in your code.
1 MyAlias 1 : Int
^^^^^^^^^^^^^^^
If you didn't intend on using `MyAlias` then remove it so future readers
of your code don't wonder why it is there.
"#
),
)
}
#[test] #[test]
fn invalid_num() { fn invalid_num() {
report_problem_as( report_problem_as(
@ -2946,4 +3124,275 @@ mod test_reporting {
), ),
) )
} }
#[test]
fn integer_out_of_range() {
report_problem_as(
indoc!(
r#"
x = 9_223_372_036_854_775_807_000
y = -9_223_372_036_854_775_807_000
h = 0x8FFF_FFFF_FFFF_FFFF
l = -0x8FFF_FFFF_FFFF_FFFF
minlit = -9_223_372_036_854_775_808
maxlit = 9_223_372_036_854_775_807
x + y + h + l + minlit + maxlit
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This integer literal is too big:
1 x = 9_223_372_036_854_775_807_000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit integers, allowing values between
9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
Hint: Learn more about number literals at TODO
-- SYNTAX PROBLEM --------------------------------------------------------------
This integer literal is too small:
3 y = -9_223_372_036_854_775_807_000
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit integers, allowing values between
9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
Hint: Learn more about number literals at TODO
-- SYNTAX PROBLEM --------------------------------------------------------------
This integer literal is too big:
5 h = 0x8FFF_FFFF_FFFF_FFFF
^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit integers, allowing values between
9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
Hint: Learn more about number literals at TODO
-- SYNTAX PROBLEM --------------------------------------------------------------
This integer literal is too small:
6 l = -0x8FFF_FFFF_FFFF_FFFF
^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit integers, allowing values between
9_223_372_036_854_775_808 and 9_223_372_036_854_775_807.
Hint: Learn more about number literals at TODO
"#
),
)
}
#[test]
fn float_out_of_range() {
report_problem_as(
&format!(
r#"
overflow = 1{:e}
underflow = -1{:e}
overflow + underflow
"#,
f64::MAX,
f64::MAX,
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This float literal is too big:
2 overflow = 11.7976931348623157e308
^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit floating points, allowing values
between-1.7976931348623157e308 and 1.7976931348623157e308
Hint: Learn more about number literals at TODO
-- SYNTAX PROBLEM --------------------------------------------------------------
This float literal is too small:
3 underflow = -11.7976931348623157e308
^^^^^^^^^^^^^^^^^^^^^^^^
Roc uses signed 64-bit floating points, allowing values
between-1.7976931348623157e308 and 1.7976931348623157e308
Hint: Learn more about number literals at TODO
"#
),
)
}
#[test]
fn integer_malformed() {
// the generated messages here are incorrect. Waiting for a rust nightly feature to land,
// see https://github.com/rust-lang/rust/issues/22639
// this test is here to spot regressions in error reporting
report_problem_as(
indoc!(
r#"
dec = 100A
hex = 0xZZZ
oct = 0o9
bin = 0b2
dec + hex + oct + bin
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This integer literal contains an invalid digit:
1 dec = 100A
^^^^
Integer literals can only contain the digits 0-9.
Hint: Learn more about number literals at TODO
-- SYNTAX PROBLEM --------------------------------------------------------------
This hex integer literal contains an invalid digit:
3 hex = 0xZZZ
^^^^^
Hexadecimal (base-16) integer literals can only contain the digits
0-9, a-f and A-F.
Hint: Learn more about number literals at TODO
-- SYNTAX PROBLEM --------------------------------------------------------------
This octal integer literal contains an invalid digit:
5 oct = 0o9
^^^
Octal (base-8) integer literals can only contain the digits 0-7.
Hint: Learn more about number literals at TODO
-- SYNTAX PROBLEM --------------------------------------------------------------
This binary integer literal contains an invalid digit:
7 bin = 0b2
^^^
Binary (base-2) integer literals can only contain the digits 0 and 1.
Hint: Learn more about number literals at TODO
"#
),
)
}
#[test]
fn integer_empty() {
report_problem_as(
indoc!(
r#"
dec = 20
hex = 0x
oct = 0o
bin = 0b
dec + hex + oct + bin
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This hex integer literal contains no digits:
3 hex = 0x
^^
Hexadecimal (base-16) integer literals must contain at least one of
the digits 0-9, a-f and A-F.
Hint: Learn more about number literals at TODO
-- SYNTAX PROBLEM --------------------------------------------------------------
This octal integer literal contains no digits:
5 oct = 0o
^^
Octal (base-8) integer literals must contain at least one of the
digits 0-7.
Hint: Learn more about number literals at TODO
-- SYNTAX PROBLEM --------------------------------------------------------------
This binary integer literal contains no digits:
7 bin = 0b
^^
Binary (base-2) integer literals must contain at least one of the
digits 0 and 1.
Hint: Learn more about number literals at TODO
"#
),
)
}
#[test]
fn float_malformed() {
report_problem_as(
indoc!(
r#"
x = 3.0A
x
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This float literal contains an invalid digit:
1 x = 3.0A
^^^^
Floating point literals can only contain the digits 0-9, or use
scientific notation 10e4
Hint: Learn more about number literals at TODO
"#
),
)
}
} }

View file

@ -8,7 +8,7 @@ extern crate bumpalo;
mod helpers; mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_solve { mod solve_expr {
use crate::helpers::{ use crate::helpers::{
assert_correct_variable_usage, can_expr, infer_expr, with_larger_debug_stack, CanExprOut, assert_correct_variable_usage, can_expr, infer_expr, with_larger_debug_stack, CanExprOut,
}; };
@ -1214,7 +1214,7 @@ mod test_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
int : Num.Num Int.Integer int : Num.Num Num.Integer
int int
"# "#
@ -1243,7 +1243,7 @@ mod test_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
int : Int.Int int : Num.Int
int = 5 int = 5
int int
@ -1273,7 +1273,7 @@ mod test_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
int : Num.Num Int.Integer int : Num.Num Num.Integer
int = 5.5 int = 5.5
int int
@ -1316,7 +1316,7 @@ mod test_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
float : Float.Float float : Num.Float
float = 5.5 float = 5.5
float float
@ -1446,7 +1446,7 @@ mod test_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
float : Num.Num Float.FloatingPoint float : Num.Num Num.FloatingPoint
float = 5.5 float = 5.5
float float
@ -1496,7 +1496,7 @@ mod test_solve {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
x : Num.Num Int.Integer x : Num.Num Num.Integer
x = x =
when 2 is when 2 is
3 -> 4 3 -> 4
@ -1708,7 +1708,7 @@ mod test_solve {
r#" r#"
Foo a : { foo : a } Foo a : { foo : a }
v : Foo (Num.Num Int.Integer) v : Foo (Num.Num Num.Integer)
v = { foo: 42 } v = { foo: 42 }
v v
@ -1772,7 +1772,7 @@ mod test_solve {
r#" r#"
Peano : [ S Peano, Z ] Peano : [ S Peano, Z ]
length : Peano -> Num.Num Int.Integer length : Peano -> Num.Num Num.Integer
length = \peano -> length = \peano ->
when peano is when peano is
Z -> 0 Z -> 0
@ -1872,10 +1872,10 @@ mod test_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
r : { x : (Num.Num Int.Integer) } r : { x : (Num.Num Num.Integer) }
r = { x : 1 } r = { x : 1 }
s : { left : { x : Num.Num Float.FloatingPoint } } s : { left : { x : Num.Num Num.FloatingPoint } }
s = { left: { x : 3.14 } } s = { left: { x : 3.14 } }
when 0 is when 0 is
@ -2010,7 +2010,7 @@ mod test_solve {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
{ x, y } : { x : Str.Str, y : Num.Num Float.FloatingPoint } { x, y } : { x : Str.Str, y : Num.Num Num.FloatingPoint }
{ x, y } = { x : "foo", y : 3.14 } { x, y } = { x : "foo", y : 3.14 }
x x
@ -2025,7 +2025,7 @@ mod test_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
Foo : { x : Str.Str, y : Num.Num Float.FloatingPoint } Foo : { x : Str.Str, y : Num.Num Num.FloatingPoint }
{ x, y } : Foo { x, y } : Foo
{ x, y } = { x : "foo", y : 3.14 } { x, y } = { x : "foo", y : 3.14 }
@ -2061,7 +2061,7 @@ mod test_solve {
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
Foo : { x : Str.Str, y : Num.Num Float.FloatingPoint } Foo : { x : Str.Str, y : Num.Num Num.FloatingPoint }
{ x, y } : Foo { x, y } : Foo
{ x, y } = { x : "foo", y : 3.14 } { x, y } = { x : "foo", y : 3.14 }
@ -2163,7 +2163,7 @@ mod test_solve {
ListB a : [ Cons a (ListC a) ] ListB a : [ Cons a (ListC a) ]
ListC a : [ Cons a (ListA a), Nil ] ListC a : [ Cons a (ListA a), Nil ]
val : ListC Int.Int val : ListC Num.Int
val = Cons 1 (Cons 2 (Cons 3 Nil)) val = Cons 1 (Cons 2 (Cons 3 Nil))
val val
@ -2524,7 +2524,7 @@ mod test_solve {
#[test] #[test]
fn rigids() { fn rigids() {
// I was sligtly surprised this works // I was slightly surprised this works
infer_eq_without_problem( infer_eq_without_problem(
indoc!( indoc!(
r#" r#"
@ -2533,10 +2533,9 @@ mod test_solve {
x : List b x : List b
x = [] x = []
v = List.getUnsafe input 0 when List.get input 0 is
Ok val -> List.push x val
List.push x v Err _ -> f input
f f
"# "#
), ),
@ -2549,7 +2548,7 @@ mod test_solve {
#[should_panic] #[should_panic]
fn rigid_record_quantification() { fn rigid_record_quantification() {
// the ext here is qualified on the outside (because we have rank 1 types, not rank 2). // the ext here is qualified on the outside (because we have rank 1 types, not rank 2).
// That means e.g. `f : { bar : String, foo : Int } -> Bool }` is a valid argument. but // That means e.g. `f : { bar : String, foo : Int } -> Bool }` is a valid argument, but
// that function could not be applied to the `{ foo : Int }` list. Therefore, this function // that function could not be applied to the `{ foo : Int }` list. Therefore, this function
// is not allowed. // is not allowed.
// //

View file

@ -8,7 +8,7 @@ extern crate bumpalo;
mod helpers; mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_uniq_solve { mod solve_uniq_expr {
use crate::helpers::{ use crate::helpers::{
assert_correct_variable_usage, infer_expr, uniq_expr, with_larger_debug_stack, assert_correct_variable_usage, infer_expr, uniq_expr, with_larger_debug_stack,
}; };
@ -1017,7 +1017,7 @@ mod test_uniq_solve {
.left .left
"# "#
), ),
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
); );
} }
@ -1029,7 +1029,7 @@ mod test_uniq_solve {
\rec -> rec.left \rec -> rec.left
"# "#
), ),
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
); );
} }
@ -1041,8 +1041,7 @@ mod test_uniq_solve {
\{ left, right } -> { left, right } \{ left, right } -> { left, right }
"# "#
), ),
// "Attr * (Attr (* | a | b) { left : (Attr a c), right : (Attr b d) }* -> Attr * { left : (Attr a c), right : (Attr b d) })" "Attr * (Attr (* | b | d) { left : (Attr b a), right : (Attr d c) }* -> Attr * { left : (Attr b a), right : (Attr d c) })"
"Attr * (Attr (* | a | b) { left : (Attr b c), right : (Attr a d) }* -> Attr * { left : (Attr b c), right : (Attr a d) })"
); );
} }
@ -1069,7 +1068,7 @@ mod test_uniq_solve {
), ),
// NOTE: Foo loses the relation to the uniqueness attribute `a` // NOTE: Foo loses the relation to the uniqueness attribute `a`
// That is fine. Whenever we try to extract from it, the relation will be enforced // That is fine. Whenever we try to extract from it, the relation will be enforced
"Attr * (Attr (* | a) [ Foo (Attr a b) ]* -> Attr * [ Foo (Attr a b) ]*)", "Attr * (Attr (* | b) [ Foo (Attr b a) ]* -> Attr * [ Foo (Attr b a) ]*)",
); );
} }
@ -1084,9 +1083,7 @@ mod test_uniq_solve {
// TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers? // TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers?
// i.e. the `b` could be ignored in this example, is that true in general? // i.e. the `b` could be ignored in this example, is that true in general?
// seems like it because we don't really extract anything. // seems like it because we don't really extract anything.
// "Attr * (Attr (* | b | c) [ Foo (Attr b a) (Attr c *) ]* -> Attr * [ Foo (Attr b a) (Attr * Str) ]*)"
// "Attr * (Attr (* | a | b) [ Foo (Attr b c) (Attr a *) ]* -> Attr * [ Foo (Attr b c) (Attr * Str) ]*)"
"Attr * (Attr (* | a | b) [ Foo (Attr a c) (Attr b *) ]* -> Attr * [ Foo (Attr a c) (Attr * Str) ]*)"
); );
} }
@ -1121,7 +1118,7 @@ mod test_uniq_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
x : Num.Num Int.Integer x : Num.Num Num.Integer
x = 4 x = 4
x x
@ -1139,7 +1136,7 @@ mod test_uniq_solve {
\{ left } -> left \{ left } -> left
"# "#
), ),
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
); );
} }
@ -1151,7 +1148,7 @@ mod test_uniq_solve {
\{ left } -> left \{ left } -> left
"# "#
), ),
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
); );
} }
@ -1166,7 +1163,7 @@ mod test_uniq_solve {
numIdentity numIdentity
"# "#
), ),
"Attr * (Attr a (Num (Attr b p)) -> Attr a (Num (Attr b p)))", "Attr * (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p)))",
); );
} }
@ -1181,7 +1178,7 @@ mod test_uniq_solve {
x x
"# "#
), ),
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
); );
} }
@ -1196,7 +1193,7 @@ mod test_uniq_solve {
x x
"# "#
), ),
"Attr * (Attr (* | a) { left : (Attr a b) }* -> Attr a b)", "Attr * (Attr (* | b) { left : (Attr b a) }* -> Attr b a)",
); );
} }
@ -1214,7 +1211,7 @@ mod test_uniq_solve {
{ numIdentity, p, q } { numIdentity, p, q }
"# "#
), ),
"Attr * { numIdentity : (Attr Shared (Attr a (Num (Attr b p)) -> Attr a (Num (Attr b p)))), p : (Attr * (Num (Attr * p))), q : (Attr * Float) }" "Attr * { numIdentity : (Attr Shared (Attr b (Num (Attr a p)) -> Attr b (Num (Attr a p)))), p : (Attr * (Num (Attr * p))), q : (Attr * Float) }"
); );
} }
@ -1230,7 +1227,7 @@ mod test_uniq_solve {
r r
"# "#
), ),
"Attr * (Attr a { x : (Attr Shared b) }c -> Attr a { x : (Attr Shared b) }c)", "Attr * (Attr c { x : (Attr Shared a) }b -> Attr c { x : (Attr Shared a) }b)",
); );
} }
@ -1246,7 +1243,7 @@ mod test_uniq_solve {
r r
"# "#
), ),
"Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared c) }d -> Attr a { x : (Attr Shared b), y : (Attr Shared c) }d)", "Attr * (Attr d { x : (Attr Shared a), y : (Attr Shared b) }c -> Attr d { x : (Attr Shared a), y : (Attr Shared b) }c)",
); );
} }
@ -1265,7 +1262,7 @@ mod test_uniq_solve {
p) p)
"# "#
), ),
"Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared c) }d -> Attr a { x : (Attr Shared b), y : (Attr Shared c) }d)" "Attr * (Attr d { x : (Attr Shared a), y : (Attr Shared b) }c -> Attr d { x : (Attr Shared a), y : (Attr Shared b) }c)"
); );
} }
@ -1281,7 +1278,7 @@ mod test_uniq_solve {
r r
"# "#
), ),
"Attr * (Attr a { x : (Attr Shared b) }c -> Attr a { x : (Attr Shared b) }c)", "Attr * (Attr c { x : (Attr Shared a) }b -> Attr c { x : (Attr Shared a) }b)",
); );
} }
@ -1293,7 +1290,7 @@ mod test_uniq_solve {
\r -> { r & x: r.x, y: r.x } \r -> { r & x: r.x, y: r.x }
"# "#
), ),
"Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared b) }c -> Attr a { x : (Attr Shared b), y : (Attr Shared b) }c)" "Attr * (Attr c { x : (Attr Shared a), y : (Attr Shared a) }b -> Attr c { x : (Attr Shared a), y : (Attr Shared a) }b)"
); );
} }
@ -1309,7 +1306,7 @@ mod test_uniq_solve {
r r
"# "#
), ),
"Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f)" "Attr * (Attr (f | d) { foo : (Attr d { bar : (Attr Shared a), baz : (Attr Shared b) }c) }e -> Attr (f | d) { foo : (Attr d { bar : (Attr Shared a), baz : (Attr Shared b) }c) }e)"
); );
} }
@ -1327,7 +1324,7 @@ mod test_uniq_solve {
r r
"# "#
), ),
"Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e)" "Attr * (Attr (e | c) { foo : (Attr c { bar : (Attr Shared a) }b) }d -> Attr (e | c) { foo : (Attr c { bar : (Attr Shared a) }b) }d)"
); );
} }
@ -1346,7 +1343,7 @@ mod test_uniq_solve {
s s
"# "#
), ),
"Attr * (Attr a { x : (Attr Shared b), y : (Attr Shared b) }c -> Attr a { x : (Attr Shared b), y : (Attr Shared b) }c)", "Attr * (Attr c { x : (Attr Shared a), y : (Attr Shared a) }b -> Attr c { x : (Attr Shared a), y : (Attr Shared a) }b)",
); );
} }
@ -1362,9 +1359,7 @@ mod test_uniq_solve {
r.tic.tac.toe r.tic.tac.toe
"# "#
), ),
// "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (d | b | e) { bar : (Attr (e | b) { baz : (Attr b f) }*) }*), tic : (Attr (a | b | c) { tac : (Attr (c | b) { toe : (Attr b f) }*) }*) }* -> Attr b f)" "Attr * (Attr (* | b | c | d | e | f) { foo : (Attr (d | b | c) { bar : (Attr (c | b) { baz : (Attr b a) }*) }*), tic : (Attr (f | b | e) { tac : (Attr (e | b) { toe : (Attr b a) }*) }*) }* -> Attr b a)"
"Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (a | b | e) { bar : (Attr (e | b) { baz : (Attr b f) }*) }*), tic : (Attr (d | b | c) { tac : (Attr (c | b) { toe : (Attr b f) }*) }*) }* -> Attr b f)"
); );
} }
@ -1373,7 +1368,7 @@ mod test_uniq_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
x : Num.Num Int.Integer x : Num.Num Num.Integer
x = x =
when 2 is when 2 is
3 -> 4 3 -> 4
@ -1697,7 +1692,7 @@ mod test_uniq_solve {
map map
"# "#
), ),
"Attr Shared (Attr Shared (Attr a b -> c), Attr (d | a) [ Cons (Attr a b) (Attr (d | a) e), Nil ]* as e -> Attr f [ Cons c (Attr f g), Nil ]* as g)" , "Attr Shared (Attr Shared (Attr b a -> c), Attr (e | b) [ Cons (Attr b a) (Attr (e | b) d), Nil ]* as d -> Attr g [ Cons c (Attr g f), Nil ]* as f)" ,
); );
} }
@ -1738,7 +1733,7 @@ mod test_uniq_solve {
map map
"# "#
), ),
"Attr Shared (Attr a [ S (Attr a b), Z ]* as b -> Attr c [ S (Attr c d), Z ]* as d)", "Attr Shared (Attr b [ S (Attr b a), Z ]* as a -> Attr d [ S (Attr d c), Z ]* as c)",
); );
} }
@ -1812,7 +1807,7 @@ mod test_uniq_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
{ x, y } : { x : Str.Str, y : Num.Num Float.FloatingPoint } { x, y } : { x : Str.Str, y : Num.Num Num.FloatingPoint }
{ x, y } = { x : "foo", y : 3.14 } { x, y } = { x : "foo", y : 3.14 }
x x
@ -1900,7 +1895,7 @@ mod test_uniq_solve {
head head
"# "#
), ),
"Attr * (Attr (* | a) (AssocList (Attr b k) (Attr c v)) -> Attr * (Maybe (Attr a { key : (Attr b k), value : (Attr c v) })))" "Attr * (Attr (* | c) (AssocList (Attr a k) (Attr b v)) -> Attr * (Maybe (Attr c { key : (Attr a k), value : (Attr b v) })))"
); );
} }
@ -1924,7 +1919,7 @@ mod test_uniq_solve {
head head
"# "#
), ),
"Attr * (Attr (* | a) (ConsList (Attr a { key : (Attr c k), value : (Attr b v) })) -> Attr * (Maybe (Attr a { key : (Attr c k), value : (Attr b v) })))" "Attr * (Attr (* | c) (ConsList (Attr c { key : (Attr a k), value : (Attr b v) })) -> Attr * (Maybe (Attr c { key : (Attr a k), value : (Attr b v) })))"
); );
} }
@ -1944,7 +1939,7 @@ mod test_uniq_solve {
map map
"# "#
), ),
"Attr * (Attr (b | c) (ConsList (Attr c a)) -> Attr (b | c) (ConsList (Attr c a)))", "Attr * (Attr (c | b) (ConsList (Attr b a)) -> Attr (c | b) (ConsList (Attr b a)))",
); );
} }
@ -2065,7 +2060,7 @@ mod test_uniq_solve {
toAs toAs
"# "#
), ),
"Attr Shared (Attr Shared (Attr a b -> c), Attr (d | a | e) [ Cons (Attr e f) (Attr (d | a | e) [ Cons (Attr a b) (Attr (d | a | e) g), Nil ]*), Nil ]* as g -> Attr h [ Cons (Attr e f) (Attr * [ Cons c (Attr h i) ]*), Nil ]* as i)" "Attr Shared (Attr Shared (Attr b a -> c), Attr (g | b | e) [ Cons (Attr e d) (Attr (g | b | e) [ Cons (Attr b a) (Attr (g | b | e) f), Nil ]*), Nil ]* as f -> Attr i [ Cons (Attr e d) (Attr * [ Cons c (Attr i h) ]*), Nil ]* as h)"
); );
} }
@ -2099,10 +2094,10 @@ mod test_uniq_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
Float.highest / Float.highest Num.maxFloat / Num.maxFloat
"# "#
), ),
"Attr * Float", "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
); );
} }
@ -2111,10 +2106,10 @@ mod test_uniq_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
3.0 / 4.0 3.0 / 4.0
"# "#
), ),
"Attr * Float", "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
); );
} }
@ -2123,10 +2118,10 @@ mod test_uniq_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
3.0 / Float.highest 3.0 / Num.maxFloat
"# "#
), ),
"Attr * Float", "Attr * (Result (Attr * Float) (Attr * [ DivByZero ]*))",
); );
} }
@ -2135,8 +2130,8 @@ mod test_uniq_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
Int.highest // Int.highest Num.maxInt // Num.maxInt
"# "#
), ),
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))", "Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
); );
@ -2147,8 +2142,8 @@ mod test_uniq_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
3 // 4 3 // 4
"# "#
), ),
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))", "Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
); );
@ -2159,8 +2154,8 @@ mod test_uniq_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
3 // Int.highest 3 // Num.maxInt
"# "#
), ),
"Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))", "Attr * (Result (Attr * Int) (Attr * [ DivByZero ]*))",
); );
@ -2171,12 +2166,12 @@ mod test_uniq_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
\list -> \list ->
p = List.get list 1 p = List.get list 1
q = List.get list 1 q = List.get list 1
{ p, q } { p, q }
"# "#
), ),
"Attr * (Attr * (List (Attr Shared a)) -> Attr * { p : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))), q : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))) })" "Attr * (Attr * (List (Attr Shared a)) -> Attr * { p : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))), q : (Attr * (Result (Attr Shared a) (Attr * [ OutOfBounds ]*))) })"
); );
@ -2196,7 +2191,7 @@ mod test_uniq_solve {
list list
"# "#
), ),
"Attr * (Attr a (List (Attr Shared (Num (Attr Shared b)))) -> Attr a (List (Attr Shared (Num (Attr Shared b)))))", "Attr * (Attr b (List (Attr Shared (Num (Attr Shared a)))) -> Attr b (List (Attr Shared (Num (Attr Shared a)))))",
); );
} }
@ -2212,7 +2207,7 @@ mod test_uniq_solve {
List.set list 0 42 List.set list 0 42
"# "#
), ),
"Attr * (Attr (a | b) (List (Attr b (Num (Attr c d)))) -> Attr (a | b) (List (Attr b (Num (Attr c d)))))", "Attr * (Attr (d | c) (List (Attr c (Num (Attr b a)))) -> Attr (d | c) (List (Attr c (Num (Attr b a)))))",
); );
} }
@ -2234,7 +2229,7 @@ mod test_uniq_solve {
sum sum
"# "#
), ),
"Attr * (Attr (* | a) (List (Attr a (Num (Attr a b)))) -> Attr c (Num (Attr c b)))", "Attr * (Attr (* | b) (List (Attr b (Num (Attr b a)))) -> Attr c (Num (Attr c a)))",
); );
} }
@ -2242,7 +2237,7 @@ mod test_uniq_solve {
fn num_add() { fn num_add() {
infer_eq( infer_eq(
"Num.add", "Num.add",
"Attr * (Attr a (Num (Attr a b)), Attr c (Num (Attr c b)) -> Attr d (Num (Attr d b)))", "Attr * (Attr b (Num (Attr b a)), Attr c (Num (Attr c a)) -> Attr d (Num (Attr d a)))",
); );
} }
@ -2258,12 +2253,12 @@ mod test_uniq_solve {
#[test] #[test]
fn list_get() { fn list_get() {
infer_eq("List.get", "Attr * (Attr (* | a) (List (Attr a b)), Attr * Int -> Attr * (Result (Attr a b) (Attr * [ OutOfBounds ]*)))"); infer_eq("List.get", "Attr * (Attr (* | b) (List (Attr b a)), Attr * Int -> Attr * (Result (Attr b a) (Attr * [ OutOfBounds ]*)))");
} }
#[test] #[test]
fn list_set() { fn list_set() {
infer_eq("List.set", "Attr * (Attr (* | a | b) (List (Attr a c)), Attr * Int, Attr (a | b) c -> Attr * (List (Attr a c)))"); infer_eq("List.set", "Attr * (Attr (* | b | c) (List (Attr b a)), Attr * Int, Attr (b | c) a -> Attr * (List (Attr b a)))");
} }
#[test] #[test]
@ -2299,7 +2294,7 @@ mod test_uniq_solve {
fn list_foldr() { fn list_foldr() {
infer_eq( infer_eq(
"List.foldr", "List.foldr",
"Attr * (Attr (* | a) (List (Attr a b)), Attr Shared (Attr a b, c -> c), c -> c)", "Attr * (Attr (* | b) (List (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)",
); );
} }
@ -2327,7 +2322,7 @@ mod test_uniq_solve {
reverse reverse
"# "#
), ),
"Attr * (Attr (* | a) (List (Attr a b)) -> Attr * (List (Attr a b)))", "Attr * (Attr (* | b) (List (Attr b a)) -> Attr * (List (Attr b a)))",
); );
} }
@ -2336,7 +2331,9 @@ mod test_uniq_solve {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1 when List.get (List.set [ 12, 9, 7, 3 ] 1 42) 1 is
Ok num -> num
Err OutOfBounds -> 0
"# "#
), ),
"Attr * (Num (Attr * *))", "Attr * (Num (Attr * *))",
@ -2373,7 +2370,7 @@ mod test_uniq_solve {
fn set_foldl() { fn set_foldl() {
infer_eq( infer_eq(
"Set.foldl", "Set.foldl",
"Attr * (Attr (* | a) (Set (Attr a b)), Attr Shared (Attr a b, c -> c), c -> c)", "Attr * (Attr (* | b) (Set (Attr b a)), Attr Shared (Attr b a, c -> c), c -> c)",
); );
} }
@ -2386,7 +2383,7 @@ mod test_uniq_solve {
fn set_remove() { fn set_remove() {
infer_eq( infer_eq(
"Set.remove", "Set.remove",
"Attr * (Attr * (Set (Attr a b)), Attr * b -> Attr * (Set (Attr a b)))", "Attr * (Attr * (Set (Attr b a)), Attr * a -> Attr * (Set (Attr b a)))",
); );
} }
@ -2402,7 +2399,7 @@ mod test_uniq_solve {
#[test] #[test]
fn map_get() { fn map_get() {
infer_eq("Map.get", "Attr * (Attr (* | a) (Map (Attr * b) (Attr a c)), Attr * b -> Attr * (Result (Attr a c) (Attr * [ KeyNotFound ]*)))"); infer_eq("Map.get", "Attr * (Attr (* | c) (Map (Attr * a) (Attr c b)), Attr * a -> Attr * (Result (Attr c b) (Attr * [ KeyNotFound ]*)))");
} }
#[test] #[test]
@ -2562,8 +2559,7 @@ mod test_uniq_solve {
map map
"# "#
), ),
// "Attr * (Attr (* | c | d) (Result (Attr c a) (Attr d e)), Attr * (Attr c a -> Attr f b) -> Attr * (Result (Attr f b) (Attr d e)))" "Attr * (Attr (* | c | d) (Result (Attr c a) (Attr d e)), Attr * (Attr c a -> Attr f b) -> Attr * (Result (Attr f b) (Attr d e)))"
"Attr * (Attr (* | c | d) (Result (Attr d a) (Attr c e)), Attr * (Attr d a -> Attr f b) -> Attr * (Result (Attr f b) (Attr c e)))"
); );
} }
@ -2581,8 +2577,7 @@ mod test_uniq_solve {
withDefault withDefault
"# "#
), ),
// "Attr * (Attr (* | b | c) (Result (Attr b a) (Attr c e)), Attr b a -> Attr b a)", "Attr * (Attr (* | b | c) (Result (Attr b a) (Attr c e)), Attr b a -> Attr b a)",
"Attr * (Attr (* | b | c) (Result (Attr c a) (Attr b e)), Attr c a -> Attr c a)",
); );
} }
@ -2597,7 +2592,7 @@ mod test_uniq_solve {
Err _ -> default Err _ -> default
"# "#
), ),
"Attr * (Attr (* | a | b) [ Err (Attr a *), Ok (Attr b c) ]*, Attr b c -> Attr b c)", "Attr * (Attr (* | a | c) [ Err (Attr a *), Ok (Attr c b) ]*, Attr c b -> Attr c b)",
); );
} }
@ -2685,7 +2680,7 @@ mod test_uniq_solve {
f f
"# "#
), ),
"Attr * (Attr a (Num (Attr a b)), Attr c (Num (Attr c b)) -> Attr d (Num (Attr d b)))", "Attr * (Attr b (Num (Attr b a)), Attr c (Num (Attr c a)) -> Attr d (Num (Attr d a)))",
); );
} }
@ -2712,7 +2707,7 @@ mod test_uniq_solve {
\x -> Num.abs x \x -> Num.abs x
"# "#
), ),
"Attr * (Attr a (Num (Attr a b)) -> Attr c (Num (Attr c b)))", "Attr * (Attr b (Num (Attr b a)) -> Attr c (Num (Attr c a)))",
); );
} }

View file

@ -140,6 +140,11 @@ fn find_names_needed(
// We must not accidentally generate names that collide with them! // We must not accidentally generate names that collide with them!
names_taken.insert(name); names_taken.insert(name);
} }
Structure(Apply(Symbol::ATTR_ATTR, args)) => {
// assign uniqueness var names based on when they occur in the base type
find_names_needed(args[1], subs, roots, root_appearances, names_taken);
find_names_needed(args[0], subs, roots, root_appearances, names_taken);
}
Structure(Apply(_, args)) => { Structure(Apply(_, args)) => {
for var in args { for var in args {
find_names_needed(var, subs, roots, root_appearances, names_taken); find_names_needed(var, subs, roots, root_appearances, names_taken);
@ -153,21 +158,30 @@ fn find_names_needed(
find_names_needed(ret_var, subs, roots, root_appearances, names_taken); find_names_needed(ret_var, subs, roots, root_appearances, names_taken);
} }
Structure(Record(fields, ext_var)) => { Structure(Record(fields, ext_var)) => {
for (_, var) in fields { let mut sorted_fields: Vec<_> = fields.iter().collect();
find_names_needed(var, subs, roots, root_appearances, names_taken); sorted_fields.sort();
for (_, var) in sorted_fields {
find_names_needed(*var, subs, roots, root_appearances, names_taken);
} }
find_names_needed(ext_var, subs, roots, root_appearances, names_taken); find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
} }
Structure(TagUnion(tags, ext_var)) => { Structure(TagUnion(tags, ext_var)) => {
for var in tags.values().flatten() { let mut sorted_tags: Vec<_> = tags.iter().collect();
sorted_tags.sort();
for var in sorted_tags.into_iter().map(|(_, v)| v).flatten() {
find_names_needed(*var, subs, roots, root_appearances, names_taken); find_names_needed(*var, subs, roots, root_appearances, names_taken);
} }
find_names_needed(ext_var, subs, roots, root_appearances, names_taken); find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
} }
Structure(RecursiveTagUnion(rec_var, tags, ext_var)) => { Structure(RecursiveTagUnion(rec_var, tags, ext_var)) => {
for var in tags.values().flatten() { let mut sorted_tags: Vec<_> = tags.iter().collect();
sorted_tags.sort();
for var in sorted_tags.into_iter().map(|(_, v)| v).flatten() {
find_names_needed(*var, subs, roots, root_appearances, names_taken); find_names_needed(*var, subs, roots, root_appearances, names_taken);
} }
@ -178,6 +192,7 @@ fn find_names_needed(
Bool::Shared => {} Bool::Shared => {}
Bool::Container(cvar, mvars) => { Bool::Container(cvar, mvars) => {
find_names_needed(cvar, subs, roots, root_appearances, names_taken); find_names_needed(cvar, subs, roots, root_appearances, names_taken);
for var in mvars { for var in mvars {
find_names_needed(var, subs, roots, root_appearances, names_taken); find_names_needed(var, subs, roots, root_appearances, names_taken);
} }
@ -276,7 +291,8 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
match symbol { match symbol {
Symbol::NUM_NUM => { Symbol::NUM_NUM => {
debug_assert!(args.len() == 1); debug_assert_eq!(args.len(), 1);
let (_, arg_var) = args let (_, arg_var) = args
.get(0) .get(0)
.expect("Num was not applied to a type argument!"); .expect("Num was not applied to a type argument!");
@ -284,8 +300,8 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
match &content { match &content {
Alias(nested, _, _) => match *nested { Alias(nested, _, _) => match *nested {
Symbol::INT_INTEGER => buf.push_str("Int"), Symbol::NUM_INTEGER => buf.push_str("Int"),
Symbol::FLOAT_FLOATINGPOINT => buf.push_str("Float"), Symbol::NUM_FLOATINGPOINT => buf.push_str("Float"),
_ => write_parens!(write_parens, buf, { _ => write_parens!(write_parens, buf, {
buf.push_str("Num "); buf.push_str("Num ");
@ -297,8 +313,8 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
let attr_content = subs.get_without_compacting(nested_args[1]).content; let attr_content = subs.get_without_compacting(nested_args[1]).content;
match &attr_content { match &attr_content {
Alias(nested, _, _) => match *nested { Alias(nested, _, _) => match *nested {
Symbol::INT_INTEGER => buf.push_str("Int"), Symbol::NUM_INTEGER => buf.push_str("Int"),
Symbol::FLOAT_FLOATINGPOINT => buf.push_str("Float"), Symbol::NUM_FLOATINGPOINT => buf.push_str("Float"),
_ => 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, content, subs, buf, parens);
@ -554,7 +570,8 @@ pub fn chase_ext_tag_union(
chase_ext_tag_union(subs, ext_var, fields) chase_ext_tag_union(subs, ext_var, fields)
} }
Content::Structure(Apply(Symbol::ATTR_ATTR, arguments)) => { Content::Structure(Apply(Symbol::ATTR_ATTR, arguments)) => {
debug_assert!(arguments.len() == 2); debug_assert_eq!(arguments.len(), 2);
chase_ext_tag_union(subs, arguments[1], fields) chase_ext_tag_union(subs, arguments[1], fields)
} }
Content::Alias(_, _, var) => chase_ext_tag_union(subs, var, fields), Content::Alias(_, _, var) => chase_ext_tag_union(subs, var, fields),
@ -581,7 +598,8 @@ pub fn chase_ext_record(
Structure(EmptyRecord) => Ok(()), Structure(EmptyRecord) => Ok(()),
Content::Structure(Apply(Symbol::ATTR_ATTR, arguments)) => { Content::Structure(Apply(Symbol::ATTR_ATTR, arguments)) => {
debug_assert!(arguments.len() == 2); debug_assert_eq!(arguments.len(), 2);
chase_ext_record(subs, arguments[1], fields) chase_ext_record(subs, arguments[1], fields)
} }
@ -689,10 +707,10 @@ fn write_apply(
match &arg_content { match &arg_content {
Content::Structure(FlatType::Apply(symbol, nested_args)) => match *symbol { Content::Structure(FlatType::Apply(symbol, nested_args)) => match *symbol {
Symbol::INT_INTEGER if nested_args.is_empty() => { Symbol::NUM_INTEGER if nested_args.is_empty() => {
buf.push_str("Int"); buf.push_str("Int");
} }
Symbol::FLOAT_FLOATINGPOINT if nested_args.is_empty() => { Symbol::NUM_FLOATINGPOINT if nested_args.is_empty() => {
buf.push_str("Float"); buf.push_str("Float");
} }
Symbol::ATTR_ATTR => match nested_args Symbol::ATTR_ATTR => match nested_args
@ -703,10 +721,10 @@ fn write_apply(
double_nested_symbol, double_nested_symbol,
double_nested_args, double_nested_args,
))) => match double_nested_symbol { ))) => match double_nested_symbol {
Symbol::INT_INTEGER if double_nested_args.is_empty() => { Symbol::NUM_INTEGER if double_nested_args.is_empty() => {
buf.push_str("Int"); buf.push_str("Int");
} }
Symbol::FLOAT_FLOATINGPOINT if double_nested_args.is_empty() => { Symbol::NUM_FLOATINGPOINT if double_nested_args.is_empty() => {
buf.push_str("Float"); buf.push_str("Float");
} }
_ => default_case(subs, arg_content), _ => default_case(subs, arg_content),

View file

@ -545,6 +545,22 @@ impl Content {
_ => false, _ => false,
} }
} }
#[cfg(debug_assertions)]
#[allow(dead_code)]
pub fn dbg(self, subs: &Subs) -> Self {
let home = roc_module::symbol::ModuleIds::default().get_or_insert(&"#Dbg".into());
let mut interns = roc_module::symbol::Interns::default();
interns.all_ident_ids = roc_module::symbol::IdentIds::exposed_builtins(0);
eprintln!(
"{}",
crate::pretty_print::content_to_string(self.clone(), subs, home, &interns)
);
self
}
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]

View file

@ -4,6 +4,7 @@ use crate::subs::{Subs, VarStore, Variable};
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_collections::all::{union, ImMap, ImSet, Index, MutMap, MutSet, SendMap}; use roc_collections::all::{union, ImMap, ImSet, Index, MutMap, MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use std::fmt; use std::fmt;
@ -747,6 +748,10 @@ pub enum Reason {
name: Option<Symbol>, name: Option<Symbol>,
arity: u8, arity: u8,
}, },
LowLevelOpArg {
op: LowLevel,
arg_index: Index,
},
FloatLiteral, FloatLiteral,
IntLiteral, IntLiteral,
NumLiteral, NumLiteral,
@ -771,6 +776,7 @@ pub enum Reason {
pub enum Category { pub enum Category {
Lookup(Symbol), Lookup(Symbol),
CallResult(Option<Symbol>), CallResult(Option<Symbol>),
LowLevelOpResult(LowLevel),
TagApply(TagName), TagApply(TagName),
Lambda, Lambda,
Uniqueness, Uniqueness,
@ -919,10 +925,10 @@ fn write_error_type_help(
let argument = arguments.remove(0).1; let argument = arguments.remove(0).1;
match argument { match argument {
Type(Symbol::INT_INTEGER, _) => { Type(Symbol::NUM_INTEGER, _) => {
buf.push_str("Int"); buf.push_str("Int");
} }
Type(Symbol::FLOAT_FLOATINGPOINT, _) => { Type(Symbol::NUM_FLOATINGPOINT, _) => {
buf.push_str("Float"); buf.push_str("Float");
} }
other => { other => {
@ -1020,10 +1026,10 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
let argument = arguments.remove(0).1; let argument = arguments.remove(0).1;
match argument { match argument {
Type(Symbol::INT_INTEGER, _) => { Type(Symbol::NUM_INTEGER, _) => {
buf.push_str("Int"); buf.push_str("Int");
} }
Type(Symbol::FLOAT_FLOATINGPOINT, _) => { Type(Symbol::NUM_FLOATINGPOINT, _) => {
buf.push_str("Float"); buf.push_str("Float");
} }
other => { other => {

View file

@ -14,7 +14,7 @@ use roc_types::types::Type::{self, *};
pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint { pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
let num_type = Variable(num_var); let num_type = Variable(num_var);
let reason = Reason::IntLiteral; let reason = Reason::IntLiteral;
let int_type = builtin_type(Symbol::INT_INT, vec![]); let int_type = builtin_type(Symbol::NUM_INT, vec![]);
let expected_literal = ForReason(reason, int_type, region); let expected_literal = ForReason(reason, int_type, region);
exists( exists(
@ -30,7 +30,7 @@ pub fn int_literal(num_var: Variable, expected: Expected<Type>, region: Region)
pub fn float_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint { pub fn float_literal(num_var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
let num_type = Variable(num_var); let num_type = Variable(num_var);
let reason = Reason::FloatLiteral; let reason = Reason::FloatLiteral;
let float_type = builtin_type(Symbol::FLOAT_FLOAT, vec![]); let float_type = builtin_type(Symbol::NUM_FLOAT, vec![]);
let expected_literal = ForReason(reason, float_type, region); let expected_literal = ForReason(reason, float_type, region);
exists( exists(

View file

@ -527,7 +527,8 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) {
| Str(_) | Str(_)
| BlockStr(_) | BlockStr(_)
| EmptyRecord | EmptyRecord
| Accessor { .. } => {} | Accessor { .. }
| RunLowLevel { .. } => {}
Var(symbol) => usage.register_unique(*symbol), Var(symbol) => usage.register_unique(*symbol),
@ -720,7 +721,7 @@ fn special_case_builtins(
} }
Symbol::LIST_SET => { Symbol::LIST_SET => {
debug_assert!(loc_args.len() == 3); debug_assert_eq!(loc_args.len(), 3);
let loc_list = &loc_args[0].1; let loc_list = &loc_args[0].1;
let loc_index = &loc_args[1].1; let loc_index = &loc_args[1].1;

View file

@ -18,19 +18,14 @@ mod test_usage_analysis {
use roc_collections::all::{ImMap, ImSet}; use roc_collections::all::{ImMap, ImSet};
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_uniq::sharing; use roc_uniq::sharing::{self, Container, FieldAccess, Mark, Usage, VarUsage};
use roc_uniq::sharing::FieldAccess; use std::collections::HashMap;
use roc_uniq::sharing::VarUsage;
use roc_uniq::sharing::{Container, Mark, Usage};
use Container::*; use Container::*;
use Mark::*; use Mark::*;
use Usage::*; use Usage::*;
fn field_access_seq( fn field_access_seq(accesses: Vec<Vec<&str>>, expected_ref: HashMap<&str, Usage>) {
accesses: Vec<Vec<&str>>,
expected_ref: std::collections::HashMap<&str, Usage>,
) {
use Mark::*; use Mark::*;
use Usage::*; use Usage::*;
@ -43,13 +38,12 @@ mod test_usage_analysis {
match usage { match usage {
Usage::Access(_, _, fields) => { Usage::Access(_, _, fields) => {
let mut actual: std::collections::HashMap<Lowercase, Usage> = let mut actual: HashMap<Lowercase, Usage> = HashMap::default();
std::collections::HashMap::default();
for (k, v) in fields.into_iter() { for (k, v) in fields.into_iter() {
actual.insert(k, v); actual.insert(k, v);
} }
let mut expected = std::collections::HashMap::default(); let mut expected = HashMap::default();
for (k, v) in expected_ref { for (k, v) in expected_ref {
expected.insert(k.into(), v); expected.insert(k.into(), v);
} }
@ -60,10 +54,7 @@ mod test_usage_analysis {
} }
} }
fn field_access_par( fn field_access_par(accesses: Vec<Vec<&str>>, expected_ref: HashMap<&str, Usage>) {
accesses: Vec<Vec<&str>>,
expected_ref: std::collections::HashMap<&str, Usage>,
) {
use Mark::*; use Mark::*;
use Usage::*; use Usage::*;
@ -76,13 +67,12 @@ mod test_usage_analysis {
match usage { match usage {
Usage::Access(_, _, fields) => { Usage::Access(_, _, fields) => {
let mut actual: std::collections::HashMap<Lowercase, Usage> = let mut actual: HashMap<Lowercase, Usage> = HashMap::default();
std::collections::HashMap::default();
for (k, v) in fields.into_iter() { for (k, v) in fields.into_iter() {
actual.insert(k, v); actual.insert(k, v);
} }
let mut expected = std::collections::HashMap::default(); let mut expected = HashMap::default();
for (k, v) in expected_ref { for (k, v) in expected_ref {
expected.insert(k.into(), v); expected.insert(k.into(), v);
} }
@ -93,7 +83,7 @@ mod test_usage_analysis {
} }
} }
fn field_access(fields: std::collections::HashMap<&str, Usage>) -> FieldAccess { fn field_access(fields: HashMap<&str, Usage>) -> FieldAccess {
let mut new_fields = ImMap::default(); let mut new_fields = ImMap::default();
for (k, v) in fields { for (k, v) in fields {

View file

@ -362,7 +362,7 @@ Now let's say I do a pattern match with no type annotations.
```elm ```elm
when foo is when foo is
MyInt num -> num + 1 MyInt num -> num + 1
MyFloat float -> Float.round float MyFloat float -> Num.round float
``` ```
The inferred type of this expression would be `[ MyInt Int, MyFloat Float ]`, The inferred type of this expression would be `[ MyInt Int, MyFloat Float ]`,
@ -386,7 +386,7 @@ toInt : [ Foo, Bar Float ] -> Int
toInt = \tag -> toInt = \tag ->
when tag is when tag is
Foo -> 1 Foo -> 1
Bar float -> Float.round float Bar float -> Num.round float
``` ```
Each of these type annotations involves a *tag union* - a collection of tags bracketed by `[` and `]`. Each of these type annotations involves a *tag union* - a collection of tags bracketed by `[` and `]`.
@ -837,7 +837,7 @@ If you put these into a hypothetical Roc REPL, here's what you'd see:
In Elm, operators are functions. In Roc, all operators are syntax sugar. In Elm, operators are functions. In Roc, all operators are syntax sugar.
This means, for example, that you cannot write `(/)` in Roc; that would be a syntax This means, for example, that you cannot write `(/)` in Roc; that would be a syntax
error. However, the `/` operator in Roc is infix syntax sugar for `Float.div`, error. However, the `/` operator in Roc is infix syntax sugar for `Num.div`,
which is a normal function you can pass to anything you like. which is a normal function you can pass to anything you like.
Elm has one unary operator, namely `-`. (In Elm, `-x` means Elm has one unary operator, namely `-`. (In Elm, `-x` means
@ -915,11 +915,11 @@ a b c
In Roc, the `|>` operator inserts the previous expression as the *first* argument In Roc, the `|>` operator inserts the previous expression as the *first* argument
to the subsequent expression, rather than as the *last* argument as it does in Elm. to the subsequent expression, rather than as the *last* argument as it does in Elm.
This makes a number of operations more useful in pipelines. For example, in Roc, `|> Float.div 2.0` divides by 2: This makes a number of operations more useful in pipelines. For example, in Roc, `|> Num.div 2.0` divides by 2:
```elixir ```elixir
2000 2000
|> Float.div 2.0 |> Num.div 2.0
# 1000.0 : Float # 1000.0 : Float
``` ```
@ -1032,11 +1032,11 @@ Here are various Roc expressions involving operators, and what they desugar to.
| `a + b` | `Num.add a b` | | `a + b` | `Num.add a b` |
| `a - b` | `Num.sub a b` | | `a - b` | `Num.sub a b` |
| `a * b` | `Num.mul a b` | | `a * b` | `Num.mul a b` |
| `a / b` | `Float.div a b` | | `a / b` | `Num.div a b` |
| `a // b` | `Int.div a b` | | `a // b` | `Num.divFloor a b` |
| `a ^ b` | `Num.pow a b` | | `a ^ b` | `Num.pow a b` |
| `a % b` | `Float.rem a b` | | `a % b` | `Num.rem a b` |
| `a %% b` | `Float.mod a b` | | `a %% b` | `Num.mod a b` |
| `-a` | `Num.neg a` | | `-a` | `Num.neg a` |
| `-f x y` | `Num.neg (f x y)` | | `-f x y` | `Num.neg (f x y)` |
| `a == b` | `Bool.isEq a b` | | `a == b` | `Bool.isEq a b` |