diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 835d68a2fb..80e1192e2c 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -31,14 +31,12 @@ For any other OS, checkout the [Zig installation page](https://github.com/ziglan ### 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. - 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 +116,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/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/src/lib.rs b/cli/src/lib.rs index e961e8c455..c15257487b 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -90,7 +90,11 @@ pub fn build_app<'a>() -> App<'a> { } pub fn docs(files: Vec) { - roc_docs::generate(files, roc_builtins::std::standard_stdlib(), Path::new("./")) + 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<()> { 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/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/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 1a7aa72d37..6494512630 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -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, } 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 ad73268eda..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; 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/file.rs b/compiler/load/src/file.rs index e9474fcd84..fc6177c4bd 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -2265,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, @@ -2275,6 +2279,7 @@ fn load_pkg_config<'a>( module_ids.clone(), ident_ids_by_module.clone(), &header, + header_src, pkg_module_timing, ) .1; @@ -2870,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>>, @@ -2889,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)> = @@ -3080,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, @@ -3209,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, @@ -4114,7 +4143,7 @@ where Ok(()) } -fn to_file_problem_report(filename: &PathBuf, error: io::ErrorKind) -> String { +fn to_file_problem_report(filename: &Path, error: io::ErrorKind) -> String { use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE}; use ven_pretty::DocAllocator; 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/mono/src/ir.rs b/compiler/mono/src/ir.rs index 2e954f346b..0a7d3cde2b 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -424,6 +424,8 @@ impl<'a> Procs<'a> { is_self_recursive: bool, ret_var: Variable, ) { + let number_of_arguments = loc_args.len(); + match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) { Ok((_, pattern_symbols, body)) => { // a named closure. Since these aren't specialized by the surrounding @@ -444,17 +446,22 @@ impl<'a> Procs<'a> { } Err(error) => { - // If the function has invalid patterns in its arguments, - // its call sites will code gen to runtime errors. This happens - // at the call site so we don't have to try to define the - // function LLVM, which would be difficult considering LLVM - // wants to know what symbols each argument corresponds to, - // and in this case the patterns were invalid, so we don't know - // what the symbols ought to be. + let mut pattern_symbols = Vec::with_capacity_in(number_of_arguments, env.arena); - let error_msg = format!("TODO generate a RuntimeError message for {:?}", error); + for _ in 0..number_of_arguments { + pattern_symbols.push(env.unique_symbol()); + } - self.runtime_errors.insert(name, env.arena.alloc(error_msg)); + self.partial_procs.insert( + name, + PartialProc { + annotation, + pattern_symbols: pattern_symbols.into_bump_slice(), + captured_symbols: CapturedSymbols::None, + body: roc_can::expr::Expr::RuntimeError(error.value), + is_self_recursive: false, + }, + ); } } } @@ -1788,10 +1795,11 @@ fn specialize_external<'a>( let snapshot = env.subs.snapshot(); let cache_snapshot = layout_cache.snapshot(); - let unified = roc_unify::unify::unify(env.subs, annotation, fn_var); + let _unified = roc_unify::unify::unify(env.subs, annotation, fn_var); - let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_)); - debug_assert!(is_valid, "unificaton failure for {:?}", proc_name); + // This will not hold for programs with type errors + // let is_valid = matches!(unified, roc_unify::unify::Unified::Success(_)); + // debug_assert!(is_valid, "unificaton failure for {:?}", proc_name); // if this is a closure, add the closure record argument let pattern_symbols = if let CapturedSymbols::Captured(_) = captured_symbols { @@ -2131,9 +2139,7 @@ fn build_specialized_proc_adapter<'a>( arg_layouts.push(layout); } - let ret_layout = layout_cache - .from_var(&env.arena, ret_var, env.subs) - .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); + let ret_layout = layout_cache.from_var(&env.arena, ret_var, env.subs)?; build_specialized_proc( env.arena, diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index cc43104d1b..98c16ab593 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/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 borrow; pub mod expand_rc; diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index eb72f51cae..1a785f9671 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -125,7 +125,7 @@ where E: 'a, { move |_, state: State<'a>| { - if state.column > min_indent { + if state.column >= min_indent { Ok((NoProgress, (), state)) } else { Err((NoProgress, indent_problem(state.line, state.column), state)) diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 0b163b5057..7bd48bc7cf 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -16,16 +16,33 @@ use roc_region::all::{Located, Position, Region}; use crate::parser::Progress::{self, *}; +fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> { + |_arena, state: State<'a>| { + if state.has_reached_end() { + Ok((NoProgress, (), state)) + } else { + Err(( + NoProgress, + EExpr::BadExprEnd(state.line, state.column), + state, + )) + } + } +} + pub fn test_parse_expr<'a>( min_indent: u16, arena: &'a bumpalo::Bump, state: State<'a>, ) -> Result>, EExpr<'a>> { - let parser = space0_before_e( - move |a, s| parse_loc_expr(min_indent, a, s), - min_indent, - EExpr::Space, - EExpr::IndentStart, + let parser = skip_second!( + space0_before_e( + move |a, s| parse_loc_expr(min_indent, a, s), + min_indent, + EExpr::Space, + EExpr::IndentStart, + ), + expr_end() ); match parser.parse(arena, state) { @@ -35,9 +52,27 @@ pub fn test_parse_expr<'a>( } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MultiBackpassing { - Allow, - Disallow, +pub struct ExprParseOptions { + /// Check for and accept multi-backpassing syntax + /// This is usually true, but false within list/record literals + /// because the comma separating backpassing arguments conflicts + /// with the comma separating literal elements + accept_multi_backpassing: bool, + + /// Check for the `->` token, and raise an error if found + /// This is usually true, but false in if-guards + /// + /// > Just foo if foo == 2 -> ... + check_for_arrow: bool, +} + +impl Default for ExprParseOptions { + fn default() -> Self { + ExprParseOptions { + accept_multi_backpassing: true, + check_for_arrow: true, + } + } } pub fn expr_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'a>> { @@ -159,7 +194,7 @@ fn record_field_access<'a>() -> impl Parser<'a, &'a str, EExpr<'a>> { fn parse_loc_term<'a>( min_indent: u16, - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Located>, EExpr<'a>> { @@ -167,10 +202,7 @@ fn parse_loc_term<'a>( loc_expr_in_parens_etc_help(min_indent), loc!(specialize(EExpr::Str, string_literal_help())), loc!(specialize(EExpr::Number, positive_number_literal_help())), - loc!(specialize( - EExpr::Lambda, - closure_help(min_indent, multi_backpassing) - )), + loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))), loc!(record_literal_help(min_indent)), loc!(specialize(EExpr::List, list_literal_help(min_indent))), loc!(map_with_arena!( @@ -183,17 +215,14 @@ fn parse_loc_term<'a>( fn loc_possibly_negative_or_negated_term<'a>( min_indent: u16, - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, ) -> impl Parser<'a, Located>, EExpr<'a>> { one_of![ |arena, state: State<'a>| { let initial = state; let (_, (loc_op, loc_expr), state) = and!(loc!(unary_negate()), |a, s| parse_loc_term( - min_indent, - multi_backpassing, - a, - s + min_indent, options, a, s )) .parse(arena, state)?; @@ -205,7 +234,7 @@ fn loc_possibly_negative_or_negated_term<'a>( loc!(specialize(EExpr::Number, number_literal_help())), loc!(map_with_arena!( and!(loc!(word1(b'!', EExpr::Start)), |a, s| { - parse_loc_term(min_indent, multi_backpassing, a, s) + parse_loc_term(min_indent, options, a, s) }), |arena: &'a Bump, (loc_op, loc_expr): (Located<_>, _)| { Expr::UnaryOp( @@ -216,7 +245,7 @@ fn loc_possibly_negative_or_negated_term<'a>( )), |arena, state| { // TODO use parse_loc_term_better - parse_loc_term(min_indent, multi_backpassing, arena, state) + parse_loc_term(min_indent, options, arena, state) } ] } @@ -260,25 +289,19 @@ fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> { fn parse_expr_start<'a>( min_indent: u16, - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, start: Position, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Located>, EExpr<'a>> { one_of![ - loc!(specialize( - EExpr::If, - if_expr_help(min_indent, multi_backpassing) - )), + loc!(specialize(EExpr::If, if_expr_help(min_indent, options))), loc!(specialize( EExpr::When, - when::expr_help(min_indent, multi_backpassing) + when::expr_help(min_indent, options) )), - loc!(specialize( - EExpr::Lambda, - closure_help(min_indent, multi_backpassing) - )), - loc!(move |a, s| parse_expr_operator_chain(min_indent, multi_backpassing, start, a, s)), + loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))), + loc!(move |a, s| parse_expr_operator_chain(min_indent, options, start, a, s)), fail_expr_start_e() ] .parse(arena, state) @@ -286,13 +309,13 @@ fn parse_expr_start<'a>( fn parse_expr_operator_chain<'a>( min_indent: u16, - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, start: Position, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { let (_, expr, state) = - loc_possibly_negative_or_negated_term(min_indent, multi_backpassing).parse(arena, state)?; + loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state)?; let initial = state; let end = state.get_position(); @@ -309,14 +332,7 @@ fn parse_expr_operator_chain<'a>( end, }; - parse_expr_end( - min_indent, - multi_backpassing, - start, - expr_state, - arena, - state, - ) + parse_expr_end(min_indent, options, start, expr_state, arena, state) } } } @@ -688,7 +704,7 @@ struct DefState<'a> { } fn parse_defs_end<'a>( - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, start: Position, mut def_state: DefState<'a>, arena: &'a Bump, @@ -743,7 +759,7 @@ fn parse_defs_end<'a>( loc_def_expr, ); - parse_defs_end(multi_backpassing, start, def_state, arena, state) + parse_defs_end(options, start, def_state, arena, state) } Ok((_, BinOp::HasType, state)) => { let (_, ann_type, state) = specialize( @@ -765,7 +781,7 @@ fn parse_defs_end<'a>( ann_type, ); - parse_defs_end(multi_backpassing, start, def_state, arena, state) + parse_defs_end(options, start, def_state, arena, state) } _ => Ok((MadeProgress, def_state, initial)), @@ -774,7 +790,7 @@ fn parse_defs_end<'a>( } fn parse_defs_expr<'a>( - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, start: Position, def_state: DefState<'a>, arena: &'a Bump, @@ -782,7 +798,7 @@ fn parse_defs_expr<'a>( ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { let min_indent = start.col; - match parse_defs_end(multi_backpassing, start, def_state, arena, state) { + match parse_defs_end(options, start, def_state, arena, state) { Err(bad) => Err(bad), Ok((_, def_state, state)) => { // this is no def, because there is no `=` or `:`; parse as an expr @@ -815,7 +831,7 @@ fn parse_defs_expr<'a>( fn parse_expr_operator<'a>( min_indent: u16, - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, start: Position, mut expr_state: ExprState<'a>, loc_op: Located, @@ -835,8 +851,7 @@ fn parse_expr_operator<'a>( BinOp::Minus if expr_state.end != op_start && op_end == new_start => { // negative terms - let (_, negated_expr, state) = - parse_loc_term(min_indent, multi_backpassing, arena, state)?; + let (_, negated_expr, state) = parse_loc_term(min_indent, options, arena, state)?; let new_end = state.get_position(); let arg = numeric_negate_expression( @@ -859,14 +874,7 @@ fn parse_expr_operator<'a>( expr_state.spaces_after = spaces; expr_state.end = new_end; - parse_expr_end( - min_indent, - multi_backpassing, - start, - expr_state, - arena, - state, - ) + parse_expr_end(min_indent, options, start, expr_state, arena, state) } BinOp::Assignment => { let expr_region = expr_state.expr.region; @@ -915,7 +923,7 @@ fn parse_expr_operator<'a>( spaces_after: &[], }; - parse_defs_expr(multi_backpassing, start, def_state, arena, state) + parse_defs_expr(options, start, def_state, arena, state) } BinOp::Backpassing => { let expr_region = expr_state.expr.region; @@ -1062,11 +1070,9 @@ fn parse_expr_operator<'a>( spaces_after: &[], }; - parse_defs_expr(multi_backpassing, start, def_state, arena, state) + parse_defs_expr(options, start, def_state, arena, state) } - _ => match loc_possibly_negative_or_negated_term(min_indent, multi_backpassing) - .parse(arena, state) - { + _ => match loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state) { Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), Ok((_, mut new_expr, state)) => { let new_end = state.get_position(); @@ -1104,14 +1110,7 @@ fn parse_expr_operator<'a>( expr_state.spaces_after = spaces; // TODO new start? - parse_expr_end( - min_indent, - multi_backpassing, - start, - expr_state, - arena, - state, - ) + parse_expr_end(min_indent, options, start, expr_state, arena, state) } } } @@ -1124,7 +1123,7 @@ fn parse_expr_operator<'a>( fn parse_expr_end<'a>( min_indent: u16, - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, start: Position, mut expr_state: ExprState<'a>, arena: &'a Bump, @@ -1132,7 +1131,7 @@ fn parse_expr_end<'a>( ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { let parser = skip_first!( crate::blankspace::check_indent(min_indent, EExpr::IndentEnd), - move |a, s| parse_loc_term(min_indent, multi_backpassing, a, s) + move |a, s| parse_loc_term(min_indent, options, a, s) ); match parser.parse(arena, state) { @@ -1163,14 +1162,7 @@ fn parse_expr_end<'a>( expr_state.end = new_end; expr_state.spaces_after = new_spaces; - parse_expr_end( - min_indent, - multi_backpassing, - start, - expr_state, - arena, - state, - ) + parse_expr_end(min_indent, options, start, expr_state, arena, state) } } } @@ -1183,19 +1175,12 @@ fn parse_expr_end<'a>( expr_state.consume_spaces(arena); expr_state.initial = before_op; parse_expr_operator( - min_indent, - multi_backpassing, - start, - expr_state, - loc_op, - arena, - state, + min_indent, options, start, expr_state, loc_op, arena, state, ) } Err((NoProgress, _, mut state)) => { // try multi-backpassing - if multi_backpassing == MultiBackpassing::Allow && state.bytes.starts_with(b",") - { + if options.accept_multi_backpassing && state.bytes.starts_with(b",") { state.bytes = &state.bytes[1..]; state.column += 1; @@ -1256,6 +1241,12 @@ fn parse_expr_end<'a>( Ok((MadeProgress, ret, state)) } } + } else if options.check_for_arrow && state.bytes.starts_with(b"->") { + Err(( + MadeProgress, + EExpr::BadOperator(&[b'-', b'>'], state.line, state.column), + state, + )) } else { // roll back space parsing let state = expr_state.initial; @@ -1273,7 +1264,15 @@ fn parse_loc_expr<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Located>, EExpr<'a>> { - parse_loc_expr_with_options(min_indent, MultiBackpassing::Allow, arena, state) + parse_loc_expr_with_options( + min_indent, + ExprParseOptions { + accept_multi_backpassing: true, + ..Default::default() + }, + arena, + state, + ) } pub fn parse_loc_expr_no_multi_backpassing<'a>( @@ -1281,17 +1280,25 @@ pub fn parse_loc_expr_no_multi_backpassing<'a>( arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Located>, EExpr<'a>> { - parse_loc_expr_with_options(min_indent, MultiBackpassing::Disallow, arena, state) + parse_loc_expr_with_options( + min_indent, + ExprParseOptions { + accept_multi_backpassing: false, + ..Default::default() + }, + arena, + state, + ) } fn parse_loc_expr_with_options<'a>( min_indent: u16, - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Located>, EExpr<'a>> { let start = state.get_position(); - parse_expr_start(min_indent, multi_backpassing, start, arena, state) + parse_expr_start(min_indent, options, start, arena, state) } /// If the given Expr would parse the same way as a valid Pattern, convert it. @@ -1443,8 +1450,13 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located>>, E space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; let start = state.get_position(); - let (_, def_state, state) = - parse_defs_end(MultiBackpassing::Disallow, start, def_state, arena, state)?; + + let options = ExprParseOptions { + accept_multi_backpassing: false, + check_for_arrow: true, + }; + + let (_, def_state, state) = parse_defs_end(options, start, def_state, arena, state)?; let (_, final_space, state) = space0_e(start.col, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; @@ -1482,7 +1494,7 @@ pub fn defs<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located>>, E fn closure_help<'a>( min_indent: u16, - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, ELambda<'a>> { map_with_arena!( skip_first!( @@ -1510,7 +1522,7 @@ fn closure_help<'a>( // Parse the body space0_before_e( specialize_ref(ELambda::Body, move |arena, state| { - parse_loc_expr_with_options(min_indent, multi_backpassing, arena, state) + parse_loc_expr_with_options(min_indent, options, arena, state) }), min_indent, ELambda::Space, @@ -1535,7 +1547,7 @@ mod when { /// Parser for when expressions. pub fn expr_help<'a>( min_indent: u16, - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, When<'a>> { then( and!( @@ -1543,7 +1555,7 @@ mod when { skip_second!( space0_around_ee( specialize_ref(When::Condition, move |arena, state| { - parse_loc_expr_with_options(min_indent, multi_backpassing, arena, state) + parse_loc_expr_with_options(min_indent, options, arena, state) }), min_indent, When::Space, @@ -1566,7 +1578,7 @@ mod when { // Everything in the branches must be indented at least as much as the case itself. let min_indent = case_indent; - let (p1, branches, state) = branches(min_indent).parse(arena, state)?; + let (p1, branches, state) = branches(min_indent, options).parse(arena, state)?; Ok(( progress.or(p1), @@ -1586,22 +1598,27 @@ mod when { } } - fn branches<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, When<'a>> { - move |arena, state| { + fn branches<'a>( + min_indent: u16, + options: ExprParseOptions, + ) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, When<'a>> { + move |arena, state: State<'a>| { + let when_indent = state.indent_col; + let mut branches: Vec<'a, &'a WhenBranch<'a>> = Vec::with_capacity_in(2, arena); // 1. Parse the first branch and get its indentation level. (It must be >= min_indent.) // 2. Parse the other branches. Their indentation levels must be == the first branch's. - let (_, (loc_first_patterns, loc_first_guard), state) = - branch_alternatives(min_indent).parse(arena, state)?; - let loc_first_pattern = loc_first_patterns.first().unwrap(); - let original_indent = loc_first_pattern.region.start_col; - let indented_more = original_indent + 1; + let (_, ((pattern_indent_level, loc_first_patterns), loc_first_guard), mut state) = + branch_alternatives(min_indent, options, None).parse(arena, state)?; + let original_indent = pattern_indent_level; + + state.indent_col = pattern_indent_level; // Parse the first "->" and the expression after it. let (_, loc_first_expr, mut state) = - branch_result(indented_more).parse(arena, state)?; + branch_result(original_indent + 1).parse(arena, state)?; // Record this as the first branch, then optionally parse additional branches. branches.push(arena.alloc(WhenBranch { @@ -1613,19 +1630,21 @@ mod when { let branch_parser = map!( and!( then( - branch_alternatives(min_indent), - move |_arena, state, _, (loc_patterns, loc_guard)| { - match alternatives_indented_correctly(&loc_patterns, original_indent) { - Ok(()) => Ok((MadeProgress, (loc_patterns, loc_guard), state)), - Err(indent) => Err(( + branch_alternatives(min_indent, options, Some(pattern_indent_level)), + move |_arena, state, _, ((indent_col, loc_patterns), loc_guard)| { + if pattern_indent_level == indent_col { + Ok((MadeProgress, (loc_patterns, loc_guard), state)) + } else { + let indent = pattern_indent_level - indent_col; + Err(( MadeProgress, When::PatternAlignment(indent, state.line, state.column), state, - )), + )) } }, ), - branch_result(indented_more) + branch_result(original_indent + 1) ), |((patterns, guard), expr)| { let patterns: Vec<'a, _> = patterns; @@ -1655,40 +1674,36 @@ mod when { } } - Ok((MadeProgress, branches, state)) + Ok(( + MadeProgress, + branches, + State { + indent_col: when_indent, + ..state + }, + )) } } /// Parsing alternative patterns in when branches. fn branch_alternatives<'a>( min_indent: u16, - ) -> impl Parser<'a, (Vec<'a, Located>>, Option>>), When<'a>> { + options: ExprParseOptions, + pattern_indent_level: Option, + ) -> impl Parser< + 'a, + ( + (Col, Vec<'a, Located>>), + Option>>, + ), + When<'a>, + > { + let options = ExprParseOptions { + check_for_arrow: false, + ..options + }; and!( - sep_by1(word1(b'|', When::Bar), |arena, state| { - let (_, spaces, state) = - backtrackable(space0_e(min_indent, When::Space, When::IndentPattern)) - .parse(arena, state)?; - - let (_, loc_pattern, state) = space0_after_e( - specialize(When::Pattern, crate::pattern::loc_pattern_help(min_indent)), - min_indent, - When::Space, - When::IndentPattern, - ) - .parse(arena, state)?; - - Ok(( - MadeProgress, - if spaces.is_empty() { - loc_pattern - } else { - arena - .alloc(loc_pattern.value) - .with_spaces_before(spaces, loc_pattern.region) - }, - state, - )) - }), + branch_alternatives_help(min_indent, pattern_indent_level), one_of![ map!( skip_first!( @@ -1696,7 +1711,7 @@ mod when { // TODO we should require space before the expression but not after space0_around_ee( specialize_ref(When::IfGuard, move |arena, state| { - parse_loc_expr(min_indent, arena, state) + parse_loc_expr_with_options(min_indent + 1, options, arena, state) }), min_indent, When::Space, @@ -1711,22 +1726,103 @@ mod when { ) } - /// Check if alternatives of a when branch are indented correctly. - fn alternatives_indented_correctly<'a>( - loc_patterns: &'a Vec<'a, Located>>, - original_indent: u16, - ) -> Result<(), u16> { - let (first, rest) = loc_patterns.split_first().unwrap(); - let first_indented_correctly = first.region.start_col == original_indent; - if first_indented_correctly { - for when_pattern in rest.iter() { - if when_pattern.region.start_col < original_indent { - return Err(original_indent - when_pattern.region.start_col); + fn branch_single_alternative<'a>( + min_indent: u16, + ) -> impl Parser<'a, Located>, When<'a>> { + move |arena, state| { + let (_, spaces, state) = + backtrackable(space0_e(min_indent, When::Space, When::IndentPattern)) + .parse(arena, state)?; + + let (_, loc_pattern, state) = space0_after_e( + specialize(When::Pattern, crate::pattern::loc_pattern_help(min_indent)), + min_indent, + When::Space, + When::IndentPattern, + ) + .parse(arena, state)?; + + Ok(( + MadeProgress, + if spaces.is_empty() { + loc_pattern + } else { + arena + .alloc(loc_pattern.value) + .with_spaces_before(spaces, loc_pattern.region) + }, + state, + )) + } + } + + fn branch_alternatives_help<'a>( + min_indent: u16, + pattern_indent_level: Option, + ) -> impl Parser<'a, (Col, Vec<'a, Located>>), When<'a>> { + move |arena, state: State<'a>| { + let initial = state; + + // put no restrictions on the indent after the spaces; we'll check it manually + match space0_e(0, When::Space, When::IndentPattern).parse(arena, state) { + Err((MadeProgress, fail, _)) => Err((NoProgress, fail, initial)), + Err((NoProgress, fail, _)) => Err((NoProgress, fail, initial)), + Ok((_progress, spaces, state)) => { + match pattern_indent_level { + Some(wanted) if state.column > wanted => { + // this branch is indented too much + Err(( + NoProgress, + When::IndentPattern(state.line, state.column), + initial, + )) + } + Some(wanted) if state.column < wanted => { + let indent = wanted - state.column; + Err(( + NoProgress, + When::PatternAlignment(indent, state.line, state.column), + initial, + )) + } + _ => { + let pattern_indent = + min_indent.max(pattern_indent_level.unwrap_or(min_indent)); + // the region is not reliable for the indent col in the case of + // parentheses around patterns + let pattern_indent_col = state.column; + + let parser = sep_by1( + word1(b'|', When::Bar), + branch_single_alternative(pattern_indent + 1), + ); + + match parser.parse(arena, state) { + Err((MadeProgress, fail, state)) => { + Err((MadeProgress, fail, state)) + } + Err((NoProgress, fail, _)) => { + // roll back space parsing if the pattern made no progress + Err((NoProgress, fail, initial)) + } + + Ok((_, mut loc_patterns, state)) => { + // tag spaces onto the first parsed pattern + if !spaces.is_empty() { + if let Some(first) = loc_patterns.get_mut(0) { + *first = arena + .alloc(first.value) + .with_spaces_before(spaces, first.region); + } + } + + Ok((MadeProgress, (pattern_indent_col, loc_patterns), state)) + } + } + } + } } } - Ok(()) - } else { - Err(original_indent - first.region.start_col) } } @@ -1789,7 +1885,7 @@ fn if_branch<'a>( fn if_expr_help<'a>( min_indent: u16, - multi_backpassing: MultiBackpassing, + options: ExprParseOptions, ) -> impl Parser<'a, Expr<'a>, If<'a>> { move |arena: &'a Bump, state| { let (_, _, state) = parser::keyword_e(keyword::IF, If::If).parse(arena, state)?; @@ -1821,7 +1917,7 @@ fn if_expr_help<'a>( let (_, else_branch, state) = space0_before_e( specialize_ref(If::ElseBranch, move |arena, state| { - parse_loc_expr_with_options(min_indent, multi_backpassing, arena, state) + parse_loc_expr_with_options(min_indent, options, arena, state) }), min_indent, If::Space, diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index 5442509877..f8bb29630b 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -42,15 +42,15 @@ pub enum PackageOrPath<'a> { #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct ModuleName<'a>(&'a str); -impl<'a> Into<&'a str> for ModuleName<'a> { - fn into(self) -> &'a str { - self.0 +impl<'a> From> for &'a str { + fn from(name: ModuleName<'a>) -> Self { + name.0 } } -impl<'a> Into for ModuleName<'a> { - fn into(self) -> InlinableString { - self.0.into() +impl<'a> From> for InlinableString { + fn from(name: ModuleName<'a>) -> InlinableString { + name.0.into() } } diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index a6df64b54b..6afb7049af 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -185,7 +185,7 @@ pub enum SyntaxError<'a> { ReservedKeyword(Region), ArgumentsBeforeEquals(Region), NotYetImplemented(String), - TODO, + Todo, Type(Type<'a>), Pattern(EPattern<'a>), Expr(EExpr<'a>), @@ -381,6 +381,7 @@ pub type Col = u16; pub enum EExpr<'a> { Start(Row, Col), End(Row, Col), + BadExprEnd(Row, Col), Space(BadInputError, Row, Col), Dot(Row, Col), @@ -930,8 +931,8 @@ where state = next_state; buf.push(next_output); } - Err((element_progress, fail, state)) => { - return Err((element_progress, fail, state)); + Err((_, fail, state)) => { + return Err((MadeProgress, fail, state)); } } } diff --git a/compiler/parse/src/pattern.rs b/compiler/parse/src/pattern.rs index 38a05bd3aa..1ae58f38a1 100644 --- a/compiler/parse/src/pattern.rs +++ b/compiler/parse/src/pattern.rs @@ -62,8 +62,8 @@ pub fn loc_pattern_help<'a>( EPattern::Record, crate::pattern::record_pattern_help(min_indent) )), + loc!(number_pattern_help()), loc!(string_pattern_help()), - loc!(number_pattern_help()) ) } diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 1aa5d0cba9..dff6f0d72d 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -2550,6 +2550,247 @@ mod test_parse { assert_eq!(Ok(expected), actual); } + #[test] + fn when_with_negative_numbers() { + let arena = Bump::new(); + let newlines = &[Newline]; + let pattern1 = Pattern::SpaceBefore(arena.alloc(NumLiteral("1")), newlines); + let loc_pattern1 = Located::new(1, 1, 1, 2, pattern1); + let expr1 = Num("2"); + let loc_expr1 = Located::new(1, 1, 6, 7, expr1); + let branch1 = &*arena.alloc(WhenBranch { + patterns: arena.alloc([loc_pattern1]), + value: loc_expr1, + guard: None, + }); + let newlines = &[Newline]; + let pattern2 = Pattern::SpaceBefore(arena.alloc(NumLiteral("-3")), newlines); + let loc_pattern2 = Located::new(2, 2, 1, 3, pattern2); + let expr2 = Num("4"); + let loc_expr2 = Located::new(2, 2, 7, 8, expr2); + let branch2 = &*arena.alloc(WhenBranch { + patterns: arena.alloc([loc_pattern2]), + value: loc_expr2, + guard: None, + }); + let branches = &[branch1, branch2]; + let var = Var { + module_name: "", + ident: "x", + }; + let loc_cond = Located::new(0, 0, 5, 6, var); + let expected = Expr::When(arena.alloc(loc_cond), branches); + let actual = parse_expr_with( + &arena, + indoc!( + r#" + when x is + 1 -> 2 + -3 -> 4 + "# + ), + ); + + assert_eq!(Ok(expected), actual); + } + + #[test] + fn when_with_function_application() { + let arena = Bump::new(); + let newlines = &[Newline]; + let pattern1 = Pattern::SpaceBefore(arena.alloc(NumLiteral("1")), newlines); + let loc_pattern1 = Located::new(1, 1, 4, 5, pattern1); + let num_2 = Num("2"); + let num_neg = Located::new( + 1, + 1, + 9, + 16, + Expr::Var { + module_name: "Num", + ident: "neg", + }, + ); + let expr0 = Located::new(2, 2, 5, 6, Expr::SpaceBefore(&num_2, &[Newline])); + let expr1 = Expr::Apply( + &num_neg, + &*arena.alloc([&*arena.alloc(expr0)]), + CalledVia::Space, + ); + let loc_expr1 = Located::new(1, 2, 9, 6, expr1); + let branch1 = &*arena.alloc(WhenBranch { + patterns: arena.alloc([loc_pattern1]), + value: loc_expr1, + guard: None, + }); + let newlines = &[Newline]; + let pattern2 = Pattern::SpaceBefore(arena.alloc(Underscore("")), newlines); + let loc_pattern2 = Located::new(3, 3, 4, 5, pattern2); + let expr2 = Num("4"); + let loc_expr2 = Located::new(3, 3, 9, 10, expr2); + let branch2 = &*arena.alloc(WhenBranch { + patterns: arena.alloc([loc_pattern2]), + value: loc_expr2, + guard: None, + }); + let branches = &[branch1, branch2]; + let var = Var { + module_name: "", + ident: "x", + }; + let loc_cond = Located::new(0, 0, 5, 6, var); + let expected = Expr::When(arena.alloc(loc_cond), branches); + let actual = parse_expr_with( + &arena, + indoc!( + r#" + when x is + 1 -> Num.neg + 2 + _ -> 4 + "# + ), + ); + + assert_eq!(Ok(expected), actual); + } + + #[test] + fn when_if_guard() { + let arena = Bump::new(); + + let branch1 = { + let newlines = &[Newline]; + let pattern1 = Pattern::SpaceBefore(arena.alloc(Underscore("")), newlines); + let loc_pattern1 = Located::new(1, 1, 4, 5, pattern1); + let num_1 = Num("1"); + let expr1 = Located::new( + 2, + 2, + 8, + 9, + Expr::SpaceBefore(arena.alloc(num_1), &[Newline]), + ); + let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1); + &*arena.alloc(WhenBranch { + patterns: arena.alloc([loc_pattern1]), + value: loc_expr1, + guard: None, + }) + }; + + let branch2 = { + let pattern1 = Pattern::SpaceBefore(arena.alloc(Underscore("")), &[Newline, Newline]); + let loc_pattern1 = Located::new(4, 4, 4, 5, pattern1); + let num_1 = Num("2"); + let expr1 = Located::new( + 5, + 5, + 8, + 9, + Expr::SpaceBefore(arena.alloc(num_1), &[Newline]), + ); + let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1); + &*arena.alloc(WhenBranch { + patterns: arena.alloc([loc_pattern1]), + value: loc_expr1, + guard: None, + }) + }; + + let branch3 = { + let pattern1 = + Pattern::SpaceBefore(arena.alloc(Pattern::GlobalTag("Ok")), &[Newline, Newline]); + let loc_pattern1 = Located::new(7, 7, 4, 6, pattern1); + let num_1 = Num("3"); + let expr1 = Located::new( + 8, + 8, + 8, + 9, + Expr::SpaceBefore(arena.alloc(num_1), &[Newline]), + ); + let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1); + &*arena.alloc(WhenBranch { + patterns: arena.alloc([loc_pattern1]), + value: loc_expr1, + guard: None, + }) + }; + + let branches = &[branch1, branch2, branch3]; + let var = Var { + module_name: "", + ident: "x", + }; + let loc_cond = Located::new(0, 0, 5, 6, var); + let expected = Expr::When(arena.alloc(loc_cond), branches); + let actual = parse_expr_with( + &arena, + indoc!( + r#" + when x is + _ -> + 1 + + _ -> + 2 + + Ok -> + 3 + "# + ), + ); + + assert_eq!(Ok(expected), actual); + } + + #[test] + fn when_in_parens() { + let arena = Bump::new(); + + let branch1 = { + let newlines = &[Newline]; + let pattern1 = Pattern::SpaceBefore(arena.alloc(Pattern::GlobalTag("Ok")), newlines); + let loc_pattern1 = Located::new(1, 1, 4, 6, pattern1); + let num_1 = Num("3"); + let expr1 = Located::new( + 2, + 2, + 8, + 9, + Expr::SpaceBefore(arena.alloc(num_1), &[Newline]), + ); + let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1); + &*arena.alloc(WhenBranch { + patterns: arena.alloc([loc_pattern1]), + value: loc_expr1, + guard: None, + }) + }; + + let branches = &[branch1]; + let var = Var { + module_name: "", + ident: "x", + }; + let loc_cond = Located::new(0, 0, 6, 7, var); + let when = Expr::When(arena.alloc(loc_cond), branches); + let expected = Expr::ParensAround(&when); + let actual = parse_expr_with( + &arena, + indoc!( + r#" + (when x is + Ok -> + 3) + "# + ), + ); + + assert_eq!(Ok(expected), actual); + } + #[test] fn when_with_records() { let arena = Bump::new(); @@ -2599,6 +2840,47 @@ mod test_parse { assert_eq!(Ok(expected), actual); } + #[test] + fn when_in_parens_indented() { + let arena = Bump::new(); + + let branch1 = { + let newlines = &[Newline]; + let pattern1 = Pattern::SpaceBefore(arena.alloc(Pattern::GlobalTag("Ok")), newlines); + let loc_pattern1 = Located::new(1, 1, 4, 6, pattern1); + let num_1 = Num("3"); + let expr1 = Located::new(1, 1, 10, 11, num_1); + let loc_expr1 = expr1; // Located::new(1, 2, 9, 6, expr1); + &*arena.alloc(WhenBranch { + patterns: arena.alloc([loc_pattern1]), + value: loc_expr1, + guard: None, + }) + }; + + let branches = &[branch1]; + let var = Var { + module_name: "", + ident: "x", + }; + let loc_cond = Located::new(0, 0, 6, 7, var); + let when = Expr::When(arena.alloc(loc_cond), branches); + let spaced = Expr::SpaceAfter(&when, &[Newline]); + let expected = Expr::ParensAround(&spaced); + let actual = parse_expr_with( + &arena, + indoc!( + r#" + (when x is + Ok -> 3 + ) + "# + ), + ); + + assert_eq!(Ok(expected), actual); + } + #[test] fn when_with_alternative_patterns() { let arena = Bump::new(); @@ -2620,9 +2902,9 @@ mod test_parse { let pattern2_alt = Pattern::SpaceBefore(arena.alloc(StrLiteral(PlainLine("bar"))), newlines); let loc_pattern2 = Located::new(2, 2, 1, 6, pattern2); - let loc_pattern2_alt = Located::new(3, 3, 1, 6, pattern2_alt); + let loc_pattern2_alt = Located::new(3, 3, 2, 7, pattern2_alt); let expr2 = Num("2"); - let loc_expr2 = Located::new(3, 3, 10, 11, expr2); + let loc_expr2 = Located::new(3, 3, 11, 12, expr2); let branch2 = &*arena.alloc(WhenBranch { patterns: arena.alloc([loc_pattern2, loc_pattern2_alt]), value: loc_expr2, @@ -2642,7 +2924,7 @@ mod test_parse { when x is "blah" | "blop" -> 1 "foo" | - "bar" -> 2 + "bar" -> 2 "# ), ); diff --git a/compiler/reporting/src/error/parse.rs b/compiler/reporting/src/error/parse.rs index e131745996..5f889843aa 100644 --- a/compiler/reporting/src/error/parse.rs +++ b/compiler/reporting/src/error/parse.rs @@ -272,22 +272,31 @@ fn to_expr_report<'a>( ]) .indent(4), ])], - b"->" => vec![alloc.stack(vec![ - alloc.concat(vec![ - alloc.reflow("The arrow "), - alloc.parser_suggestion("->"), - alloc.reflow(" is only used to define cases in a "), - alloc.keyword("when"), - alloc.reflow("."), - ]), - alloc - .vcat(vec![ - alloc.text("when color is"), - alloc.text("Red -> \"stop!\"").indent(4), - alloc.text("Green -> \"go!\"").indent(4), - ]) - .indent(4), - ])], + b"->" => match context { + Context::InNode(Node::WhenBranch, _row, _col, _) => { + return to_unexpected_arrow_report( + alloc, filename, *row, *col, start_row, start_col, + ); + } + _ => { + vec![alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("The arrow "), + alloc.parser_suggestion("->"), + alloc.reflow(" is only used to define cases in a "), + alloc.keyword("when"), + alloc.reflow("."), + ]), + alloc + .vcat(vec![ + alloc.text("when color is"), + alloc.text("Red -> \"stop!\"").indent(4), + alloc.text("Green -> \"go!\"").indent(4), + ]) + .indent(4), + ])] + } + }, b"!" => vec![ alloc.reflow("The boolean negation operator "), alloc.parser_suggestion("!"), @@ -458,6 +467,27 @@ fn to_expr_report<'a>( *col, ), + EExpr::BadExprEnd(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); + let region = Region::from_row_col(*row, *col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("Whatever I am running into is confusing me a lot! "), + alloc.reflow("Normally I can give fairly specific hints, "), + alloc.reflow("but something is really tripping me up this time."), + ]), + ]); + + Report { + filename, + doc, + title: "SYNTAX PROBLEM".to_string(), + } + } + EExpr::Colon(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let region = Region::from_row_col(*row, *col); @@ -982,7 +1012,7 @@ fn to_list_report<'a>( ), List::Open(row, col) | List::End(row, col) => { - match dbg!(what_is_next(alloc.src_lines, row, col)) { + match what_is_next(alloc.src_lines, row, col) { Next::Other(Some(',')) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -1387,25 +1417,68 @@ fn to_unfinished_when_report<'a>( start_row: Row, start_col: Col, message: RocDocBuilder<'a>, +) -> Report<'a> { + match what_is_next(alloc.src_lines, row, col) { + Next::Token("->") => { + to_unexpected_arrow_report(alloc, filename, row, col, start_row, start_col) + } + + _ => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow(r"I was partway through parsing a "), + alloc.keyword("when"), + alloc.reflow(r" expression, but I got stuck here:"), + ]), + alloc.region_with_subregion(surroundings, region), + message, + note_for_when_error(alloc), + ]); + + Report { + filename, + doc, + title: "UNFINISHED WHEN".to_string(), + } + } + } +} + +fn to_unexpected_arrow_report<'a>( + alloc: &'a RocDocAllocator<'a>, + filename: PathBuf, + row: Row, + col: Col, + start_row: Row, + start_col: Col, ) -> Report<'a> { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); - let region = Region::from_row_col(row, col); + let region = Region::from_rows_cols(row, col, row, col + 2); let doc = alloc.stack(vec![ alloc.concat(vec![ - alloc.reflow(r"I was partway through parsing a "), + alloc.reflow(r"I am parsing a "), alloc.keyword("when"), - alloc.reflow(r" expression, but I got stuck here:"), + alloc.reflow(r" expression right now, but this arrow is confusing me:"), ]), alloc.region_with_subregion(surroundings, region), - message, + alloc.concat(vec![ + alloc.reflow(r"It makes sense to see arrows around here, "), + alloc.reflow(r"so I suspect it is something earlier."), + alloc.reflow( + r"Maybe this pattern is indented a bit farther from the previous patterns?", + ), + ]), note_for_when_error(alloc), ]); Report { filename, doc, - title: "UNFINISHED WHEN".to_string(), + title: "UNEXPECTED ARROW".to_string(), } } diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 2b3b5eb106..8d107e8eb5 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -4967,7 +4967,6 @@ mod test_reporting { #[test] fn empty_or_pattern() { - // this should get better with time report_problem_as( indoc!( r#" @@ -4981,29 +4980,16 @@ mod test_reporting { ), indoc!( r#" - ── MISSING EXPRESSION ────────────────────────────────────────────────────────── - - I am partway through parsing a definition, but I got stuck here: - - 1│ when Just 4 is + ── UNFINISHED PATTERN ────────────────────────────────────────────────────────── + + I just started parsing a pattern, but I got stuck here: + 2│ Just 4 | -> - ^ - - I was expecting to see an expression like 42 or "hello". + ^ + + Note: I may be confused by indentation "# ), - // indoc!( - // r#" - // ── UNFINISHED PATTERN ────────────────────────────────────────────────────────── - // - // I just started parsing a pattern, but I got stuck here: - // - // 2│ Just 4 | -> - // ^ - // - // Note: I may be confused by indentation - // "# - // ), ) } @@ -5135,29 +5121,111 @@ mod test_reporting { r#" when 4 is 5 -> 2 - _ -> 2 + 2 -> 2 "# ), indoc!( r#" - ── UNFINISHED WHEN ───────────────────────────────────────────────────────────── + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + I got stuck here: + + 1│ when 4 is + 2│ 5 -> 2 + ^ + + Whatever I am running into is confusing me a lot! Normally I can give + fairly specific hints, but something is really tripping me up this + time. + "# + ), + // TODO this formerly gave + // + // ── UNFINISHED WHEN ───────────────────────────────────────────────────────────── + // + // I was partway through parsing a `when` expression, but I got stuck here: + // + // 3│ _ -> 2 + // ^ + // + // I suspect this is a pattern that is not indented enough? (by 2 spaces) + // + // but that requires parsing the next pattern blindly, irrespective of indentation. Can + // we find an efficient solution that doesn't require parsing an extra pattern for + // every `when`, i.e. we want a good error message for the test case above, but for + // a valid `when`, we don't want to do extra work, e.g. here + // + // x + // when x is + // n -> n + // + // 4 + // + // We don't want to parse the `4` and say it's an outdented pattern! + ) + } - I was partway through parsing a `when` expression, but I got stuck here: - - 3│ _ -> 2 - ^ - - I suspect this is a pattern that is not indented enough? (by 2 spaces) + #[test] + fn when_over_indented_underscore() { + report_problem_as( + indoc!( + r#" + when 4 is + 5 -> 2 + _ -> 2 + "# + ), + indoc!( + r#" + ── SYNTAX PROBLEM ────────────────────────────────────────────────────────────── + + I got stuck here: + + 1│ when 4 is + 2│ 5 -> 2 + ^ + + Whatever I am running into is confusing me a lot! Normally I can give + fairly specific hints, but something is really tripping me up this + time. + "# + ), + ) + } + #[test] + fn when_over_indented_int() { + report_problem_as( + indoc!( + r#" + when 4 is + 5 -> Num.neg + 2 -> 2 + "# + ), + indoc!( + r#" + ── UNEXPECTED ARROW ──────────────────────────────────────────────────────────── + + I am parsing a `when` expression right now, but this arrow is confusing + me: + + 3│ 2 -> 2 + ^^ + + It makes sense to see arrows around here, so I suspect it is something + earlier.Maybe this pattern is indented a bit farther from the previous + patterns? + Note: Here is an example of a valid `when` expression for reference. - + when List.first plants is Ok n -> n - + Err _ -> 200 - + Notice the indentation. All patterns are aligned, and each branch is indented a bit more than the corresponding pattern. That is important! "# @@ -5809,21 +5877,18 @@ mod test_reporting { ), indoc!( r#" - ── MISSING FINAL EXPRESSION ──────────────────────────────────────────────────── - - I am partway through parsing a definition's final expression, but I - got stuck here: - + ── UNKNOWN OPERATOR ──────────────────────────────────────────────────────────── + + This looks like an operator, but it's not one I recognize! + 1│ main = 5 -> 3 - ^ - - This definition is missing a final expression. A nested definition - must be followed by either another definition, or an expression - - x = 4 - y = 2 - - x + y + ^^ + + The arrow -> is only used to define cases in a `when`. + + when color is + Red -> "stop!" + Green -> "go!" "# ), ) diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 4c1b07ad0b..aa277f8a6a 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -2258,3 +2258,21 @@ fn backpassing_result() { i64 ); } + +#[test] +#[should_panic( + expected = "Shadowing { original_region: |L 3-3, C 4-5|, shadow: |L 5-5, C 6-7| Ident(\\\"x\\\") }" +)] +fn function_malformed_pattern() { + assert_evals_to!( + indoc!( + r#" + x = 3 + + (\x -> x) 42 + "# + ), + 3, + i64 + ); +} diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 27f2e10c49..e69523e33e 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -93,9 +93,9 @@ impl VarStore { } } -impl Into for VarStore { - fn into(self) -> Variable { - Variable(self.next) +impl From for Variable { + fn from(store: VarStore) -> Self { + Variable(store.next) } } @@ -139,9 +139,9 @@ impl fmt::Debug for OptVariable { } } -impl Into> for OptVariable { - fn into(self) -> Option { - self.into_variable() +impl From for Option { + fn from(opt_var: OptVariable) -> Self { + opt_var.into_variable() } } @@ -180,9 +180,9 @@ impl Variable { } } -impl Into for Variable { - fn into(self) -> OptVariable { - OptVariable(self.0) +impl From for OptVariable { + fn from(var: Variable) -> Self { + OptVariable(var.0) } } @@ -483,9 +483,9 @@ impl fmt::Debug for Rank { } } -impl Into for Rank { - fn into(self) -> usize { - self.0 +impl From for usize { + fn from(rank: Rank) -> Self { + rank.0 } } diff --git a/docs/src/lib.rs b/docs/src/lib.rs index cd37ec76a5..76369e0d1b 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -56,6 +56,29 @@ pub fn generate(filenames: Vec, std_lib: StdLib, build_dir: &Path) { modules: files_docs, }; + if !build_dir.exists() { + fs::create_dir_all(build_dir).expect("TODO gracefully handle unable to create build dir"); + } + + // Copy over the assets + fs::write( + build_dir.join("search.js"), + include_str!("./static/search.js"), + ) + .expect("TODO gracefully handle failing to make the search javascript"); + + fs::write( + build_dir.join("styles.css"), + include_str!("./static/styles.css"), + ) + .expect("TODO gracefully handle failing to make the stylesheet"); + + fs::write( + build_dir.join("favicon.svg"), + include_str!("./static/favicon.svg"), + ) + .expect("TODO gracefully handle failing to make the favicon"); + // Register handlebars template let mut handlebars = handlebars::Handlebars::new(); handlebars @@ -75,7 +98,7 @@ pub fn generate(filenames: Vec, std_lib: StdLib, build_dir: &Path) { .expect("TODO gracefully handle writing file failing"); } - println!("Docs generated at {}", build_dir.display()); + println!("🎉 Docs generated in {}", build_dir.display()); } pub fn files_to_documentations( diff --git a/docs/src/static/styles.css b/docs/src/static/styles.css index 716bdaf834..54f05e43f7 100644 --- a/docs/src/static/styles.css +++ b/docs/src/static/styles.css @@ -66,16 +66,14 @@ a:hover { } .pkg-and-logo { - min-width: 0; - /* necessary for text-overflow: ellipsis to work in descendants */ + min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */ display: flex; align-items: center; height: 100%; background-color: var(--top-bar-bg); } -.pkg-and-logo a, -.pkg-and-logo a:visited { +.pkg-and-logo a, .pkg-and-logo a:visited { color: var(--top-bar-fg); } @@ -84,18 +82,11 @@ a:hover { text-decoration: none; } -.main-container { - min-width: 0; - /* necessary for text-overflow: ellipsis to work in descendants */ -} - .search-button { - flex-shrink: 0; - /* always shrink the package name before these; they have a relatively constrained length */ + flex-shrink: 0; /* always shrink the package name before these; they have a relatively constrained length */ padding: 12px 18px; margin-right: 42px; - display: none; - /* only show this in the mobile view */ + display: none; /* only show this in the mobile view */ } .version { @@ -127,6 +118,8 @@ main { line-height: 1.85em; margin-top: 2px; padding: 48px; + + min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */ } #sidebar-nav { @@ -160,13 +153,11 @@ main { flex-direction: row; align-items: center; flex-wrap: nowrap; - flex-grow: 1; box-sizing: border-box; font-family: var(--font-sans); font-size: 24px; height: 100%; - min-width: 0; - /* necessary for text-overflow: ellipsis to work in descendants */ + min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */ } .top-header-triangle { @@ -181,8 +172,8 @@ main { } p { - overflow-wrap: break-word; - margin: 24px 0; + overflow-wrap: break-word; + margin: 24px 0; } footer { @@ -240,8 +231,7 @@ footer p { margin-bottom: 48px; } -.module-name a, -.module-name a:visited { +.module-name a, .module-name a:visited { color: inherit; } @@ -259,8 +249,7 @@ footer p { text-overflow: ellipsis; } -a, -a:visited { +a, a:visited { color: var(--link-color); } @@ -325,20 +314,19 @@ pre code { height: 0; } -#module-search, -#module-search:focus { +#module-search, #module-search:focus { opacity: 1; padding: 12px 16px; height: 48px; } /* Show the "Search" label link when the text input has a placeholder */ -#module-search:placeholder-shown+#search-link { +#module-search:placeholder-shown + #search-link { display: flex; } /* Hide the "Search" label link when the text input has focus */ -#module-search:focus+#search-link { +#module-search:focus + #search-link { display: none; } @@ -398,13 +386,13 @@ pre code { } } -@media only screen and (max-device-width: 480px) { +@media only screen and (max-device-width: 480px) and (orientation: portrait) { .search-button { - display: block; - /* This is only visible in mobile. */ + display: block; /* This is only visible in mobile. */ } .top-header { + justify-content: space-between; width: auto; } @@ -443,21 +431,19 @@ pre code { } main { + grid-column-start: none; + grid-column-end: none; + grid-row-start: above-footer; + grid-row-end: above-footer; padding: 18px; font-size: 16px; } - .container { - margin: 0; - min-width: 320px; - max-width: 100%; - } - - .content { - flex-direction: column; - } - - .sidebar { + #sidebar-nav { + grid-column-start: none; + grid-column-end: none; + grid-row-start: sidebar; + grid-row-end: sidebar; margin-top: 0; padding-left: 0; width: auto; @@ -478,12 +464,30 @@ pre code { font-size: 16px; } - .top-header { - justify-content: space-between; + body { + grid-template-columns: auto; + grid-template-rows: [top-header] var(--top-header-height) [before-sidebar] auto [sidebar] auto [above-footer] auto [footer] auto; +/* [before-sidebar] 1fr [sidebar] var(--sidebar-width) [main-content] fit-content(calc(1280px - var(--sidebar-width))) [end] 1fr; */ + + margin: 0; + min-width: 320px; + max-width: 100%; } - .content { - /* Display the sidebar below
without affecting tab index */ - flex-direction: column-reverse; + .top-header-triangle { + display: none; } -} \ No newline at end of file + + .pkg-and-logo { + width: 100%; + } + + .pkg-full-name { + flex-grow: 1; + } + + .pkg-full-name a { + padding-top: 24px; + padding-bottom: 12px; + } +} diff --git a/docs/src/templates/page.hbs b/docs/src/templates/page.hbs index 40429fe21c..00d3e1672c 100644 --- a/docs/src/templates/page.hbs +++ b/docs/src/templates/page.hbs @@ -6,9 +6,9 @@ The Roc Programming Language - - - + + + diff --git a/editor/src/editor/markup/nodes.rs b/editor/src/editor/markup/nodes.rs index 9b1d9be4a9..46548d5235 100644 --- a/editor/src/editor/markup/nodes.rs +++ b/editor/src/editor/markup/nodes.rs @@ -96,14 +96,12 @@ pub fn expr2_to_markup<'a, 'b>( new_markup_node(text, node_id, HighlightStyle::Variable, markup_node_pool) } Expr2::List { elems, .. } => { - let mut children_ids = Vec::new(); - - children_ids.push(new_markup_node( + let mut children_ids = vec![new_markup_node( "[ ".to_string(), node_id, HighlightStyle::Bracket, markup_node_pool, - )); + )]; for (idx, node_id) in elems.iter_node_ids().enumerate() { let sub_expr2 = env.pool.get(node_id); @@ -135,14 +133,12 @@ pub fn expr2_to_markup<'a, 'b>( markup_node_pool.add(list_node) } Expr2::Record { fields, .. } => { - let mut children_ids = Vec::new(); - - children_ids.push(new_markup_node( + let mut children_ids = vec![new_markup_node( "{ ".to_string(), node_id, HighlightStyle::Bracket, markup_node_pool, - )); + )]; for (idx, field_node_id) in fields.iter_node_ids().enumerate() { let (pool_field_name, _, sub_expr2_node_id) = env.pool.get(field_node_id); diff --git a/editor/src/lib.rs b/editor/src/lib.rs index d07472faca..45c2fbd68a 100644 --- a/editor/src/lib.rs +++ b/editor/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)] #[cfg_attr(test, macro_use)] extern crate indoc; diff --git a/examples/benchmarks/platform/host.zig b/examples/benchmarks/platform/host.zig index 898b2b317b..7cd230c61a 100644 --- a/examples/benchmarks/platform/host.zig +++ b/examples/benchmarks/platform/host.zig @@ -59,7 +59,10 @@ pub export fn main() u8 { call_the_closure(function_pointer, closure_data_pointer); } else { - unreachable; + const msg = @intToPtr([*:0]const u8, elements[1]); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + + return 0; } var ts2: std.os.timespec = undefined; diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index f6a3e38b42..57a6c6e624 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -502,11 +502,11 @@ pub enum RocCallResult { Failure(*mut c_char), } -impl Into> for RocCallResult { - fn into(self) -> Result { +impl From> for Result { + fn from(call_result: RocCallResult) -> Self { use RocCallResult::*; - match self { + match call_result { Success(value) => Ok(value), Failure(failure) => Err({ let msg = unsafe { @@ -529,11 +529,11 @@ impl Into> for RocCallResult { } } -impl<'a, T: Sized + Copy> Into> for &'a RocCallResult { - fn into(self) -> Result { +impl<'a, T: Sized + Copy> From<&'a RocCallResult> for Result { + fn from(call_result: &'a RocCallResult) -> Self { use RocCallResult::*; - match self { + match call_result { Success(value) => Ok(*value), Failure(failure) => Err({ let msg = unsafe { diff --git a/vendor/ena/src/unify/mod.rs b/vendor/ena/src/unify/mod.rs index 2210266f02..77f151f2e6 100644 --- a/vendor/ena/src/unify/mod.rs +++ b/vendor/ena/src/unify/mod.rs @@ -31,6 +31,7 @@ //! The best way to see how it is used is to read the `tests.rs` file; //! search for e.g. `UnitKey`. +use std::cmp::Ordering; use std::fmt::{self, Debug}; use std::marker; use std::ops::Range; @@ -361,17 +362,23 @@ impl UnificationTable { } }; self.redirect_root(new_rank, redirected, new_root, new_value); - } else if rank_a > rank_b { - // a has greater rank, so a should become b's parent, - // i.e., b should redirect to a. - self.redirect_root(rank_a, key_b, key_a, new_value); - } else if rank_a < rank_b { - // b has greater rank, so a should redirect to b. - self.redirect_root(rank_b, key_a, key_b, new_value); } else { - // If equal, redirect one to the other and increment the - // other's rank. - self.redirect_root(rank_a + 1, key_a, key_b, new_value); + match rank_a.cmp(&rank_b) { + Ordering::Greater => { + // a has greater rank, so a should become b's parent, + // i.e., b should redirect to a. + self.redirect_root(rank_a, key_b, key_a, new_value); + } + Ordering::Less => { + // b has greater rank, so a should redirect to b. + self.redirect_root(rank_b, key_a, key_b, new_value); + } + Ordering::Equal => { + // If equal, redirect one to the other and increment the + // other's rank. + self.redirect_root(rank_a + 1, key_a, key_b, new_value); + } + } } }