diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 835d68a2fb..7c9f99c16d 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -8,8 +8,8 @@ To build the compiler, you need these installed: * `libunwind` (macOS should already have this one installed) * `libc++-dev` * Python 2.7 (Windows only), `python-is-python3` (Ubuntu) -* [Zig](https://ziglang.org/) 0.7.1 or greater -* a particular version of LLVM (see below) +* [Zig](https://ziglang.org/), see below for version +* LLVM, see below for version To run the test suite (via `cargo test`), you additionally need to install: @@ -24,21 +24,21 @@ MacOS systems should already have `libunwind`, but other systems will need to in Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.) ### Zig +**version: 0.7.x** If you're on MacOS, you can install with `brew install zig` If you're on Ubuntu and use Snap, you can install with `snap install zig --classic --beta` For any other OS, checkout the [Zig installation page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager) ### LLVM - -To see which version of LLVM you need, take a look at `Cargo.toml`, in particular the `branch` section of the `inkwell` dependency. It should have something like `llvmX-Y` where X and Y are the major and minor revisions of LLVM you need. +**version: 10.0.x** For Ubuntu and Debian, you can use the `Automatic installation script` at [apt.llvm.org](https://apt.llvm.org): ``` sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ``` -For macOS, you can run `brew install llvm` (but before you do so, check the version with `brew info llvm`--if it's 10.0.1, you may need to install a slightly older version. See below for details.) +For macOS, check the troubleshooting section below. There are also plenty of alternative options at http://releases.llvm.org/download.html @@ -118,15 +118,30 @@ If you encounter `cannot find -lz` run `sudo apt install zlib1g-dev`. ### LLVM installation on macOS -It looks like LLVM 10.0.1 [has some issues with libxml2 on macOS](https://discourse.brew.sh/t/llvm-config-10-0-1-advertise-libxml2-tbd-as-system-libs/8593). You can install the older 10.0.0_3 by doing +By default homebrew will try to install llvm 11, which is currently +unsupported. You need to install an older version (10.0.0_3) by doing: ``` -$ brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/6616d50fb0b24dbe30f5e975210bdad63257f517/Formula/llvm.rb +$ brew edit llvm + +# Replace the contents of the file with https://raw.githubusercontent.com/Homebrew/homebrew-core/6616d50fb0b24dbe30f5e975210bdad63257f517/Formula/llvm.rb + +# we expect llvm-as-10 to be present +$ ln -s /usr/local/opt/llvm/bin/{llvm-as,llvm-as-10} + # "pinning" ensures that homebrew doesn't update it automatically $ brew pin llvm ``` -If that doesn't work and you get a `brew` error `Error: Calling Installation of llvm from a GitHub commit URL is disabled! Use 'brew extract llvm' to stable tap on GitHub instead.` while trying the above solution, you can follow the steps extracting the formula into your private tap (one public version is at `sladwig/tap/llvm`). If installing LLVM still fails, it might help to run `sudo xcode-select -r` before installing again. +It might also be useful to add these exports to your shell: + +``` +export PATH="/usr/local/opt/llvm/bin:$PATH" +export LDFLAGS="-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib" +export CPPFLAGS="-I/usr/local/opt/llvm/include" +``` + +If installing LLVM still fails, it might help to run `sudo xcode-select -r` before installing again. ### LLVM installation on Windows diff --git a/Cargo.lock b/Cargo.lock index 2d40ce558c..17ace44f5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -889,25 +889,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" -[[package]] -name = "docs" -version = "0.1.0" -dependencies = [ - "bumpalo", - "fs_extra", - "handlebars", - "maplit", - "pretty_assertions 0.5.1", - "pulldown-cmark", - "roc_builtins", - "roc_can", - "roc_collections", - "roc_load", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "downcast-rs" version = "1.2.0" @@ -2013,6 +1994,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "nonempty" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fa586da3e43cc7df44aae0e21ed2e743218b876de3f38035683d30bd8a3828e" + [[package]] name = "num-traits" version = "0.2.14" @@ -2961,6 +2948,7 @@ dependencies = [ "roc_can", "roc_collections", "roc_constrain", + "roc_docs", "roc_editor", "roc_fmt", "roc_gen", @@ -3012,6 +3000,25 @@ dependencies = [ "roc_types", ] +[[package]] +name = "roc_docs" +version = "0.1.0" +dependencies = [ + "bumpalo", + "fs_extra", + "handlebars", + "maplit", + "pretty_assertions 0.5.1", + "pulldown-cmark", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_load", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "roc_editor" version = "0.1.0" @@ -3034,6 +3041,7 @@ dependencies = [ "libc", "log", "maplit", + "nonempty", "page_size", "palette", "pest", diff --git a/Earthfile b/Earthfile index c297d4c3e2..a6c6be7384 100644 --- a/Earthfile +++ b/Earthfile @@ -1,4 +1,4 @@ -FROM rust:1.50-slim-buster +FROM rust:1.51-slim-buster WORKDIR /earthbuild prep-debian: diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 6c4b3ba494..bd542b0f43 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -33,6 +33,7 @@ target-all = [ [dependencies] roc_collections = { path = "../compiler/collections" } roc_can = { path = "../compiler/can" } +roc_docs = { path = "../docs" } roc_parse = { path = "../compiler/parse" } roc_region = { path = "../compiler/region" } roc_module = { path = "../compiler/module" } diff --git a/cli/src/lib.rs b/cli/src/lib.rs index dfec81975e..c15257487b 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -8,7 +8,7 @@ use roc_build::link::LinkType; use roc_gen::llvm::build::OptLevel; use roc_load::file::LoadingProblem; use std::io; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process; use std::process::Command; use target_lexicon::Triple; @@ -76,6 +76,25 @@ pub fn build_app<'a>() -> App<'a> { .help("(optional) The directory or files to open on launch.") ) ) + .subcommand( + App::new("docs") + .about("Generate documentation for Roc modules") + .arg(Arg::with_name(DIRECTORY_OR_FILES) + .index(1) + .multiple(true) + .required(true) + .help("The directory or files to build documentation for") + + ) + ) +} + +pub fn docs(files: Vec) { + roc_docs::generate( + files, + roc_builtins::std::standard_stdlib(), + Path::new("./generated-docs"), + ) } pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { @@ -132,10 +151,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io .expect("TODO gracefully handle block_on failing"); } } - Err(LoadingProblem::ParsingFailedReport(report)) => { - print!("{}", report); - } - Err(LoadingProblem::NoPlatform(report)) => { + Err(LoadingProblem::FormattedReport(report)) => { print!("{}", report); } Err(other) => { diff --git a/cli/src/main.rs b/cli/src/main.rs index 3a07efd95a..fca8e3924b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,6 +1,6 @@ -use roc_cli::{build, build_app, repl, DIRECTORY_OR_FILES}; +use roc_cli::{build, build_app, docs, repl, DIRECTORY_OR_FILES}; use std::io; -use std::path::Path; +use std::path::{Path, PathBuf}; use target_lexicon::Triple; fn main() -> io::Result<()> { @@ -35,6 +35,21 @@ fn main() -> io::Result<()> { } } } + Some("docs") => { + let values = matches + .subcommand_matches("docs") + .unwrap() + .values_of_os(DIRECTORY_OR_FILES) + .unwrap(); + + let paths = values + .map(|os_str| Path::new(os_str).to_path_buf()) + .collect::>(); + + docs(paths); + + Ok(()) + } _ => unreachable!(), } } diff --git a/cli/src/repl/eval.rs b/cli/src/repl/eval.rs index 22f608df70..8948762bd4 100644 --- a/cli/src/repl/eval.rs +++ b/cli/src/repl/eval.rs @@ -148,7 +148,7 @@ fn jit_to_ast_help<'a>( } }; - let fields = [Layout::Builtin(Builtin::Int64), layout.clone()]; + let fields = [Layout::Builtin(Builtin::Int64), *layout]; let layout = Layout::Struct(&fields); let result_stack_size = layout.stack_size(env.ptr_bytes); diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 572cd521fa..84d5d1704b 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -57,7 +57,7 @@ pub fn gen_and_eval<'a>( let mut loaded = match loaded { Ok(v) => v, - Err(LoadingProblem::ParsingFailedReport(report)) => { + Err(LoadingProblem::FormattedReport(report)) => { return Ok(ReplOutput::Problems(vec![report])); } Err(e) => { @@ -153,7 +153,7 @@ pub fn gen_and_eval<'a>( let expr_type_str = content_to_string(content.clone(), &subs, home, &interns); let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { - Some(layout) => layout.clone(), + Some(layout) => *layout, None => { return Ok(ReplOutput::NoProblems { expr: "".to_string(), diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 987bbc322c..b070ec540e 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -54,7 +54,7 @@ mod cli_run { ) { let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); if !compile_out.stderr.is_empty() { - panic!(compile_out.stderr); + panic!("{}", compile_out.stderr); } assert!(compile_out.status.success()); diff --git a/compiler/builtins/docs/Bool.roc b/compiler/builtins/docs/Bool.roc index 1c3a142200..601a3d491f 100644 --- a/compiler/builtins/docs/Bool.roc +++ b/compiler/builtins/docs/Bool.roc @@ -1,11 +1,11 @@ -interface Bool2 +interface Bool exposes [ not, and, or, xor, isEq, isNotEq ] imports [] -## Returns #False when given #True, and vice versa. +## Returns `False` when given `True`, and vice versa. not : [True, False] -> [True, False] -## Returns #True when given #True and #True, and #False when either argument is #False. +## Returns `True` when given `True` and `True`, and `False` when either argument is `False`. ## ## `a && b` is shorthand for `Bool.and a b` ## @@ -39,7 +39,7 @@ not : [True, False] -> [True, False] and : Bool, Bool -> Bool -## Returns #True when given #True for either argument, and #False only when given #False and #False. +## Returns `True` when given `True` for either argument, and `False` only when given `False` and `False`. ## ## `a || b` is shorthand for `Bool.or a b`. ## @@ -55,14 +55,13 @@ and : Bool, Bool -> Bool ## ## In some languages, `&&` and `||` are special-cased in the compiler to skip ## evaluating the expression after the operator under certain circumstances. -## -## In Roc, this is not the case. See the performance notes for #Bool.and for details. +## # In Roc, this is not the case. See the performance notes for #Bool.and for details. or : Bool, Bool -> Bool ## Exclusive or xor : Bool, Bool -> Bool -## Returns #True if the two values are *structurally equal*, and #False otherwise. +## Returns `True` if the two values are *structurally equal*, and `False` otherwise. ## ## `a == b` is shorthand for `Bool.isEq a b` ## diff --git a/compiler/builtins/docs/Dict.roc b/compiler/builtins/docs/Dict.roc index c3a78b00df..9486764194 100644 --- a/compiler/builtins/docs/Dict.roc +++ b/compiler/builtins/docs/Dict.roc @@ -8,15 +8,14 @@ isEmpty : Dict * * -> Bool ## Convert each key and value in the #Dict to something new, by calling a conversion ## function on each of them. Then return a new #Map of the converted keys and values. -## +## ## >>> Dict.map {{ 3.14 => "pi", 1.0 => "one" }} \{ key, value } -> { key: -## +## ## >>> Dict.map {[ "", "a", "bc" ]} Str.isEmpty -## +## ## `map` functions like this are common in Roc, and they all work similarly. ## See for example #Result.map, #List.map, and #Set.map. map : Dict beforeKey beforeValue, - (\{ key: beforeKey, value: beforeValue } -> - { key: afterKey, value: afterValue } - ) -> Dict afterKey afterValue + ({ key: beforeKey, value: beforeValue } -> { key: afterKey, value: afterValue }) + -> Dict afterKey afterValue diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index dda129e03d..fbc7a192d7 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -1,5 +1,54 @@ interface List2 - exposes [ List, single, empty, repeat, range, reverse, sort, map, mapWithIndex, mapOrCancel, mapOks, update, updater, allOks, append, prepend, concat, join, joinMap, oks, zip, zipMap, keepIf, dropIf, first, last, get, max, min, put, drop, append, prepend, dropLast, dropFirst, takeFirst, takeLast, split, sublist, walk, walkBackwards, walkUntil, walkBackwardsUntil, len, isEmpty, contains, all, any ] + exposes + [ List + , single + , empty + , repeat + , range + , reverse + , sort + , map + , mapWithIndex + , mapOrCancel + , mapOks + , update + , updater + , allOks + , append + , prepend + , concat + , join + , joinMap + , oks + , zip + , zipMap + , keepIf + , dropIf + , first + , last + , get + , max + , min + , put + , drop + , append + , prepend + , dropLast + , dropFirst + , takeFirst + , takeLast + , split + , sublist + , walk + , walkBackwards + , walkUntil + , walkBackwardsUntil + , len + , isEmpty + , contains + , all + , any + ] imports [] ## Types @@ -298,7 +347,7 @@ oks : List (Result elem *) -> List elem ## ## > For a generalized version that returns whatever you like, instead of a `Pair`, ## > see `zipMap`. -zip : List a, List b, -> List [ Pair a b ]* +zip : List a, List b -> List [ Pair a b ]* ## Like `zip` but you can specify what to do with each element. ## @@ -307,7 +356,7 @@ zip : List a, List b, -> List [ Pair a b ]* ## >>> List.zipMap [ 1, 2, 3 ] [ 0, 5, 4 ] [ 2, 1 ] \num1 num2 num3 -> num1 + num2 - num3 ## ## Accepts up to 8 lists. -zipMap : List a, List b, (a, b) -> List c +zipMap : List a, List b, (a, b -> c) -> List c ## Filter diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 5f29271f76..9d8ef2104d 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -51,7 +51,7 @@ interface Num2 ## ## In practice, these are rarely needed. It's most common to write ## number literals without any suffix. -Num range : @Num range +Num range : [ @Num range ] ## A fixed-size integer - that is, a number with no fractional component. ## @@ -102,21 +102,21 @@ Num range : @Num range ## * Start by deciding if this integer should allow negative numbers, and choose signed or unsigned accordingly. ## * Next, think about the range of numbers you expect this number to hold. Choose the smallest size you will never expect to overflow, no matter the inputs your program receives. (Validating inputs for size, and presenting the user with an error if they are too big, can help guard against overflow.) ## * Finally, if a particular numeric calculation is running too slowly, you can try experimenting with other number sizes. This rarely makes a meaningful difference, but some processors can operate on different number sizes at different speeds. -Int size : Num (@Int size) +Int size : Num [ @Int size ] ## A signed 8-bit integer, ranging from -128 to 127 -I8 : Int @I8 -U8 : Int @U8 -U16 : Int @U16 -I16 : Int @I16 -U32 : Int @U32 -I32 : Int @I32 -I64 : Int @I64 -U64 : Int @U64 -I128 : Int @I128 -U128 : Int @U128 -Ilen : Int @Ilen -Nat : Int @Nat +I8 : Int [ @I8 ] +U8 : Int [ @U8 ] +U16 : Int [ @U16 ] +I16 : Int [ @I16 ] +U32 : Int [ @U32 ] +I32 : Int [ @I32 ] +I64 : Int [ @I64 ] +U64 : Int [ @U64 ] +I128 : Int [ @I128 ] +U128 : Int [ @U128 ] +Ilen : Int [ @Ilen ] +Nat : Int [ @Nat ] ## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values. ## @@ -574,9 +574,9 @@ divRound : Int, Int -> Int ## Bitwise -xor : Int -> Int -> Int +xor : Int, Int -> Int -and : Int -> Int -> Int +and : Int, Int -> Int not : Int -> Int diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index 54920bd56e..69b46ef348 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -1,7 +1,7 @@ -interface Str2 - exposes [ Str2, decimal, split, isEmpty, startsWith, endsWith, contains, anyGraphemes, allGraphemes, join, joinWith, padGraphemesStart, padGraphemesEnd, graphemes, reverseGraphemes, isCaseInsensitiveEq, isCaseInsensitiveNeq, walkGraphemes, isCapitalized, isAllUppercase, isAllLowercase, toUtf8, toUtf16, toUtf32, walkUtf8, walkUtf16, walkUtf32, walkRevUtf8, walkRevUtf16, walkRevUtf32 ] +interface Str + exposes [ Str, decimal, split, isEmpty, startsWith, endsWith, contains, anyGraphemes, allGraphemes, join, joinWith, padGraphemesStart, padGraphemesEnd, graphemes, reverseGraphemes, isCaseInsensitiveEq, isCaseInsensitiveNeq, walkGraphemes, isCapitalized, isAllUppercase, isAllLowercase, toUtf8, toUtf16, toUtf32, walkUtf8, walkUtf16, walkUtf32, walkRevUtf8, walkRevUtf16, walkRevUtf32 ] imports [] -## Types +## # Types ## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks ## to the basics. diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index a8d7a27913..4c4b13d99a 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -430,6 +430,20 @@ pub fn types() -> MutMap { ), ); + // log : Float a -> Float a + let log_needs_positive = SolvedType::TagUnion( + vec![(TagName::Global("LogNeedsPositive".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + + add_type( + Symbol::NUM_LOG, + top_level_function( + vec![float_type(flex(TVAR1))], + Box::new(result_type(float_type(flex(TVAR1)), log_needs_positive)), + ), + ); + // round : Float a -> Int b add_type( Symbol::NUM_ROUND, @@ -722,6 +736,15 @@ pub fn types() -> MutMap { ), ); + // product : List (Num a) -> Num a + add_type( + Symbol::LIST_PRODUCT, + top_level_function( + vec![list_type(num_type(flex(TVAR1)))], + Box::new(num_type(flex(TVAR1))), + ), + ); + // walk : List elem, (elem -> accum -> accum), accum -> accum add_type( Symbol::LIST_WALK, diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index f9db76ab05..1dae84e6c9 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -65,6 +65,13 @@ impl IntroducedVariables { } } +fn malformed(env: &mut Env, region: Region, name: &str) { + use roc_problem::can::RuntimeError::*; + + let problem = MalformedTypeName((*name).into(), region); + env.problem(roc_problem::can::Problem::RuntimeError(problem)); +} + pub fn canonicalize_annotation( env: &mut Env, scope: &mut Scope, @@ -446,7 +453,16 @@ fn can_annotation_help( local_aliases, references, ), - Wildcard | Malformed(_) => { + Wildcard => { + let var = var_store.fresh(); + + introduced_variables.insert_wildcard(var); + + Type::Variable(var) + } + Malformed(string) => { + malformed(env, region, string); + let var = var_store.fresh(); introduced_variables.insert_wildcard(var); @@ -542,8 +558,9 @@ fn can_assigned_fields<'a>( field = nested; continue 'inner; } - Malformed(_) => { - // TODO report this? + Malformed(string) => { + malformed(env, region, string); + // completely skip this element, advance to the next tag continue 'outer; } @@ -645,8 +662,9 @@ fn can_tags<'a>( tag = nested; continue 'inner; } - Tag::Malformed(_) => { - // TODO report this? + Tag::Malformed(string) => { + malformed(env, region, string); + // completely skip this element, advance to the next tag continue 'outer; } diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index fee0598286..334fee9cda 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -77,6 +77,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_CONCAT => list_concat, LIST_CONTAINS => list_contains, LIST_SUM => list_sum, + LIST_PRODUCT => list_product, LIST_PREPEND => list_prepend, LIST_JOIN => list_join, LIST_MAP => list_map, @@ -138,6 +139,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option NUM_REM => num_rem, NUM_IS_MULTIPLE_OF => num_is_multiple_of, NUM_SQRT => num_sqrt, + NUM_LOG => num_log, NUM_ROUND => num_round, NUM_IS_ODD => num_is_odd, NUM_IS_EVEN => num_is_even, @@ -217,6 +219,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { Symbol::LIST_CONCAT => list_concat, Symbol::LIST_CONTAINS => list_contains, Symbol::LIST_SUM => list_sum, + Symbol::LIST_PRODUCT => list_product, Symbol::LIST_PREPEND => list_prepend, Symbol::LIST_JOIN => list_join, Symbol::LIST_MAP => list_map, @@ -274,6 +277,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap { Symbol::NUM_REM => num_rem, Symbol::NUM_IS_MULTIPLE_OF => num_is_multiple_of, Symbol::NUM_SQRT => num_sqrt, + Symbol::NUM_LOG => num_log, Symbol::NUM_ROUND => num_round, Symbol::NUM_IS_ODD => num_is_odd, Symbol::NUM_IS_EVEN => num_is_even, @@ -411,8 +415,8 @@ fn lowlevel_4(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def { /// Num.maxInt : Int fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def { let int_var = var_store.fresh(); - let int_percision_var = var_store.fresh(); - let body = Int(int_var, int_percision_var, i64::MAX.into()); + let int_precision_var = var_store.fresh(); + let body = Int(int_var, int_precision_var, i64::MAX.into()); Def { annotation: None, @@ -426,8 +430,8 @@ fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def { /// Num.minInt : Int fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def { let int_var = var_store.fresh(); - let int_percision_var = var_store.fresh(); - let body = Int(int_var, int_percision_var, i64::MIN.into()); + let int_precision_var = var_store.fresh(); + let body = Int(int_var, int_precision_var, i64::MIN.into()); Def { annotation: None, @@ -1131,50 +1135,82 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); let float_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); - let percision_var = var_store.fresh(); + let precision_var = var_store.fresh(); let ret_var = var_store.fresh(); let body = If { branch_var: ret_var, cond_var: bool_var, branches: vec![( - // if-condition - no_region( - // Num.neq denominator 0 - RunLowLevel { - op: LowLevel::NotEq, - args: vec![ - (float_var, Var(Symbol::ARG_1)), - (float_var, Float(unbound_zero_var, percision_var, 0.0)), - ], - ret_var: bool_var, - }, - ), - // denominator was not zero - no_region( - // Ok (Float.#divUnchecked numerator denominator) - tag( - "Ok", - vec![ - // Num.#divUnchecked numerator denominator - RunLowLevel { - op: LowLevel::NumSqrtUnchecked, - args: vec![(float_var, Var(Symbol::ARG_1))], - ret_var: float_var, - }, - ], - var_store, - ), - ), - )], - final_else: Box::new( - // denominator was zero + no_region(RunLowLevel { + op: LowLevel::NumGte, + args: vec![ + (float_var, Var(Symbol::ARG_1)), + (float_var, Float(unbound_zero_var, precision_var, 0.0)), + ], + ret_var: bool_var, + }), no_region(tag( - "Err", - vec![tag("DivByZero", Vec::new(), var_store)], + "Ok", + vec![RunLowLevel { + op: LowLevel::NumSqrtUnchecked, + args: vec![(float_var, Var(Symbol::ARG_1))], + ret_var: float_var, + }], var_store, )), - ), + )], + final_else: Box::new(no_region(tag( + "Err", + vec![tag("SqrtOfNegative", Vec::new(), var_store)], + var_store, + ))), + }; + + defn( + symbol, + vec![(float_var, Symbol::ARG_1)], + var_store, + body, + ret_var, + ) +} + +/// Num.log : Float -> Result Float [ LogNeedsPositive ]* +fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def { + let bool_var = var_store.fresh(); + let float_var = var_store.fresh(); + let unbound_zero_var = var_store.fresh(); + let precision_var = var_store.fresh(); + let ret_var = var_store.fresh(); + + let body = If { + branch_var: ret_var, + cond_var: bool_var, + branches: vec![( + no_region(RunLowLevel { + op: LowLevel::NumGt, + args: vec![ + (float_var, Var(Symbol::ARG_1)), + (float_var, Float(unbound_zero_var, precision_var, 0.0)), + ], + ret_var: bool_var, + }), + no_region(tag( + "Ok", + vec![RunLowLevel { + op: LowLevel::NumLogUnchecked, + args: vec![(float_var, Var(Symbol::ARG_1))], + ret_var: float_var, + }], + var_store, + )), + )], + final_else: Box::new(no_region(tag( + "Err", + vec![tag("LogNeedsPositive", Vec::new(), var_store)], + var_store, + ))), }; defn( @@ -1385,8 +1421,8 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def { /// Num.maxI128: I128 fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def { let int_var = var_store.fresh(); - let int_percision_var = var_store.fresh(); - let body = Int(int_var, int_percision_var, i128::MAX); + let int_precision_var = var_store.fresh(); + let body = Int(int_var, int_precision_var, i128::MAX); let std = roc_builtins::std::types(); let solved = std.get(&symbol).unwrap(); @@ -2116,22 +2152,12 @@ fn list_walk_backwards(symbol: Symbol, var_store: &mut VarStore) -> Def { /// List.sum : List (Num a) -> Num a fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def { - let list_var = var_store.fresh(); - let result_var = var_store.fresh(); + lowlevel_1(symbol, LowLevel::ListSum, var_store) +} - let body = RunLowLevel { - op: LowLevel::ListSum, - args: vec![(list_var, Var(Symbol::ARG_1))], - ret_var: result_var, - }; - - defn( - symbol, - vec![(list_var, Symbol::ARG_1)], - var_store, - body, - result_var, - ) +/// List.product : List (Num a) -> Num a +fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def { + lowlevel_1(symbol, LowLevel::ListProduct, var_store) } /// List.keepIf : List elem, (elem -> Bool) -> List elem @@ -2661,7 +2687,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); let num_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); - let percision_var = var_store.fresh(); + let precision_var = var_store.fresh(); let ret_var = var_store.fresh(); let body = If { @@ -2675,7 +2701,7 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::NotEq, args: vec![ (num_var, Var(Symbol::ARG_2)), - (num_var, Float(unbound_zero_var, percision_var, 0.0)), + (num_var, Float(unbound_zero_var, precision_var, 0.0)), ], ret_var: bool_var, }, @@ -2724,7 +2750,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { let bool_var = var_store.fresh(); let num_var = var_store.fresh(); let unbound_zero_var = var_store.fresh(); - let unbound_zero_percision_var = var_store.fresh(); + let unbound_zero_precision_var = var_store.fresh(); let ret_var = var_store.fresh(); let body = If { @@ -2740,7 +2766,7 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def { (num_var, Var(Symbol::ARG_2)), ( num_var, - Int(unbound_zero_var, unbound_zero_percision_var, 0), + Int(unbound_zero_var, unbound_zero_precision_var, 0), ), ], ret_var: bool_var, @@ -2795,7 +2821,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let len_var = var_store.fresh(); let zero_var = var_store.fresh(); - let zero_percision_var = var_store.fresh(); + let zero_precision_var = var_store.fresh(); let list_elem_var = var_store.fresh(); let ret_var = var_store.fresh(); @@ -2810,7 +2836,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, Int(zero_var, zero_percision_var, 0)), + (len_var, Int(zero_var, zero_precision_var, 0)), ( len_var, RunLowLevel { @@ -2834,7 +2860,7 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def { op: LowLevel::ListGetUnsafe, args: vec![ (list_var, Var(Symbol::ARG_1)), - (len_var, Int(zero_var, zero_percision_var, 0)), + (len_var, Int(zero_var, zero_precision_var, 0)), ], ret_var: list_elem_var, }, @@ -2876,7 +2902,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let len_var = var_store.fresh(); let num_var = var_store.fresh(); - let num_percision_var = var_store.fresh(); + let num_precision_var = var_store.fresh(); let list_elem_var = var_store.fresh(); let ret_var = var_store.fresh(); @@ -2891,7 +2917,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { RunLowLevel { op: LowLevel::NotEq, args: vec![ - (len_var, Int(num_var, num_percision_var, 0)), + (len_var, Int(num_var, num_precision_var, 0)), ( len_var, RunLowLevel { @@ -2930,7 +2956,7 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def { ret_var: len_var, }, ), - (arg_var, Int(num_var, num_percision_var, 1)), + (arg_var, Int(num_var, num_precision_var, 1)), ], ret_var: len_var, }, diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 82c9fb71c8..54d361b46d 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1481,7 +1481,7 @@ fn to_pending_def<'a>( } } - SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) | Nested(sub_def) => { + SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => { to_pending_def(env, var_store, sub_def, scope, pattern_type) } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 3eaffe6e55..78cdb8038c 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -408,46 +408,7 @@ pub fn canonicalize_expr<'a>( } ast::Expr::Var { module_name, ident } => { canonicalize_lookup(env, scope, module_name, ident, region) - } //ast::Expr::InterpolatedStr(pairs, suffix) => { - // let mut output = Output::new(); - // let can_pairs: Vec<(String, Located)> = pairs - // .into_iter() - // .map(|(string, loc_ident)| { - // // From a language design perspective, we only permit idents in interpolation. - // // However, in a canonical Expr we store it as a full Expr, not a Symbol. - // // This is so that we can resolve it to either Var or Unrecognized; if we - // // stored it as a Symbol, we couldn't record runtime errors here. - // let can_expr = match resolve_ident( - // &env, - // &scope, - // loc_ident.value, - // &mut output.references, - // ) { - // Ok(symbol) => Var(symbol), - // Err(ident) => { - // let loc_ident = Located { - // region: loc_ident.region, - // value: ident, - // }; - - // env.problem(Problem::LookupNotInScope(loc_ident.clone())); - - // RuntimeError(LookupNotInScope(loc_ident)) - // } - // }; - - // ( - // string, - // Located { - // region: loc_ident.region, - // value: can_expr, - // }, - // ) - // }) - // .collect(); - - // (InterpolatedStr(can_pairs, suffix), output) - //} + } ast::Expr::Defs(loc_defs, loc_ret) => { can_defs_with_return( env, @@ -767,11 +728,6 @@ pub fn canonicalize_expr<'a>( (RuntimeError(problem), Output::default()) } - ast::Expr::Nested(sub_expr) => { - let (answer, output) = canonicalize_expr(env, var_store, scope, region, sub_expr); - - (answer.value, output) - } ast::Expr::NonBase10Int { string, base, diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 520e67e2f1..53e082407e 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -4,7 +4,7 @@ use roc_module::ident::ModuleName; use roc_module::operator::BinOp::Pizza; use roc_module::operator::{BinOp, CalledVia}; use roc_parse::ast::Expr::{self, *}; -use roc_parse::ast::{AssignedField, Def, Pattern, WhenBranch}; +use roc_parse::ast::{AssignedField, Def, WhenBranch}; use roc_region::all::{Located, Region}; // BinOp precedence logic adapted from Gluon by Markus Westerlind, MIT licensed @@ -59,7 +59,7 @@ fn new_op_call_expr<'a>( Located { value, region } } -fn desugar_defs<'a>( +fn desugar_def_helps<'a>( arena: &'a Bump, region: Region, defs: &'a [&'a Located>], @@ -88,39 +88,23 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> { use roc_parse::ast::Def::*; match def { - Body(loc_pattern, loc_expr) | Nested(Body(loc_pattern, loc_expr)) => { - Body(loc_pattern, desugar_expr(arena, loc_expr)) - } - SpaceBefore(def, _) - | SpaceAfter(def, _) - | Nested(SpaceBefore(def, _)) - | Nested(SpaceAfter(def, _)) => desugar_def(arena, def), - Nested(Nested(def)) => desugar_def(arena, def), - alias @ Alias { .. } => Nested(alias), - Nested(alias @ Alias { .. }) => Nested(alias), - ann @ Annotation(_, _) => Nested(ann), - Nested(ann @ Annotation(_, _)) => Nested(ann), + Body(loc_pattern, loc_expr) => Body(loc_pattern, desugar_expr(arena, loc_expr)), + SpaceBefore(def, _) | SpaceAfter(def, _) => desugar_def(arena, def), + alias @ Alias { .. } => *alias, + ann @ Annotation(_, _) => *ann, AnnotatedBody { ann_pattern, ann_type, comment, body_pattern, body_expr, - } - | Nested(AnnotatedBody { - ann_pattern, - ann_type, - comment, - body_pattern, - body_expr, - }) => AnnotatedBody { + } => AnnotatedBody { ann_pattern, ann_type, comment: *comment, body_pattern: *body_pattern, body_expr: desugar_expr(arena, body_expr), }, - Nested(NotYetImplemented(s)) => todo!("{}", s), NotYetImplemented(s) => todo!("{}", s), } } @@ -130,33 +114,22 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> { pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Located> { match &loc_expr.value { Float(_) - | Nested(Float(_)) | Num(_) - | Nested(Num(_)) | NonBase10Int { .. } - | Nested(NonBase10Int { .. }) | Str(_) - | Nested(Str(_)) | AccessorFunction(_) - | Nested(AccessorFunction(_)) | Var { .. } - | Nested(Var { .. }) | MalformedIdent(_, _) - | Nested(MalformedIdent(_, _)) | MalformedClosure - | Nested(MalformedClosure) | PrecedenceConflict { .. } - | Nested(PrecedenceConflict { .. }) | GlobalTag(_) - | Nested(GlobalTag(_)) - | PrivateTag(_) - | Nested(PrivateTag(_)) => loc_expr, + | PrivateTag(_) => loc_expr, - Access(sub_expr, paths) | Nested(Access(sub_expr, paths)) => { + Access(sub_expr, paths) => { let region = loc_expr.region; let loc_sub_expr = Located { region, - value: Nested(sub_expr), + value: **sub_expr, }; let value = Access(&desugar_expr(arena, arena.alloc(loc_sub_expr)).value, paths); @@ -165,11 +138,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a List { items, final_comments, - } - | Nested(List { - items, - final_comments, - }) => { + } => { let mut new_items = Vec::with_capacity_in(items.len(), arena); for item in items.iter() { @@ -189,11 +158,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a Record { fields, final_comments, - } - | Nested(Record { - fields, - final_comments, - }) => { + } => { let mut new_fields = Vec::with_capacity_in(fields.len(), arena); for field in fields.iter() { @@ -220,12 +185,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a fields, update, final_comments, - } - | Nested(RecordUpdate { - fields, - update, - final_comments, - }) => { + } => { // NOTE the `update` field is always a `Var { .. }` and does not need to be desugared let mut new_fields = Vec::with_capacity_in(fields.len(), arena); @@ -249,14 +209,11 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a }, }) } - Closure(loc_patterns, loc_ret) | Nested(Closure(loc_patterns, loc_ret)) => { - arena.alloc(Located { - region: loc_expr.region, - value: Closure(loc_patterns, desugar_expr(arena, loc_ret)), - }) - } - Backpassing(loc_patterns, loc_body, loc_ret) - | Nested(Backpassing(loc_patterns, loc_body, loc_ret)) => { + Closure(loc_patterns, loc_ret) => arena.alloc(Located { + region: loc_expr.region, + value: Closure(loc_patterns, desugar_expr(arena, loc_ret)), + }), + Backpassing(loc_patterns, loc_body, loc_ret) => { // loc_patterns <- loc_body // // loc_ret @@ -293,13 +250,9 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a } } } - BinOps(lefts, right) | Nested(BinOps(lefts, right)) => { - desugar_bin_ops(arena, loc_expr.region, lefts, right) - } - Defs(defs, loc_ret) | Nested(Defs(defs, loc_ret)) => { - desugar_defs(arena, loc_expr.region, *defs, loc_ret) - } - Apply(loc_fn, loc_args, called_via) | Nested(Apply(loc_fn, loc_args, called_via)) => { + BinOps(lefts, right) => desugar_bin_ops(arena, loc_expr.region, lefts, right), + Defs(defs, loc_ret) => desugar_def_helps(arena, loc_expr.region, *defs, loc_ret), + Apply(loc_fn, loc_args, called_via) => { let mut desugared_args = Vec::with_capacity_in(loc_args.len(), arena); for loc_arg in loc_args.iter() { @@ -313,7 +266,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a region: loc_expr.region, }) } - When(loc_cond_expr, branches) | Nested(When(loc_cond_expr, branches)) => { + When(loc_cond_expr, branches) => { let loc_desugared_cond = &*arena.alloc(desugar_expr(arena, &loc_cond_expr)); let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena); @@ -321,15 +274,10 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a let desugared = desugar_expr(arena, &branch.value); let mut alternatives = Vec::with_capacity_in(branch.patterns.len(), arena); - for loc_pattern in branch.patterns.iter() { - alternatives.push(Located { - region: loc_pattern.region, - value: Pattern::Nested(&loc_pattern.value), - }) - } + alternatives.extend(branch.patterns.iter().copied()); let desugared_guard = if let Some(guard) = &branch.guard { - Some(desugar_expr(arena, guard).clone()) + Some(*desugar_expr(arena, guard)) } else { None }; @@ -338,10 +286,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a desugared_branches.push(&*arena.alloc(WhenBranch { patterns: alternatives, - value: Located { - region: desugared.region, - value: Nested(&desugared.value), - }, + value: *desugared, guard: desugared_guard, })); } @@ -353,7 +298,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a region: loc_expr.region, }) } - UnaryOp(loc_arg, loc_op) | Nested(UnaryOp(loc_arg, loc_op)) => { + UnaryOp(loc_arg, loc_op) => { use roc_module::operator::UnaryOp::*; let region = loc_op.region; @@ -379,24 +324,18 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a region: loc_expr.region, }) } - SpaceBefore(expr, _) - | Nested(SpaceBefore(expr, _)) - | SpaceAfter(expr, _) - | Nested(SpaceAfter(expr, _)) - | ParensAround(expr) - | Nested(ParensAround(expr)) - | Nested(Nested(expr)) => { + SpaceBefore(expr, _) | SpaceAfter(expr, _) | ParensAround(expr) => { // Since we've already begun canonicalization, spaces and parens // are no longer needed and should be dropped. desugar_expr( arena, arena.alloc(Located { - value: Nested(expr), + value: **expr, region: loc_expr.region, }), ) } - If(if_thens, final_else_branch) | Nested(If(if_thens, final_else_branch)) => { + If(if_thens, final_else_branch) => { // If does not get desugared into `when` so we can give more targetted error messages during type checking. let desugared_final_else = &*arena.alloc(desugar_expr(arena, &final_else_branch)); @@ -404,8 +343,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a for (condition, then_branch) in if_thens.iter() { desugared_if_thens.push(( - desugar_expr(arena, condition).clone(), - desugar_expr(arena, then_branch).clone(), + *desugar_expr(arena, condition), + *desugar_expr(arena, then_branch), )); } diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index d95965da52..a150befc24 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -238,7 +238,7 @@ pub fn canonicalize_pattern<'a>( ptype => unsupported_pattern(env, ptype, region), }, - SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => { + SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => { return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region) } RecordDestructure(patterns) => { diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index f6d9558ede..92de3da1ce 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -19,7 +19,6 @@ impl<'a> Formattable<'a> for Def<'a> { SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => { spaces.iter().any(|s| s.is_comment()) || sub_def.is_multiline() } - Nested(def) => def.is_multiline(), NotYetImplemented(s) => todo!("{}", s), } } @@ -99,7 +98,6 @@ impl<'a> Formattable<'a> for Def<'a> { sub_def.format(buf, indent); fmt_spaces(buf, spaces.iter(), indent); } - Nested(def) => def.format(buf, indent), NotYetImplemented(s) => todo!("{}", s), } } diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index f04cfdbe35..41fc6592d0 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -75,7 +75,7 @@ impl<'a> Formattable<'a> for Expr<'a> { expr: loc_subexpr, .. }) => loc_subexpr.is_multiline(), - ParensAround(subexpr) | Nested(subexpr) => subexpr.is_multiline(), + ParensAround(subexpr) => subexpr.is_multiline(), Closure(loc_patterns, loc_body) => { // check the body first because it's more likely to be multiline @@ -298,9 +298,6 @@ impl<'a> Formattable<'a> for Expr<'a> { sub_expr.format_with_options(buf, parens, newlines, indent); } - Nested(nested_expr) => { - nested_expr.format_with_options(buf, parens, newlines, indent); - } AccessorFunction(key) => { buf.push('.'); buf.push_str(key); @@ -508,8 +505,6 @@ fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool { false } - Nested(nested_expr) => empty_line_before_expr(nested_expr), - _ => false, } } diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index f768b26813..30f0d96125 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -22,8 +22,6 @@ impl<'a> Formattable<'a> for Pattern<'a> { spaces.iter().any(|s| s.is_comment()) } - Pattern::Nested(nested_pat) => nested_pat.is_multiline(), - Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()), Pattern::RequiredField(_, subpattern) => subpattern.is_multiline(), @@ -153,10 +151,6 @@ impl<'a> Formattable<'a> for Pattern<'a> { } } - Nested(sub_pattern) => { - sub_pattern.format_with_options(buf, parens, newlines, indent); - } - // Malformed Malformed(string) | MalformedIdent(string, _) => buf.push_str(string), QualifiedIdentifier { module_name, ident } => { diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 151dd27635..ebcf367152 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -28,7 +28,7 @@ mod test_fmt { assert_eq!(buf, expected) } - Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", input, error) + Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error) }; } @@ -1833,23 +1833,23 @@ mod test_fmt { indoc!( r#" when b is - 1 | 2 | - 3 - -> + 1 | 2 | + 3 + -> - 4 - 5 | 6 | 7 -> + 4 + 5 | 6 | 7 -> - 8 - 9 - | 10 -> 11 + 8 + 9 + | 10 -> 11 - 12 | 13 -> - when c is - 14 | 15 -> 16 - 17 - | 18 -> 19 - 20 -> 21 + 12 | 13 -> + when c is + 14 | 15 -> 16 + 17 + | 18 -> 19 + 20 -> 21 "# ), diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 1d1f7e7a5e..4dfda5e9fa 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -7,8 +7,8 @@ use crate::llvm::build_hash::generic_hash; use crate::llvm::build_list::{ allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, list_contains, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map, - list_map2, list_map3, list_map_with_index, list_prepend, list_repeat, list_reverse, list_set, - list_single, list_sum, list_walk, list_walk_backwards, + list_map2, list_map3, list_map_with_index, list_prepend, list_product, list_repeat, + list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards, }; use crate::llvm::build_str::{ str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, str_from_utf8, @@ -90,9 +90,9 @@ pub enum OptLevel { Optimize, } -impl Into for OptLevel { - fn into(self) -> OptimizationLevel { - match self { +impl From for OptimizationLevel { + fn from(level: OptLevel) -> Self { + match level { OptLevel::Normal => OptimizationLevel::None, OptLevel::Optimize => OptimizationLevel::Aggressive, } @@ -337,6 +337,12 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { f64_type.fn_type(&[f64_type.into()], false), ); + add_intrinsic( + module, + LLVM_LOG_F64, + f64_type.fn_type(&[f64_type.into()], false), + ); + add_intrinsic( module, LLVM_LROUND_I64_F64, @@ -455,6 +461,7 @@ fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) { static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64"; static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32"; static LLVM_SQRT_F64: &str = "llvm.sqrt.f64"; +static LLVM_LOG_F64: &str = "llvm.log.f64"; static LLVM_LROUND_I64_F64: &str = "llvm.lround.i64.f64"; static LLVM_FABS_F64: &str = "llvm.fabs.f64"; static LLVM_SIN_F64: &str = "llvm.sin.f64"; @@ -2030,7 +2037,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( // access itself! // scope = scope.clone(); - scope.insert(*symbol, (layout.clone(), val)); + scope.insert(*symbol, (*layout, val)); stack.push(*symbol); } @@ -2063,8 +2070,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( } => { // when the fail case is just Rethrow, there is no cleanup work to do // so we can just treat this invoke as a normal call - let stmt = - roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass); + let stmt = roc_mono::ir::Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass); build_exp_stmt(env, layout_ids, scope, parent, &stmt) } @@ -2088,7 +2094,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( scope, parent, *symbol, - layout.clone(), + *layout, function_value.into(), call.arguments, None, @@ -2108,7 +2114,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( scope, parent, *symbol, - layout.clone(), + *layout, function_ptr.into(), call.arguments, None, @@ -2135,7 +2141,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( scope, parent, *symbol, - layout.clone(), + *layout, function_ptr.into(), call.arguments, Some(closure_data), @@ -2194,7 +2200,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes); let switch_args = SwitchArgsIr { - cond_layout: cond_layout.clone(), + cond_layout: *cond_layout, cond_symbol: *cond_symbol, branches, default_branch: default_branch.1, @@ -2242,7 +2248,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( for (ptr, param) in joinpoint_args.iter().zip(parameters.iter()) { let value = env.builder.build_load(*ptr, "load_jp_argument"); - scope.insert(param.symbol, (param.layout.clone(), value)); + scope.insert(param.symbol, (param.layout, value)); } // put the continuation in @@ -2277,7 +2283,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>( match modify { Inc(symbol, inc_amount) => { let (value, layout) = load_symbol_and_layout(scope, symbol); - let layout = layout.clone(); + let layout = *layout; if layout.contains_refcounted() { increment_refcount_layout( @@ -3177,7 +3183,7 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( let function_pointer_type = { let function_layout = - ClosureLayout::extend_function_layout(arena, arguments, closure.clone(), result); + ClosureLayout::extend_function_layout(arena, arguments, *closure, result); // this is already a (function) pointer type basic_type_from_layout(arena, context, &function_layout, env.ptr_bytes) @@ -3252,7 +3258,7 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( ); // STEP 4: build a {} -> u64 function that gives the size of the closure - let layout = Layout::Closure(arguments, closure.clone(), result); + let layout = Layout::Closure(arguments, *closure, result); build_host_exposed_alias_size(env, def_name, alias_symbol, &layout); } @@ -3455,7 +3461,7 @@ pub fn build_proc<'a, 'ctx, 'env>( // Add args to scope for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) { set_name(arg_val, arg_symbol.ident_string(&env.interns)); - scope.insert(*arg_symbol, (layout.clone(), arg_val)); + scope.insert(*arg_symbol, (*layout, arg_val)); } let body = build_exp_stmt(env, layout_ids, &mut scope, fn_val, &proc.body); @@ -3931,6 +3937,13 @@ fn run_low_level<'a, 'ctx, 'env>( list_sum(env, parent, list, layout) } + ListProduct => { + debug_assert_eq!(args.len(), 1); + + let list = load_symbol(scope, &args[0]); + + list_product(env, parent, list, layout) + } ListAppend => { // List.append : List elem, elem -> List elem debug_assert_eq!(args.len(), 2); @@ -3963,8 +3976,8 @@ fn run_low_level<'a, 'ctx, 'env>( list_join(env, inplace, parent, list, outer_list_layout) } - NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor - | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => { + NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos + | NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => { debug_assert_eq!(args.len(), 1); let (arg, arg_layout) = load_symbol_and_layout(scope, &args[0]); @@ -4559,7 +4572,7 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( { env.builder.position_at_end(pass_block); - scope.insert(symbol, (ret_layout.clone(), call_result)); + scope.insert(symbol, (*ret_layout, call_result)); build_exp_stmt(env, layout_ids, scope, parent, pass); @@ -5273,6 +5286,7 @@ fn build_float_unary_op<'a, 'ctx, 'env>( NumNeg => bd.build_float_neg(arg, "negate_float").into(), NumAbs => env.call_intrinsic(LLVM_FABS_F64, &[arg.into()]), NumSqrtUnchecked => env.call_intrinsic(LLVM_SQRT_F64, &[arg.into()]), + NumLogUnchecked => env.call_intrinsic(LLVM_LOG_F64, &[arg.into()]), NumRound => env.call_intrinsic(LLVM_LROUND_I64_F64, &[arg.into()]), NumSin => env.call_intrinsic(LLVM_SIN_F64, &[arg.into()]), NumCos => env.call_intrinsic(LLVM_COS_F64, &[arg.into()]), diff --git a/compiler/gen/src/llvm/build_dict.rs b/compiler/gen/src/llvm/build_dict.rs index 61b69c7009..105dc32643 100644 --- a/compiler/gen/src/llvm/build_dict.rs +++ b/compiler/gen/src/llvm/build_dict.rs @@ -717,11 +717,7 @@ pub fn dict_walk<'a, 'ctx, 'env>( env, layout_ids, stepper_layout, - &[ - key_layout.clone(), - value_layout.clone(), - accum_layout.clone(), - ], + &[*key_layout, *value_layout, *accum_layout], ) .as_global_value() .as_pointer_value(); diff --git a/compiler/gen/src/llvm/build_hash.rs b/compiler/gen/src/llvm/build_hash.rs index 0da80d7fc6..3b33bf20d3 100644 --- a/compiler/gen/src/llvm/build_hash.rs +++ b/compiler/gen/src/llvm/build_hash.rs @@ -70,7 +70,7 @@ fn build_hash_layout<'a, 'ctx, 'env>( unreachable!("recursion pointers should never be hashed directly") } WhenRecursive::Loop(union_layout) => { - let layout = Layout::Union(union_layout.clone()); + let layout = Layout::Union(union_layout); let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes); @@ -287,7 +287,7 @@ fn hash_struct<'a, 'ctx, 'env>( unreachable!("The current layout should not be recursive, but is") } WhenRecursive::Loop(union_layout) => { - let field_layout = Layout::Union(union_layout.clone()); + let field_layout = Layout::Union(*union_layout); let bt = basic_type_from_layout( env.arena, @@ -811,7 +811,7 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>( env, layout_ids, field_layouts, - WhenRecursive::Loop(union_layout.clone()), + WhenRecursive::Loop(*union_layout), seed, struct_value, ) diff --git a/compiler/gen/src/llvm/build_list.rs b/compiler/gen/src/llvm/build_list.rs index fb3232e0b8..40321129db 100644 --- a/compiler/gen/src/llvm/build_list.rs +++ b/compiler/gen/src/llvm/build_list.rs @@ -423,7 +423,7 @@ pub fn list_reverse<'a, 'ctx, 'env>( let ctx = env.context; let wrapper_struct = list.into_struct_value(); - let (input_inplace, element_layout) = match list_layout.clone() { + let (input_inplace, element_layout) = match *list_layout { Layout::Builtin(Builtin::EmptyList) => ( InPlace::InPlace, // this pointer will never actually be dereferenced @@ -434,7 +434,7 @@ pub fn list_reverse<'a, 'ctx, 'env>( MemoryMode::Unique => InPlace::InPlace, MemoryMode::Refcounted => InPlace::Clone, }, - elem_layout.clone(), + *elem_layout, ), _ => unreachable!("Invalid layout {:?} in List.reverse", list_layout), @@ -788,6 +788,81 @@ pub fn list_sum<'a, 'ctx, 'env>( builder.build_load(accum_alloca, "load_final_acum") } +/// List.product : List (Num a) -> Num a +pub fn list_product<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + list: BasicValueEnum<'ctx>, + default_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let ctx = env.context; + let builder = env.builder; + + let list_wrapper = list.into_struct_value(); + let len = list_len(env.builder, list_wrapper); + + let accum_type = basic_type_from_layout(env.arena, ctx, default_layout, env.ptr_bytes); + let accum_alloca = builder.build_alloca(accum_type, "alloca_walk_right_accum"); + + let default: BasicValueEnum = match accum_type { + BasicTypeEnum::IntType(int_type) => int_type.const_int(1, false).into(), + BasicTypeEnum::FloatType(float_type) => float_type.const_float(1.0).into(), + _ => unreachable!(""), + }; + + builder.build_store(accum_alloca, default); + + let then_block = ctx.append_basic_block(parent, "then"); + let cont_block = ctx.append_basic_block(parent, "branchcont"); + + let condition = builder.build_int_compare( + IntPredicate::UGT, + len, + ctx.i64_type().const_zero(), + "list_non_empty", + ); + + builder.build_conditional_branch(condition, then_block, cont_block); + + builder.position_at_end(then_block); + + let elem_ptr_type = get_ptr_type(&accum_type, AddressSpace::Generic); + let list_ptr = load_list_ptr(builder, list_wrapper, elem_ptr_type); + + let walk_right_loop = |_, elem: BasicValueEnum<'ctx>| { + // load current accumulator + let current = builder.build_load(accum_alloca, "retrieve_accum"); + + let new_current = build_num_binop( + env, + parent, + current, + default_layout, + elem, + default_layout, + roc_module::low_level::LowLevel::NumMul, + ); + + builder.build_store(accum_alloca, new_current); + }; + + incrementing_elem_loop( + builder, + ctx, + parent, + list_ptr, + len, + "#index", + walk_right_loop, + ); + + builder.build_unconditional_branch(cont_block); + + builder.position_at_end(cont_block); + + builder.build_load(accum_alloca, "load_final_acum") +} + /// List.walk : List elem, (elem -> accum -> accum), accum -> accum pub fn list_walk<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -868,7 +943,7 @@ fn list_walk_generic<'a, 'ctx, 'env>( env, layout_ids, func_layout, - &[element_layout.clone(), default_layout.clone()], + &[*element_layout, *default_layout], ) .as_global_value() .as_pointer_value(); @@ -959,7 +1034,7 @@ pub fn list_keep_if<'a, 'ctx, 'env>( env.builder.build_store(transform_ptr, transform); let stepper_caller = - build_transform_caller(env, layout_ids, transform_layout, &[element_layout.clone()]) + build_transform_caller(env, layout_ids, transform_layout, &[*element_layout]) .as_global_value() .as_pointer_value(); @@ -1066,7 +1141,7 @@ pub fn list_keep_result<'a, 'ctx, 'env>( env.builder.build_store(transform_ptr, transform); let stepper_caller = - build_transform_caller(env, layout_ids, transform_layout, &[before_layout.clone()]) + build_transform_caller(env, layout_ids, transform_layout, &[*before_layout]) .as_global_value() .as_pointer_value(); @@ -1130,7 +1205,7 @@ pub fn list_map<'a, 'ctx, 'env>( list, element_layout, bitcode::LIST_MAP, - &[element_layout.clone()], + &[*element_layout], ) } @@ -1151,7 +1226,7 @@ pub fn list_map_with_index<'a, 'ctx, 'env>( list, element_layout, bitcode::LIST_MAP_WITH_INDEX, - &[Layout::Builtin(Builtin::Usize), element_layout.clone()], + &[Layout::Builtin(Builtin::Usize), *element_layout], ) } @@ -1255,7 +1330,7 @@ pub fn list_map2<'a, 'ctx, 'env>( let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr"); env.builder.build_store(transform_ptr, transform); - let argument_layouts = [element1_layout.clone(), element2_layout.clone()]; + let argument_layouts = [*element1_layout, *element2_layout]; let stepper_caller = build_transform_caller(env, layout_ids, transform_layout, &argument_layouts) .as_global_value() @@ -1351,11 +1426,7 @@ pub fn list_map3<'a, 'ctx, 'env>( let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr"); env.builder.build_store(transform_ptr, transform); - let argument_layouts = [ - element1_layout.clone(), - element2_layout.clone(), - element3_layout.clone(), - ]; + let argument_layouts = [*element1_layout, *element2_layout, *element3_layout]; let stepper_caller = build_transform_caller(env, layout_ids, transform_layout, &argument_layouts) .as_global_value() diff --git a/compiler/gen/src/llvm/compare.rs b/compiler/gen/src/llvm/compare.rs index cd733b3bd1..02b2de1fbe 100644 --- a/compiler/gen/src/llvm/compare.rs +++ b/compiler/gen/src/llvm/compare.rs @@ -102,7 +102,7 @@ fn build_eq_builtin<'a, 'ctx, 'env>( Builtin::List(_, elem) => build_list_eq( env, layout_ids, - &Layout::Builtin(builtin.clone()), + &Layout::Builtin(*builtin), elem, lhs_val.into_struct_value(), rhs_val.into_struct_value(), @@ -170,7 +170,7 @@ fn build_eq<'a, 'ctx, 'env>( } WhenRecursive::Loop(union_layout) => { - let layout = Layout::Union(union_layout.clone()); + let layout = Layout::Union(union_layout); let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes); @@ -188,7 +188,7 @@ fn build_eq<'a, 'ctx, 'env>( build_tag_eq( env, layout_ids, - WhenRecursive::Loop(union_layout.clone()), + WhenRecursive::Loop(union_layout), &layout, &union_layout, field1_cast.into(), @@ -262,7 +262,7 @@ fn build_neq_builtin<'a, 'ctx, 'env>( let is_equal = build_list_eq( env, layout_ids, - &Layout::Builtin(builtin.clone()), + &Layout::Builtin(*builtin), elem, lhs_val.into_struct_value(), rhs_val.into_struct_value(), @@ -690,7 +690,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>( unreachable!("The current layout should not be recursive, but is") } WhenRecursive::Loop(union_layout) => { - let field_layout = Layout::Union(union_layout.clone()); + let field_layout = Layout::Union(*union_layout); let bt = basic_type_from_layout( env.arena, @@ -717,7 +717,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>( field2_cast.into(), &field_layout, &field_layout, - WhenRecursive::Loop(union_layout.clone()), + WhenRecursive::Loop(*union_layout), ) .into_int_value() } @@ -1234,7 +1234,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>( env, layout_ids, field_layouts, - WhenRecursive::Loop(union_layout.clone()), + WhenRecursive::Loop(*union_layout), struct1, struct2, ) diff --git a/compiler/gen/src/llvm/refcounting.rs b/compiler/gen/src/llvm/refcounting.rs index 4cd134818e..4e56cfe110 100644 --- a/compiler/gen/src/llvm/refcounting.rs +++ b/compiler/gen/src/llvm/refcounting.rs @@ -454,7 +454,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>( env, layout_ids, mode, - &WhenRecursive::Loop(variant.clone()), + &WhenRecursive::Loop(*variant), tags, value.into_pointer_value(), true, @@ -470,7 +470,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>( env, layout_ids, mode, - &WhenRecursive::Loop(variant.clone()), + &WhenRecursive::Loop(*variant), &*env.arena.alloc([other_fields]), value.into_pointer_value(), true, @@ -484,7 +484,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>( env, layout_ids, mode, - &WhenRecursive::Loop(variant.clone()), + &WhenRecursive::Loop(*variant), &*env.arena.alloc([*fields]), value.into_pointer_value(), true, @@ -497,7 +497,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>( env, layout_ids, mode, - &WhenRecursive::Loop(variant.clone()), + &WhenRecursive::Loop(*variant), tags, value.into_pointer_value(), false, @@ -549,7 +549,7 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>( unreachable!("recursion pointers should never be hashed directly") } WhenRecursive::Loop(union_layout) => { - let layout = Layout::Union(union_layout.clone()); + let layout = Layout::Union(*union_layout); let bt = basic_type_from_layout(env.arena, env.context, &layout, env.ptr_bytes); diff --git a/compiler/gen/src/run_roc.rs b/compiler/gen/src/run_roc.rs index f5e75c4b1a..ec25e4cae3 100644 --- a/compiler/gen/src/run_roc.rs +++ b/compiler/gen/src/run_roc.rs @@ -8,9 +8,9 @@ pub enum RocCallResult { Failure(*mut c_char), } -impl Into> for RocCallResult { - fn into(self) -> Result { - match self { +impl From> for Result { + fn from(call_result: RocCallResult) -> Self { + match call_result { Success(value) => Ok(value), Failure(failure) => Err({ let raw = unsafe { CString::from_raw(failure) }; diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 7b4c9a3e93..f7861bdc67 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -1,6 +1,6 @@ #![warn(clippy::all, clippy::dbg_macro)] // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. -#![allow(clippy::large_enum_variant)] +#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] use bumpalo::{collections::Vec, Bump}; use roc_builtins::bitcode; @@ -105,7 +105,7 @@ where } => { // for now, treat invoke as a normal call - let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass); + let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass); self.build_stmt(&stmt) } Stmt::Switch { @@ -252,32 +252,20 @@ where x => Err(format!("layout, {:?}, not implemented yet", x)), } } - LowLevel::NumAcos => self.build_fn_call( - sym, - bitcode::NUM_ACOS.to_string(), - args, - &[layout.clone()], - layout, - ), - LowLevel::NumAsin => self.build_fn_call( - sym, - bitcode::NUM_ASIN.to_string(), - args, - &[layout.clone()], - layout, - ), - LowLevel::NumAtan => self.build_fn_call( - sym, - bitcode::NUM_ATAN.to_string(), - args, - &[layout.clone()], - layout, - ), + LowLevel::NumAcos => { + self.build_fn_call(sym, bitcode::NUM_ACOS.to_string(), args, &[*layout], layout) + } + LowLevel::NumAsin => { + self.build_fn_call(sym, bitcode::NUM_ASIN.to_string(), args, &[*layout], layout) + } + LowLevel::NumAtan => { + self.build_fn_call(sym, bitcode::NUM_ATAN.to_string(), args, &[*layout], layout) + } LowLevel::NumPowInt => self.build_fn_call( sym, bitcode::NUM_POW_INT.to_string(), args, - &[layout.clone(), layout.clone()], + &[*layout, *layout], layout, ), LowLevel::NumSub => { @@ -472,7 +460,7 @@ where } => { // for now, treat invoke as a normal call - let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), layout.clone(), pass); + let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass); self.scan_ast(&stmt); } diff --git a/compiler/gen_dev/tests/gen_num.rs b/compiler/gen_dev/tests/gen_num.rs index a7a25cfa90..1c3f6d42b9 100644 --- a/compiler/gen_dev/tests/gen_num.rs +++ b/compiler/gen_dev/tests/gen_num.rs @@ -12,8 +12,6 @@ mod helpers; #[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] mod gen_num { - //use roc_std::RocOrder; - #[test] fn i64_values() { assert_evals_to!("0", 0, i64); diff --git a/compiler/gen_dev/tests/helpers/eval.rs b/compiler/gen_dev/tests/helpers/eval.rs index 6ef1f18267..3b490f7de9 100644 --- a/compiler/gen_dev/tests/helpers/eval.rs +++ b/compiler/gen_dev/tests/helpers/eval.rs @@ -5,6 +5,7 @@ use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; use tempfile::tempdir; +#[allow(dead_code)] fn promote_expr_to_module(src: &str) -> String { let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); @@ -18,6 +19,7 @@ fn promote_expr_to_module(src: &str) -> String { buffer } +#[allow(dead_code)] pub fn helper<'a>( arena: &'a bumpalo::Bump, src: &str, diff --git a/compiler/load/src/docs.rs b/compiler/load/src/docs.rs index b52d655cf4..f5c8bfc6ad 100644 --- a/compiler/load/src/docs.rs +++ b/compiler/load/src/docs.rs @@ -120,7 +120,7 @@ fn generate_module_doc<'a>( (acc, None) } - Body(_, _) | Nested(_) => (acc, None), + Body(_, _) => (acc, None), NotYetImplemented(s) => todo!("{}", s), } diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 9082abf113..842b23799e 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -784,6 +784,10 @@ enum Msg<'a> { }, FailedToParse(ParseProblem<'a, SyntaxError<'a>>), + FailedToReadFile { + filename: PathBuf, + error: io::ErrorKind, + }, } #[derive(Debug)] @@ -996,18 +1000,16 @@ pub enum LoadingProblem<'a> { FileProblem { filename: PathBuf, error: io::ErrorKind, - msg: &'static str, }, ParsingFailed(ParseProblem<'a, SyntaxError<'a>>), UnexpectedHeader(String), - /// there is no platform (likely running an Interface module) - NoPlatform(String), MsgChannelDied, ErrJoiningWorkerThreads, TriedToImportAppModule, - /// a formatted report of parsing failure - ParsingFailedReport(String), + + /// a formatted report + FormattedReport(String), } pub enum Phases { @@ -1399,6 +1401,14 @@ where Err(LoadingProblem::ParsingFailed(problem)) => { msg_tx.send(Msg::FailedToParse(problem)).unwrap(); } + Err(LoadingProblem::FileProblem { + filename, + error, + }) => { + msg_tx + .send(Msg::FailedToReadFile { filename, error }) + .unwrap(); + } Err(other) => { return Err(other); } @@ -1457,6 +1467,16 @@ where let worker_listeners = worker_listeners.into_bump_slice(); let msg_tx = msg_tx.clone(); + macro_rules! shut_down_worker_threads { + () => { + for listener in worker_listeners { + listener + .send(WorkerMsg::Shutdown) + .map_err(|_| LoadingProblem::MsgChannelDied)?; + } + }; + } + // The root module will have already queued up messages to process, // and processing those messages will in turn queue up more messages. for msg in msg_rx.iter() { @@ -1490,12 +1510,7 @@ where // We're done! There should be no more messages pending. debug_assert!(msg_rx.is_empty()); - // Shut down all the worker threads. - for listener in worker_listeners { - listener - .send(WorkerMsg::Shutdown) - .map_err(|_| LoadingProblem::MsgChannelDied)?; - } + shut_down_worker_threads!(); return Ok(LoadResult::Monomorphized(finish_specialization( state, @@ -1503,50 +1518,29 @@ where exposed_to_host, )?)); } + Msg::FailedToReadFile { filename, error } => { + shut_down_worker_threads!(); + + let buf = to_file_problem_report(&filename, error); + return Err(LoadingProblem::FormattedReport(buf)); + } + Msg::FailedToParse(problem) => { - // Shut down all the worker threads. - for listener in worker_listeners { - listener - .send(WorkerMsg::Shutdown) - .map_err(|_| LoadingProblem::MsgChannelDied)?; - } + shut_down_worker_threads!(); - use roc_reporting::report::{ - parse_problem, RocDocAllocator, DEFAULT_PALETTE, - }; - - // TODO this is not in fact safe - let src = unsafe { from_utf8_unchecked(problem.bytes) }; - let src_lines: Vec<&str> = src.split('\n').collect(); - - let palette = DEFAULT_PALETTE; - - let mut module_ids = Arc::try_unwrap(state.arc_modules) + let module_ids = Arc::try_unwrap(state.arc_modules) .unwrap_or_else(|_| { panic!("There were still outstanding Arc references to module_ids") }) .into_inner() .into_module_ids(); - let module_id = - module_ids.get_or_insert(&"find module name somehow?".into()); - - let interns = Interns { + let buf = to_parse_problem_report( + problem, module_ids, - all_ident_ids: state.constrained_ident_ids, - }; - - // Report parsing and canonicalization problems - let alloc = RocDocAllocator::new(&src_lines, module_id, &interns); - - let starting_line = 0; - let report = - parse_problem(&alloc, problem.filename.clone(), starting_line, problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - return Err(LoadingProblem::ParsingFailedReport(buf)); + state.constrained_ident_ids, + ); + return Err(LoadingProblem::FormattedReport(buf)); } msg => { // This is where most of the main thread's work gets done. @@ -1950,7 +1944,7 @@ fn update<'a>( }; for (layout, pend) in specs { - existing.insert(layout.clone(), pend.clone()); + existing.insert(*layout, pend.clone()); } } } @@ -2086,6 +2080,9 @@ fn update<'a>( Msg::FailedToParse(_) => { unreachable!(); } + Msg::FailedToReadFile { .. } => { + unreachable!(); + } } } @@ -2142,72 +2139,8 @@ fn finish_specialization( } Valid(To::NewPackage(p_or_p)) => p_or_p, other => { - use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE}; - use ven_pretty::DocAllocator; - - let module_id = state.root_id; - - let palette = DEFAULT_PALETTE; - - // Report parsing and canonicalization problems - let alloc = RocDocAllocator::new(&[], module_id, &interns); - - let report = { - match other { - Valid(_) => unreachable!(), - NotSpecified => { - let doc = alloc.stack(vec![ - alloc.reflow("I could not find a platform based on your input file."), - alloc.reflow(r"Does the module header contain an entry that looks like this:"), - alloc - .parser_suggestion(" packages { base: \"platform\" }") - .indent(4), - alloc.reflow("See also TODO."), - ]); - - Report { - filename: "UNKNOWN.roc".into(), - doc, - title: "NO PLATFORM".to_string(), - } - } - RootIsInterface => { - let doc = alloc.stack(vec![ - alloc.reflow(r"The input file is a interface file, but only app modules can be ran."), - alloc.concat(vec![ - alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"), - alloc.reflow(r"but won't output any executable."), - ]) - ]); - - Report { - filename: "UNKNOWN.roc".into(), - doc, - title: "NO PLATFORM".to_string(), - } - } - RootIsPkgConfig => { - let doc = alloc.stack(vec![ - alloc.reflow(r"The input file is a package config file, but only app modules can be ran."), - alloc.concat(vec![ - alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"), - alloc.reflow(r"but won't output any executable."), - ]) - ]); - - Report { - filename: "UNKNOWN.roc".into(), - doc, - title: "NO PLATFORM".to_string(), - } - } - } - }; - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - return Err(LoadingProblem::NoPlatform(buf)); + let buf = to_missing_platform_report(state.root_id, other); + return Err(LoadingProblem::FormattedReport(buf)); } }; @@ -2332,6 +2265,10 @@ fn load_pkg_config<'a>( ))) } Ok((ast::Module::Platform { header }, parser_state)) => { + let delta = bytes.len() - parser_state.bytes.len(); + let chomped = &bytes[..delta]; + let header_src = unsafe { std::str::from_utf8_unchecked(chomped) }; + // make a Pkg-Config module that ultimately exposes `main` to the host let pkg_config_module_msg = fabricate_pkg_config_module( arena, @@ -2342,6 +2279,7 @@ fn load_pkg_config<'a>( module_ids.clone(), ident_ids_by_module.clone(), &header, + header_src, pkg_module_timing, ) .1; @@ -2360,7 +2298,7 @@ fn load_pkg_config<'a>( Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg])) } Err(fail) => Err(LoadingProblem::ParsingFailed( - SyntaxError::Header(fail).into_parse_problem(filename, bytes), + SyntaxError::Header(fail).into_parse_problem(filename, "", bytes), )), } } @@ -2368,7 +2306,6 @@ fn load_pkg_config<'a>( Err(err) => Err(LoadingProblem::FileProblem { filename, error: err.kind(), - msg: "while reading a Pkg-Config.roc file", }), } } @@ -2633,7 +2570,7 @@ fn parse_header<'a>( module_timing, )), Err(fail) => Err(LoadingProblem::ParsingFailed( - SyntaxError::Header(fail).into_parse_problem(filename, src_bytes), + SyntaxError::Header(fail).into_parse_problem(filename, "", src_bytes), )), } } @@ -2670,7 +2607,6 @@ fn load_filename<'a>( Err(err) => Err(LoadingProblem::FileProblem { filename, error: err.kind(), - msg: "in `load_filename`", }), } } @@ -2939,18 +2875,24 @@ fn send_header<'a>( ) } -// TODO refactor so more logic is shared with `send_header` -#[allow(clippy::too_many_arguments)] -fn send_header_two<'a>( - arena: &'a Bump, +#[derive(Debug)] +struct PlatformHeaderInfo<'a> { filename: PathBuf, is_root_module: bool, shorthand: &'a str, + header_src: &'a str, app_module_id: ModuleId, packages: &'a [Located>], provides: &'a [Located>], requires: &'a [Located>], imports: &'a [Located>], +} + +// TODO refactor so more logic is shared with `send_header` +#[allow(clippy::too_many_arguments)] +fn send_header_two<'a>( + arena: &'a Bump, + info: PlatformHeaderInfo<'a>, parse_state: parser::State<'a>, module_ids: Arc>>, ident_ids_by_module: Arc>>, @@ -2958,6 +2900,18 @@ fn send_header_two<'a>( ) -> (ModuleId, Msg<'a>) { use inlinable_string::InlinableString; + let PlatformHeaderInfo { + filename, + shorthand, + is_root_module, + header_src, + app_module_id, + packages, + provides, + requires, + imports, + } = info; + let declared_name: InlinableString = "".into(); let mut imported: Vec<(QualifiedModuleName, Vec, Region)> = @@ -3149,7 +3103,7 @@ fn send_header_two<'a>( package_qualified_imported_modules, deps_by_name, exposes: exposed, - header_src: "#builtin effect header", + header_src, parse_state, exposed_imports: scope, module_timing, @@ -3278,21 +3232,27 @@ fn fabricate_pkg_config_module<'a>( module_ids: Arc>>, ident_ids_by_module: Arc>>, header: &PlatformHeader<'a>, + header_src: &'a str, module_timing: ModuleTiming, ) -> (ModuleId, Msg<'a>) { let provides: &'a [Located>] = header.provides.clone().into_bump_slice(); + let info = PlatformHeaderInfo { + filename, + is_root_module: false, + shorthand, + header_src, + app_module_id, + packages: &[], + provides, + requires: header.requires.clone().into_bump_slice(), + imports: header.imports.clone().into_bump_slice(), + }; + send_header_two( arena, - filename, - false, - shorthand, - app_module_id, - &[], - provides, - header.requires.clone().into_bump_slice(), - header.imports.clone().into_bump_slice(), + info, parse_state, module_ids, ident_ids_by_module, @@ -3683,9 +3643,11 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result, Loadi let parsed_defs = match module_defs().parse(&arena, parse_state) { Ok((_, success, _state)) => success, Err((_, fail, _)) => { - return Err(LoadingProblem::ParsingFailed( - fail.into_parse_problem(header.module_path, source), - )); + return Err(LoadingProblem::ParsingFailed(fail.into_parse_problem( + header.module_path, + header.header_src, + source, + ))); } }; @@ -3811,12 +3773,7 @@ fn make_specializations<'a>( // TODO: for now this final specialization pass is sequential, // with no parallelization at all. We should try to parallelize // this, but doing so will require a redesign of Procs. - procs = roc_mono::ir::specialize_all( - &mut mono_env, - procs, - &mut layout_cache, - // &finished_info.vars_by_symbol, - ); + procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache); let external_specializations_requested = procs.externals_we_need.clone(); let procedures = procs.get_specialized_procs_without_rc(mono_env.arena); @@ -4180,3 +4137,179 @@ where Ok(()) } + +fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String { + use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE}; + use ven_pretty::DocAllocator; + + let src_lines: Vec<&str> = Vec::new(); + + let mut module_ids = ModuleIds::default(); + + let module_id = module_ids.get_or_insert(&"find module name somehow?".into()); + + let interns = Interns::default(); + + // Report parsing and canonicalization problems + let alloc = RocDocAllocator::new(&src_lines, module_id, &interns); + + let report = match error { + io::ErrorKind::NotFound => { + let doc = alloc.stack(vec![ + alloc.reflow(r"I am looking for this file, but it's not there:"), + alloc + .parser_suggestion(filename.to_str().unwrap()) + .indent(4), + alloc.concat(vec![ + alloc.reflow(r"Is the file supposed to be there? "), + alloc.reflow("Maybe there is a typo in the file name?"), + ]), + ]); + + Report { + filename: "UNKNOWN.roc".into(), + doc, + title: "FILE NOT FOUND".to_string(), + } + } + io::ErrorKind::PermissionDenied => { + let doc = alloc.stack(vec![ + alloc.reflow(r"I don't have the required permissions to read this file:"), + alloc + .parser_suggestion(filename.to_str().unwrap()) + .indent(4), + alloc.concat(vec![ + alloc.reflow(r"Is it the right file? Maybe change its permissions?") + ]), + ]); + + Report { + filename: "UNKNOWN.roc".into(), + doc, + title: "PERMISSION DENIED".to_string(), + } + } + _ => { + let error = std::io::Error::from(error); + let formatted = format!("{}", error); + let doc = alloc.concat(vec![ + alloc.reflow(r"I tried to read this file, but ran into a "), + alloc.text(formatted), + alloc.reflow(r" problem."), + ]); + + Report { + filename: "UNKNOWN.roc".into(), + doc, + title: "FILE PROBLEM".to_string(), + } + } + }; + + let mut buf = String::new(); + let palette = DEFAULT_PALETTE; + report.render_color_terminal(&mut buf, &alloc, &palette); + + buf +} + +fn to_parse_problem_report<'a>( + problem: ParseProblem<'a, SyntaxError<'a>>, + mut module_ids: ModuleIds, + all_ident_ids: MutMap, +) -> String { + use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE}; + + // TODO this is not in fact safe + let src = unsafe { from_utf8_unchecked(problem.bytes) }; + let mut src_lines: Vec<&str> = problem.prefix.lines().collect(); + src_lines.extend(src.lines().skip(1)); + + let module_id = module_ids.get_or_insert(&"find module name somehow?".into()); + + let interns = Interns { + module_ids, + all_ident_ids, + }; + + // Report parsing and canonicalization problems + let alloc = RocDocAllocator::new(&src_lines, module_id, &interns); + + let starting_line = 0; + let report = parse_problem(&alloc, problem.filename.clone(), starting_line, problem); + + let mut buf = String::new(); + let palette = DEFAULT_PALETTE; + + report.render_color_terminal(&mut buf, &alloc, &palette); + + buf +} + +fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> String { + use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE}; + use ven_pretty::DocAllocator; + use PlatformPath::*; + + // Report parsing and canonicalization problems + let interns = Interns::default(); + let alloc = RocDocAllocator::new(&[], module_id, &interns); + + let report = { + match other { + Valid(_) => unreachable!(), + NotSpecified => { + let doc = alloc.stack(vec![ + alloc.reflow("I could not find a platform based on your input file."), + alloc.reflow(r"Does the module header contain an entry that looks like this:"), + alloc + .parser_suggestion(" packages { base: \"platform\" }") + .indent(4), + alloc.reflow("See also TODO."), + ]); + + Report { + filename: "UNKNOWN.roc".into(), + doc, + title: "NO PLATFORM".to_string(), + } + } + RootIsInterface => { + let doc = alloc.stack(vec![ + alloc.reflow(r"The input file is a interface file, but only app modules can be ran."), + alloc.concat(vec![ + alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"), + alloc.reflow(r"but won't output any executable."), + ]) + ]); + + Report { + filename: "UNKNOWN.roc".into(), + doc, + title: "NO PLATFORM".to_string(), + } + } + RootIsPkgConfig => { + let doc = alloc.stack(vec![ + alloc.reflow(r"The input file is a package config file, but only app modules can be ran."), + alloc.concat(vec![ + alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"), + alloc.reflow(r"but won't output any executable."), + ]) + ]); + + Report { + filename: "UNKNOWN.roc".into(), + doc, + title: "NO PLATFORM".to_string(), + } + } + } + }; + + let palette = DEFAULT_PALETTE; + let mut buf = String::new(); + report.render_color_terminal(&mut buf, &alloc, &palette); + + buf +} diff --git a/compiler/load/tests/fixtures/build/no_deps/MissingDep.roc b/compiler/load/tests/fixtures/build/no_deps/MissingDep.roc new file mode 100644 index 0000000000..c67fcb7403 --- /dev/null +++ b/compiler/load/tests/fixtures/build/no_deps/MissingDep.roc @@ -0,0 +1,8 @@ +interface MissingDep + exposes [ unit ] + imports [ ThisFileIsMissing ] + +Unit : [ Unit ] + +unit : Unit +unit = Unit diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 5eff2b2a9d..71ca201ef0 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -32,87 +32,94 @@ mod test_load { // HELPERS - fn multiple_modules(files: Vec<(&str, &str)>) -> LoadedModule { - multiple_modules_help(files).unwrap() + fn multiple_modules(files: Vec<(&str, &str)>) -> Result { + use roc_load::file::LoadingProblem; + + let arena = Bump::new(); + let arena = &arena; + + match multiple_modules_help(arena, files) { + Err(io_error) => panic!("IO trouble: {:?}", io_error), + Ok(Err(LoadingProblem::FormattedReport(buf))) => Err(buf), + Ok(Err(loading_problem)) => Err(format!("{:?}", loading_problem)), + Ok(Ok(mut loaded_module)) => { + let home = loaded_module.module_id; + + assert_eq!( + loaded_module.can_problems.remove(&home).unwrap_or_default(), + Vec::new() + ); + assert_eq!( + loaded_module + .type_problems + .remove(&home) + .unwrap_or_default(), + Vec::new() + ); + + Ok(loaded_module) + } + } } - fn multiple_modules_help(mut files: Vec<(&str, &str)>) -> Result { + fn multiple_modules_help<'a>( + arena: &'a Bump, + mut files: Vec<(&str, &str)>, + ) -> Result>, std::io::Error> { use std::fs::File; use std::io::Write; use std::path::PathBuf; use tempfile::tempdir; - let arena = Bump::new(); - let arena = &arena; - let stdlib = roc_builtins::std::standard_stdlib(); let mut file_handles: Vec<_> = Vec::new(); let exposed_types = MutMap::default(); - let loaded = { - // create a temporary directory - let dir = tempdir()?; - let app_module = files.pop().unwrap(); - let interfaces = files; + // create a temporary directory + let dir = tempdir()?; - debug_assert!( - app_module.1.starts_with("app"), - "The final module should be the application module" - ); + let app_module = files.pop().unwrap(); + let interfaces = files; - for (name, source) in interfaces { - let mut filename = PathBuf::from(name); - filename.set_extension("roc"); - let file_path = dir.path().join(filename.clone()); - let mut file = File::create(file_path)?; - writeln!(file, "{}", source)?; - file_handles.push(file); - } + debug_assert!( + app_module.1.starts_with("app"), + "The final module should be the application module" + ); - let result = { - let (name, source) = app_module; + for (name, source) in interfaces { + let mut filename = PathBuf::from(name); + filename.set_extension("roc"); + let file_path = dir.path().join(filename.clone()); + let mut file = File::create(file_path)?; + writeln!(file, "{}", source)?; + file_handles.push(file); + } - let filename = PathBuf::from(name); - let file_path = dir.path().join(filename); - let full_file_path = file_path.clone(); - let mut file = File::create(file_path)?; - writeln!(file, "{}", source)?; - file_handles.push(file); + let result = { + let (name, source) = app_module; - roc_load::file::load_and_typecheck( - arena, - full_file_path, - &stdlib, - dir.path(), - exposed_types, - 8, - builtin_defs_map, - ) - }; + let filename = PathBuf::from(name); + let file_path = dir.path().join(filename); + let full_file_path = file_path.clone(); + let mut file = File::create(file_path)?; + writeln!(file, "{}", source)?; + file_handles.push(file); - dir.close()?; - - result + roc_load::file::load_and_typecheck( + arena, + full_file_path, + arena.alloc(stdlib), + dir.path(), + exposed_types, + 8, + builtin_defs_map, + ) }; - let mut loaded_module = loaded.expect("failed to load module"); + dir.close()?; - let home = loaded_module.module_id; - - assert_eq!( - loaded_module.can_problems.remove(&home).unwrap_or_default(), - Vec::new() - ); - assert_eq!( - loaded_module - .type_problems - .remove(&home) - .unwrap_or_default(), - Vec::new() - ); - - Ok(loaded_module) + Ok(result) } fn load_fixture( @@ -134,9 +141,9 @@ mod test_load { ); let mut loaded_module = match loaded { Ok(x) => x, - Err(roc_load::file::LoadingProblem::ParsingFailedReport(report)) => { + Err(roc_load::file::LoadingProblem::FormattedReport(report)) => { println!("{}", report); - panic!(); + panic!("{}", report); } Err(e) => panic!("{:?}", e), }; @@ -285,7 +292,8 @@ mod test_load { ), ), ]; - multiple_modules(modules); + + assert!(multiple_modules(modules).is_ok()); } #[test] @@ -517,61 +525,69 @@ mod test_load { ); } - // #[test] - // fn load_records() { - // use roc::types::{ErrorType, Mismatch, Problem, TypeExt}; + #[test] + fn parse_problem() { + let modules = vec![( + "Main", + indoc!( + r#" + app "test-app" packages { blah: "./blah" } provides [ main ] to blah - // let subs_by_module = MutMap::default(); - // let loaded_module = - // load_fixture("interface_with_deps", "Records", subs_by_module); + main = [ + "# + ), + )]; - // // NOTE: `a` here is unconstrained, so unifies with - // let expected_types = hashmap! { - // "Records.intVal" => "a", - // }; + match multiple_modules(modules) { + Err(report) => assert_eq!( + report, + indoc!( + " + \u{1b}[36m── UNFINISHED LIST ─────────────────────────────────────────────────────────────\u{1b}[0m + + I cannot find the end of this list: - // let a = ErrorType::FlexVar("a".into()); + \u{1b}[36m3\u{1b}[0m\u{1b}[36m│\u{1b}[0m \u{1b}[37mmain = [\u{1b}[0m + \u{1b}[31m^\u{1b}[0m - // let mut record = SendMap::default(); - // record.insert("x".into(), a); + You could change it to something like \u{1b}[33m[ 1, 2, 3 ]\u{1b}[0m or even just \u{1b}[33m[]\u{1b}[0m. + Anything where there is an open and a close square bracket, and where + the elements of the list are separated by commas. - // let problem = Problem::Mismatch( - // Mismatch::TypeMismatch, - // ErrorType::Record(SendMap::default(), TypeExt::Closed), - // ErrorType::Record(record, TypeExt::FlexOpen("b".into())), - // ); + \u{1b}[4mNote\u{1b}[0m: I may be confused by indentation" + ) + ), + Ok(_) => unreachable!("we expect failure here"), + } + } - // assert_eq!(loaded_module.problems, vec![problem]); - // assert_eq!(expected_types.len(), loaded_module.declarations.len()); + #[test] + #[should_panic( + expected = "FileProblem { filename: \"tests/fixtures/build/interface_with_deps/invalid$name.roc\", error: NotFound }" + )] + fn file_not_found() { + let subs_by_module = MutMap::default(); + let loaded_module = load_fixture("interface_with_deps", "invalid$name", subs_by_module); - // let mut subs = loaded_module.solved.into_inner(); + expect_types( + loaded_module, + hashmap! { + "str" => "Str", + }, + ); + } - // for decl in loaded_module.declarations { - // let def = match decl { - // Declare(def) => def, - // rec_decl @ DeclareRec(_) => { - // panic!( - // "Unexpected recursive def in module declarations: {:?}", - // rec_decl - // ); - // } - // cycle @ InvalidCycle(_, _) => { - // panic!("Unexpected cyclic def in module declarations: {:?}", cycle); - // } - // }; + #[test] + #[should_panic(expected = "FILE NOT FOUND")] + fn imported_file_not_found() { + let subs_by_module = MutMap::default(); + let loaded_module = load_fixture("no_deps", "MissingDep", subs_by_module); - // for (symbol, expr_var) in def.pattern_vars { - // let content = subs.get(expr_var).content; - - // name_all_type_vars(expr_var, &mut subs); - - // let actual_str = content_to_string(content, &mut subs); - // let expected_type = expected_types.get(symbol.as_str()).unwrap_or_else(|| { - // panic!("Defs included an unexpected symbol: {:?}", symbol) - // }); - - // assert_eq!((&symbol, expected_type), (&symbol, &actual_str.as_str())); - // } - // } - // } + expect_types( + loaded_module, + hashmap! { + "str" => "Str", + }, + ); + } } diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 48cacc84fe..16e38aabb6 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -117,21 +117,21 @@ impl From for ModuleName { } } -impl Into for ModuleName { - fn into(self) -> InlinableString { - self.0 +impl From for InlinableString { + fn from(name: ModuleName) -> Self { + name.0 } } -impl<'a> Into<&'a InlinableString> for &'a ModuleName { - fn into(self) -> &'a InlinableString { - &self.0 +impl<'a> From<&'a ModuleName> for &'a InlinableString { + fn from(name: &'a ModuleName) -> Self { + &name.0 } } -impl<'a> Into> for ModuleName { - fn into(self) -> Box { - self.0.to_string().into() +impl From for Box { + fn from(name: ModuleName) -> Self { + name.0.to_string().into() } } @@ -197,9 +197,9 @@ impl<'a> From for Lowercase { } } -impl Into for Lowercase { - fn into(self) -> InlinableString { - self.0 +impl From for InlinableString { + fn from(lowercase: Lowercase) -> Self { + lowercase.0 } } @@ -234,21 +234,21 @@ impl From for Ident { } } -impl Into for Ident { - fn into(self) -> InlinableString { - self.0 +impl From for InlinableString { + fn from(ident: Ident) -> Self { + ident.0 } } -impl<'a> Into<&'a InlinableString> for &'a Ident { - fn into(self) -> &'a InlinableString { - &self.0 +impl<'a> From<&'a Ident> for &'a InlinableString { + fn from(ident: &'a Ident) -> Self { + &ident.0 } } -impl<'a> Into> for Ident { - fn into(self) -> Box { - self.0.to_string().into() +impl From for Box { + fn from(ident: Ident) -> Self { + ident.0.to_string().into() } } diff --git a/compiler/module/src/lib.rs b/compiler/module/src/lib.rs index 50d5d356a4..2f88540f2d 100644 --- a/compiler/module/src/lib.rs +++ b/compiler/module/src/lib.rs @@ -1,6 +1,6 @@ #![warn(clippy::all, clippy::dbg_macro)] // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. -#![allow(clippy::large_enum_variant)] +#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] pub mod ident; pub mod low_level; diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index a84ce73ea3..3922bb8e6d 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -34,6 +34,7 @@ pub enum LowLevel { ListWalk, ListWalkBackwards, ListSum, + ListProduct, ListKeepOks, ListKeepErrs, DictSize, @@ -71,6 +72,7 @@ pub enum LowLevel { NumSin, NumCos, NumSqrtUnchecked, + NumLogUnchecked, NumRound, NumToFloat, NumPow, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 3320c3b40b..85806a99c5 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -804,59 +804,60 @@ define_builtins! { 43 NUM_MOD_INT: "modInt" 44 NUM_MOD_FLOAT: "modFloat" 45 NUM_SQRT: "sqrt" - 46 NUM_ROUND: "round" - 47 NUM_COMPARE: "compare" - 48 NUM_POW: "pow" - 49 NUM_CEILING: "ceiling" - 50 NUM_POW_INT: "powInt" - 51 NUM_FLOOR: "floor" - 52 NUM_ADD_WRAP: "addWrap" - 53 NUM_ADD_CHECKED: "addChecked" - 54 NUM_ATAN: "atan" - 55 NUM_ACOS: "acos" - 56 NUM_ASIN: "asin" - 57 NUM_AT_SIGNED128: "@Signed128" - 58 NUM_SIGNED128: "Signed128" imported - 59 NUM_AT_SIGNED64: "@Signed64" - 60 NUM_SIGNED64: "Signed64" imported - 61 NUM_AT_SIGNED32: "@Signed32" - 62 NUM_SIGNED32: "Signed32" imported - 63 NUM_AT_SIGNED16: "@Signed16" - 64 NUM_SIGNED16: "Signed16" imported - 65 NUM_AT_SIGNED8: "@Signed8" - 66 NUM_SIGNED8: "Signed8" imported - 67 NUM_AT_UNSIGNED128: "@Unsigned128" - 68 NUM_UNSIGNED128: "Unsigned128" imported - 69 NUM_AT_UNSIGNED64: "@Unsigned64" - 70 NUM_UNSIGNED64: "Unsigned64" imported - 71 NUM_AT_UNSIGNED32: "@Unsigned32" - 72 NUM_UNSIGNED32: "Unsigned32" imported - 73 NUM_AT_UNSIGNED16: "@Unsigned16" - 74 NUM_UNSIGNED16: "Unsigned16" imported - 75 NUM_AT_UNSIGNED8: "@Unsigned8" - 76 NUM_UNSIGNED8: "Unsigned8" imported - 77 NUM_AT_BINARY64: "@Binary64" - 78 NUM_BINARY64: "Binary64" imported - 79 NUM_AT_BINARY32: "@Binary32" - 80 NUM_BINARY32: "Binary32" imported - 81 NUM_BITWISE_AND: "bitwiseAnd" - 82 NUM_BITWISE_XOR: "bitwiseXor" - 83 NUM_BITWISE_OR: "bitwiseOr" - 84 NUM_SHIFT_LEFT: "shiftLeftBy" - 85 NUM_SHIFT_RIGHT: "shiftRightBy" - 86 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" - 87 NUM_SUB_WRAP: "subWrap" - 88 NUM_SUB_CHECKED: "subChecked" - 89 NUM_MUL_WRAP: "mulWrap" - 90 NUM_MUL_CHECKED: "mulChecked" - 91 NUM_INT: "Int" imported - 92 NUM_FLOAT: "Float" imported - 93 NUM_AT_NATURAL: "@Natural" - 94 NUM_NATURAL: "Natural" imported - 95 NUM_NAT: "Nat" imported - 96 NUM_INT_CAST: "intCast" - 97 NUM_MAX_I128: "maxI128" - 98 NUM_IS_MULTIPLE_OF: "isMultipleOf" + 46 NUM_LOG: "log" + 47 NUM_ROUND: "round" + 48 NUM_COMPARE: "compare" + 49 NUM_POW: "pow" + 50 NUM_CEILING: "ceiling" + 51 NUM_POW_INT: "powInt" + 52 NUM_FLOOR: "floor" + 53 NUM_ADD_WRAP: "addWrap" + 54 NUM_ADD_CHECKED: "addChecked" + 55 NUM_ATAN: "atan" + 56 NUM_ACOS: "acos" + 57 NUM_ASIN: "asin" + 58 NUM_AT_SIGNED128: "@Signed128" + 59 NUM_SIGNED128: "Signed128" imported + 60 NUM_AT_SIGNED64: "@Signed64" + 61 NUM_SIGNED64: "Signed64" imported + 62 NUM_AT_SIGNED32: "@Signed32" + 63 NUM_SIGNED32: "Signed32" imported + 64 NUM_AT_SIGNED16: "@Signed16" + 65 NUM_SIGNED16: "Signed16" imported + 66 NUM_AT_SIGNED8: "@Signed8" + 67 NUM_SIGNED8: "Signed8" imported + 68 NUM_AT_UNSIGNED128: "@Unsigned128" + 69 NUM_UNSIGNED128: "Unsigned128" imported + 70 NUM_AT_UNSIGNED64: "@Unsigned64" + 71 NUM_UNSIGNED64: "Unsigned64" imported + 72 NUM_AT_UNSIGNED32: "@Unsigned32" + 73 NUM_UNSIGNED32: "Unsigned32" imported + 74 NUM_AT_UNSIGNED16: "@Unsigned16" + 75 NUM_UNSIGNED16: "Unsigned16" imported + 76 NUM_AT_UNSIGNED8: "@Unsigned8" + 77 NUM_UNSIGNED8: "Unsigned8" imported + 78 NUM_AT_BINARY64: "@Binary64" + 79 NUM_BINARY64: "Binary64" imported + 80 NUM_AT_BINARY32: "@Binary32" + 81 NUM_BINARY32: "Binary32" imported + 82 NUM_BITWISE_AND: "bitwiseAnd" + 83 NUM_BITWISE_XOR: "bitwiseXor" + 84 NUM_BITWISE_OR: "bitwiseOr" + 85 NUM_SHIFT_LEFT: "shiftLeftBy" + 86 NUM_SHIFT_RIGHT: "shiftRightBy" + 87 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy" + 88 NUM_SUB_WRAP: "subWrap" + 89 NUM_SUB_CHECKED: "subChecked" + 90 NUM_MUL_WRAP: "mulWrap" + 91 NUM_MUL_CHECKED: "mulChecked" + 92 NUM_INT: "Int" imported + 93 NUM_FLOAT: "Float" imported + 94 NUM_AT_NATURAL: "@Natural" + 95 NUM_NATURAL: "Natural" imported + 96 NUM_NAT: "Nat" imported + 97 NUM_INT_CAST: "intCast" + 98 NUM_MAX_I128: "maxI128" + 99 NUM_IS_MULTIPLE_OF: "isMultipleOf" } 2 BOOL: "Bool" => { @@ -913,6 +914,7 @@ define_builtins! { 23 LIST_MAP_WITH_INDEX: "mapWithIndex" 24 LIST_MAP2: "map2" 25 LIST_MAP3: "map3" + 26 LIST_PRODUCT: "product" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 7c12ff969e..f48c787194 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -22,7 +22,7 @@ pub fn infer_borrow<'a>( }; for (key, proc) in procs { - param_map.visit_proc(arena, proc, key.clone()); + param_map.visit_proc(arena, proc, *key); } let mut env = BorrowInfState { @@ -47,7 +47,7 @@ pub fn infer_borrow<'a>( // mutually recursive functions (or just make all their arguments owned) for (key, proc) in procs { - env.collect_proc(proc, key.1.clone()); + env.collect_proc(proc, key.1); } if !env.modified { @@ -113,7 +113,7 @@ impl<'a> ParamMap<'a> { Vec::from_iter_in( ps.iter().map(|p| Param { borrow: p.layout.is_refcounted(), - layout: p.layout.clone(), + layout: p.layout, symbol: p.symbol, }), arena, @@ -125,7 +125,7 @@ impl<'a> ParamMap<'a> { Vec::from_iter_in( ps.iter().map(|(layout, symbol)| Param { borrow: should_borrow_layout(layout), - layout: layout.clone(), + layout: *layout, symbol: *symbol, }), arena, @@ -140,7 +140,7 @@ impl<'a> ParamMap<'a> { Vec::from_iter_in( ps.iter().map(|(layout, symbol)| Param { borrow: false, - layout: layout.clone(), + layout: *layout, symbol: *symbol, }), arena, @@ -367,7 +367,7 @@ impl<'a> BorrowInfState<'a> { name, full_layout, .. } => { // get the borrow signature of the applied function - match self.param_map.get_symbol(*name, full_layout.clone()) { + match self.param_map.get_symbol(*name, *full_layout) { Some(ps) => { // the return value will be owned self.own_var(z); @@ -499,7 +499,7 @@ impl<'a> BorrowInfState<'a> { if self.current_proc == *g && x == *z { // anonymous functions (for which the ps may not be known) // can never be tail-recursive, so this is fine - if let Some(ps) = self.param_map.get_symbol(*g, full_layout.clone()) { + if let Some(ps) = self.param_map.get_symbol(*g, *full_layout) { self.own_params_using_args(ys, ps) } } @@ -541,14 +541,14 @@ impl<'a> BorrowInfState<'a> { Let(x, Expr::FunctionPointer(fsymbol, layout), _, b) => { // ensure that the function pointed to is in the param map - if let Some(params) = self.param_map.get_symbol(*fsymbol, layout.clone()) { + if let Some(params) = self.param_map.get_symbol(*fsymbol, *layout) { self.param_map .items - .insert(Key::Declaration(*x, layout.clone()), params); + .insert(Key::Declaration(*x, *layout), params); } self.collect_stmt(b); - self.preserve_tail_call(*x, &Expr::FunctionPointer(*fsymbol, layout.clone()), b); + self.preserve_tail_call(*x, &Expr::FunctionPointer(*fsymbol, *layout), b); } Let(x, v, _, b) => { @@ -657,7 +657,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListWalk => arena.alloc_slice_copy(&[owned, irrelevant, owned]), ListWalkBackwards => arena.alloc_slice_copy(&[owned, irrelevant, owned]), - ListSum => arena.alloc_slice_copy(&[borrowed]), + ListSum | ListProduct => arena.alloc_slice_copy(&[borrowed]), // TODO when we have lists with capacity (if ever) // List.append should own its first argument @@ -671,10 +671,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]), - NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor - | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin | NumIntCast => { - arena.alloc_slice_copy(&[irrelevant]) - } + NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound + | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin + | NumIntCast => arena.alloc_slice_copy(&[irrelevant]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]), StrFromUtf8 => arena.alloc_slice_copy(&[owned]), StrToBytes => arena.alloc_slice_copy(&[owned]), diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 729b27422e..d3bc2753d8 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -22,7 +22,7 @@ pub fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> Decision .into_iter() .map(|(guard, pattern, index)| Branch { goal: index, - patterns: vec![(Path::Empty, guard, pattern)], + patterns: vec![(Vec::new(), guard, pattern)], }) .collect(); @@ -52,7 +52,7 @@ impl<'a> Guard<'a> { pub enum DecisionTree<'a> { Match(Label), Decision { - path: Path, + path: Vec, edges: Vec<(Test<'a>, DecisionTree<'a>)>, default: Option>>, }, @@ -132,23 +132,12 @@ impl<'a> Hash for Test<'a> { } } -#[derive(Clone, Debug, PartialEq)] -pub enum Path { - Index { - index: u64, - tag_id: u8, - path: Box, - }, - Unbox(Box), - Empty, -} - // ACTUALLY BUILD DECISION TREES #[derive(Clone, Debug, PartialEq)] struct Branch<'a> { goal: Label, - patterns: Vec<(Path, Guard<'a>, Pattern<'a>)>, + patterns: Vec<(Vec, Guard<'a>, Pattern<'a>)>, } fn to_decision_tree(raw_branches: Vec) -> DecisionTree { @@ -172,13 +161,13 @@ fn to_decision_tree(raw_branches: Vec) -> DecisionTree { decision_tree.clone() } (_, None) => DecisionTree::Decision { - path, + path: path.clone(), edges: decision_edges, default: None, }, (None, Some(_)) => to_decision_tree(fallback), _ => DecisionTree::Decision { - path, + path: path.clone(), edges: decision_edges, default: Some(Box::new(to_decision_tree(fallback))), }, @@ -218,8 +207,8 @@ fn flatten_patterns(branch: Branch) -> Branch { } fn flatten<'a>( - path_pattern: (Path, Guard<'a>, Pattern<'a>), - path_patterns: &mut Vec<(Path, Guard<'a>, Pattern<'a>)>, + path_pattern: (Vec, Guard<'a>, Pattern<'a>), + path_patterns: &mut Vec<(Vec, Guard<'a>, Pattern<'a>)>, ) { match path_pattern.2 { Pattern::AppliedTag { @@ -241,8 +230,9 @@ fn flatten<'a>( // Theory: unbox doesn't have any value for us, because one-element tag unions // don't store the tag anyway. if arguments.len() == 1 { + // NOTE here elm will unbox, but we don't use that path_patterns.push(( - Path::Unbox(Box::new(path)), + path, path_pattern.1.clone(), Pattern::AppliedTag { union, @@ -254,13 +244,15 @@ fn flatten<'a>( )); } else { for (index, (arg_pattern, _)) in arguments.iter().enumerate() { + let mut new_path = path.clone(); + new_path.push(PathInstruction { + index: index as u64, + tag_id, + }); + flatten( ( - Path::Index { - index: index as u64, - tag_id, - path: Box::new(path.clone()), - }, + new_path, // same guard here? path_pattern.1.clone(), arg_pattern.clone(), @@ -283,7 +275,7 @@ fn flatten<'a>( /// path. If that is the case we give the resulting label and a mapping from free /// variables to "how to get their value". So a pattern like (Just (x,_)) will give /// us something like ("x" => value.0.0) -fn check_for_match(branches: &Vec) -> Option