Merge branch 'trunk' into applied_tag_functions

This commit is contained in:
rvcas 2021-03-27 18:00:46 -04:00
commit 6e1f42f990
36 changed files with 1059 additions and 440 deletions

View file

@ -31,14 +31,12 @@ For any other OS, checkout the [Zig installation page](https://github.com/ziglan
### LLVM ### 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): 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)" 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 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 ### 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 # "pinning" ensures that homebrew doesn't update it automatically
$ brew pin llvm $ 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 ### LLVM installation on Windows

View file

@ -1,4 +1,4 @@
FROM rust:1.50-slim-buster FROM rust:1.51-slim-buster
WORKDIR /earthbuild WORKDIR /earthbuild
prep-debian: prep-debian:

View file

@ -90,7 +90,11 @@ pub fn build_app<'a>() -> App<'a> {
} }
pub fn docs(files: Vec<PathBuf>) { pub fn docs(files: Vec<PathBuf>) {
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<()> { pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {

View file

@ -54,7 +54,7 @@ mod cli_run {
) { ) {
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
if !compile_out.stderr.is_empty() { if !compile_out.stderr.is_empty() {
panic!(compile_out.stderr); panic!("{}", compile_out.stderr);
} }
assert!(compile_out.status.success()); assert!(compile_out.status.success());

View file

@ -1,11 +1,11 @@
interface Bool2 interface Bool
exposes [ not, and, or, xor, isEq, isNotEq ] exposes [ not, and, or, xor, isEq, isNotEq ]
imports [] imports []
## Returns #False when given #True, and vice versa. ## Returns `False` when given `True`, and vice versa.
not : [True, False] -> [True, False] 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` ## `a && b` is shorthand for `Bool.and a b`
## ##
@ -39,7 +39,7 @@ not : [True, False] -> [True, False]
and : Bool, Bool -> Bool 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`. ## `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 ## In some languages, `&&` and `||` are special-cased in the compiler to skip
## evaluating the expression after the operator under certain circumstances. ## 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 or : Bool, Bool -> Bool
## Exclusive or ## Exclusive or
xor : Bool, Bool -> Bool 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` ## `a == b` is shorthand for `Bool.isEq a b`
## ##

View file

@ -51,7 +51,7 @@ interface Num2
## ##
## In practice, these are rarely needed. It's most common to write ## In practice, these are rarely needed. It's most common to write
## number literals without any suffix. ## 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. ## 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. ## * 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.) ## * 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. ## * 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 ## A signed 8-bit integer, ranging from -128 to 127
I8 : Int @I8 I8 : Int [ @I8 ]
U8 : Int @U8 U8 : Int [ @U8 ]
U16 : Int @U16 U16 : Int [ @U16 ]
I16 : Int @I16 I16 : Int [ @I16 ]
U32 : Int @U32 U32 : Int [ @U32 ]
I32 : Int @I32 I32 : Int [ @I32 ]
I64 : Int @I64 I64 : Int [ @I64 ]
U64 : Int @U64 U64 : Int [ @U64 ]
I128 : Int @I128 I128 : Int [ @I128 ]
U128 : Int @U128 U128 : Int [ @U128 ]
Ilen : Int @Ilen Ilen : Int [ @Ilen ]
Nat : Int @Nat Nat : Int [ @Nat ]
## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values. ## 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 ## Bitwise
xor : Int -> Int -> Int xor : Int, Int -> Int
and : Int -> Int -> Int and : Int, Int -> Int
not : Int -> Int not : Int -> Int

View file

@ -1,7 +1,7 @@
interface Str2 interface Str
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 ] 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 [] imports []
## Types ## # Types
## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks ## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks
## to the basics. ## to the basics.

View file

@ -28,7 +28,7 @@ mod test_fmt {
assert_eq!(buf, expected) 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!( indoc!(
r#" r#"
when b is when b is
1 | 2 | 1 | 2 |
3 3
-> ->
4 4
5 | 6 | 7 -> 5 | 6 | 7 ->
8 8
9 9
| 10 -> 11 | 10 -> 11
12 | 13 -> 12 | 13 ->
when c is when c is
14 | 15 -> 16 14 | 15 -> 16
17 17
| 18 -> 19 | 18 -> 19
20 -> 21 20 -> 21
"# "#
), ),

View file

@ -90,9 +90,9 @@ pub enum OptLevel {
Optimize, Optimize,
} }
impl Into<OptimizationLevel> for OptLevel { impl From<OptLevel> for OptimizationLevel {
fn into(self) -> OptimizationLevel { fn from(level: OptLevel) -> Self {
match self { match level {
OptLevel::Normal => OptimizationLevel::None, OptLevel::Normal => OptimizationLevel::None,
OptLevel::Optimize => OptimizationLevel::Aggressive, OptLevel::Optimize => OptimizationLevel::Aggressive,
} }

View file

@ -8,9 +8,9 @@ pub enum RocCallResult<T> {
Failure(*mut c_char), Failure(*mut c_char),
} }
impl<T: Sized> Into<Result<T, String>> for RocCallResult<T> { impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
fn into(self) -> Result<T, String> { fn from(call_result: RocCallResult<T>) -> Self {
match self { match call_result {
Success(value) => Ok(value), Success(value) => Ok(value),
Failure(failure) => Err({ Failure(failure) => Err({
let raw = unsafe { CString::from_raw(failure) }; let raw = unsafe { CString::from_raw(failure) };

View file

@ -1,6 +1,6 @@
#![warn(clippy::all, clippy::dbg_macro)] #![warn(clippy::all, clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
use bumpalo::{collections::Vec, Bump}; use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode; use roc_builtins::bitcode;

View file

@ -12,8 +12,6 @@ mod helpers;
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] #[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod gen_num { mod gen_num {
//use roc_std::RocOrder;
#[test] #[test]
fn i64_values() { fn i64_values() {
assert_evals_to!("0", 0, i64); assert_evals_to!("0", 0, i64);

View file

@ -5,6 +5,7 @@ use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use tempfile::tempdir; use tempfile::tempdir;
#[allow(dead_code)]
fn promote_expr_to_module(src: &str) -> String { fn promote_expr_to_module(src: &str) -> String {
let mut buffer = String::from("app \"test\" provides [ main ] to \"./platform\"\n\nmain =\n"); 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 buffer
} }
#[allow(dead_code)]
pub fn helper<'a>( pub fn helper<'a>(
arena: &'a bumpalo::Bump, arena: &'a bumpalo::Bump,
src: &str, src: &str,

View file

@ -2265,6 +2265,10 @@ fn load_pkg_config<'a>(
))) )))
} }
Ok((ast::Module::Platform { header }, parser_state)) => { 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 // make a Pkg-Config module that ultimately exposes `main` to the host
let pkg_config_module_msg = fabricate_pkg_config_module( let pkg_config_module_msg = fabricate_pkg_config_module(
arena, arena,
@ -2275,6 +2279,7 @@ fn load_pkg_config<'a>(
module_ids.clone(), module_ids.clone(),
ident_ids_by_module.clone(), ident_ids_by_module.clone(),
&header, &header,
header_src,
pkg_module_timing, pkg_module_timing,
) )
.1; .1;
@ -2870,18 +2875,24 @@ fn send_header<'a>(
) )
} }
// TODO refactor so more logic is shared with `send_header` #[derive(Debug)]
#[allow(clippy::too_many_arguments)] struct PlatformHeaderInfo<'a> {
fn send_header_two<'a>(
arena: &'a Bump,
filename: PathBuf, filename: PathBuf,
is_root_module: bool, is_root_module: bool,
shorthand: &'a str, shorthand: &'a str,
header_src: &'a str,
app_module_id: ModuleId, app_module_id: ModuleId,
packages: &'a [Located<PackageEntry<'a>>], packages: &'a [Located<PackageEntry<'a>>],
provides: &'a [Located<ExposesEntry<'a, &'a str>>], provides: &'a [Located<ExposesEntry<'a, &'a str>>],
requires: &'a [Located<TypedIdent<'a>>], requires: &'a [Located<TypedIdent<'a>>],
imports: &'a [Located<ImportsEntry<'a>>], imports: &'a [Located<ImportsEntry<'a>>],
}
// 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>, parse_state: parser::State<'a>,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>, module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
@ -2889,6 +2900,18 @@ fn send_header_two<'a>(
) -> (ModuleId, Msg<'a>) { ) -> (ModuleId, Msg<'a>) {
use inlinable_string::InlinableString; 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 declared_name: InlinableString = "".into();
let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> = let mut imported: Vec<(QualifiedModuleName, Vec<Ident>, Region)> =
@ -3080,7 +3103,7 @@ fn send_header_two<'a>(
package_qualified_imported_modules, package_qualified_imported_modules,
deps_by_name, deps_by_name,
exposes: exposed, exposes: exposed,
header_src: "#builtin effect header", header_src,
parse_state, parse_state,
exposed_imports: scope, exposed_imports: scope,
module_timing, module_timing,
@ -3209,21 +3232,27 @@ fn fabricate_pkg_config_module<'a>(
module_ids: Arc<Mutex<PackageModuleIds<'a>>>, module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>, ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
header: &PlatformHeader<'a>, header: &PlatformHeader<'a>,
header_src: &'a str,
module_timing: ModuleTiming, module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) { ) -> (ModuleId, Msg<'a>) {
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] = let provides: &'a [Located<ExposesEntry<'a, &'a str>>] =
header.provides.clone().into_bump_slice(); 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( send_header_two(
arena, arena,
filename, info,
false,
shorthand,
app_module_id,
&[],
provides,
header.requires.clone().into_bump_slice(),
header.imports.clone().into_bump_slice(),
parse_state, parse_state,
module_ids, module_ids,
ident_ids_by_module, ident_ids_by_module,
@ -4114,7 +4143,7 @@ where
Ok(()) 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 roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator; use ven_pretty::DocAllocator;

View file

@ -117,21 +117,21 @@ impl From<InlinableString> for ModuleName {
} }
} }
impl Into<InlinableString> for ModuleName { impl From<ModuleName> for InlinableString {
fn into(self) -> InlinableString { fn from(name: ModuleName) -> Self {
self.0 name.0
} }
} }
impl<'a> Into<&'a InlinableString> for &'a ModuleName { impl<'a> From<&'a ModuleName> for &'a InlinableString {
fn into(self) -> &'a InlinableString { fn from(name: &'a ModuleName) -> Self {
&self.0 &name.0
} }
} }
impl<'a> Into<Box<str>> for ModuleName { impl From<ModuleName> for Box<str> {
fn into(self) -> Box<str> { fn from(name: ModuleName) -> Self {
self.0.to_string().into() name.0.to_string().into()
} }
} }
@ -197,9 +197,9 @@ impl<'a> From<String> for Lowercase {
} }
} }
impl Into<InlinableString> for Lowercase { impl From<Lowercase> for InlinableString {
fn into(self) -> InlinableString { fn from(lowercase: Lowercase) -> Self {
self.0 lowercase.0
} }
} }
@ -234,21 +234,21 @@ impl From<InlinableString> for Ident {
} }
} }
impl Into<InlinableString> for Ident { impl From<Ident> for InlinableString {
fn into(self) -> InlinableString { fn from(ident: Ident) -> Self {
self.0 ident.0
} }
} }
impl<'a> Into<&'a InlinableString> for &'a Ident { impl<'a> From<&'a Ident> for &'a InlinableString {
fn into(self) -> &'a InlinableString { fn from(ident: &'a Ident) -> Self {
&self.0 &ident.0
} }
} }
impl<'a> Into<Box<str>> for Ident { impl From<Ident> for Box<str> {
fn into(self) -> Box<str> { fn from(ident: Ident) -> Self {
self.0.to_string().into() ident.0.to_string().into()
} }
} }

View file

@ -1,6 +1,6 @@
#![warn(clippy::all, clippy::dbg_macro)] #![warn(clippy::all, clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
pub mod ident; pub mod ident;
pub mod low_level; pub mod low_level;

View file

@ -424,6 +424,8 @@ impl<'a> Procs<'a> {
is_self_recursive: bool, is_self_recursive: bool,
ret_var: Variable, ret_var: Variable,
) { ) {
let number_of_arguments = loc_args.len();
match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) { match patterns_to_when(env, layout_cache, loc_args, ret_var, loc_body) {
Ok((_, pattern_symbols, body)) => { Ok((_, pattern_symbols, body)) => {
// a named closure. Since these aren't specialized by the surrounding // a named closure. Since these aren't specialized by the surrounding
@ -444,17 +446,22 @@ impl<'a> Procs<'a> {
} }
Err(error) => { Err(error) => {
// If the function has invalid patterns in its arguments, let mut pattern_symbols = Vec::with_capacity_in(number_of_arguments, env.arena);
// 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 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 snapshot = env.subs.snapshot();
let cache_snapshot = layout_cache.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(_)); // This will not hold for programs with type errors
debug_assert!(is_valid, "unificaton failure for {:?}", proc_name); // 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 // if this is a closure, add the closure record argument
let pattern_symbols = if let CapturedSymbols::Captured(_) = captured_symbols { let pattern_symbols = if let CapturedSymbols::Captured(_) = captured_symbols {
@ -2131,9 +2139,7 @@ fn build_specialized_proc_adapter<'a>(
arg_layouts.push(layout); arg_layouts.push(layout);
} }
let ret_layout = layout_cache let ret_layout = layout_cache.from_var(&env.arena, ret_var, env.subs)?;
.from_var(&env.arena, ret_var, env.subs)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
build_specialized_proc( build_specialized_proc(
env.arena, env.arena,

View file

@ -1,6 +1,6 @@
#![warn(clippy::all, clippy::dbg_macro)] #![warn(clippy::all, clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
pub mod borrow; pub mod borrow;
pub mod expand_rc; pub mod expand_rc;

View file

@ -125,7 +125,7 @@ where
E: 'a, E: 'a,
{ {
move |_, state: State<'a>| { move |_, state: State<'a>| {
if state.column > min_indent { if state.column >= min_indent {
Ok((NoProgress, (), state)) Ok((NoProgress, (), state))
} else { } else {
Err((NoProgress, indent_problem(state.line, state.column), state)) Err((NoProgress, indent_problem(state.line, state.column), state))

View file

@ -16,16 +16,33 @@ use roc_region::all::{Located, Position, Region};
use crate::parser::Progress::{self, *}; 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>( pub fn test_parse_expr<'a>(
min_indent: u16, min_indent: u16,
arena: &'a bumpalo::Bump, arena: &'a bumpalo::Bump,
state: State<'a>, state: State<'a>,
) -> Result<Located<Expr<'a>>, EExpr<'a>> { ) -> Result<Located<Expr<'a>>, EExpr<'a>> {
let parser = space0_before_e( let parser = skip_second!(
move |a, s| parse_loc_expr(min_indent, a, s), space0_before_e(
min_indent, move |a, s| parse_loc_expr(min_indent, a, s),
EExpr::Space, min_indent,
EExpr::IndentStart, EExpr::Space,
EExpr::IndentStart,
),
expr_end()
); );
match parser.parse(arena, state) { match parser.parse(arena, state) {
@ -35,9 +52,27 @@ pub fn test_parse_expr<'a>(
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MultiBackpassing { pub struct ExprParseOptions {
Allow, /// Check for and accept multi-backpassing syntax
Disallow, /// 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>> { 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>( fn parse_loc_term<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
@ -167,10 +202,7 @@ fn parse_loc_term<'a>(
loc_expr_in_parens_etc_help(min_indent), loc_expr_in_parens_etc_help(min_indent),
loc!(specialize(EExpr::Str, string_literal_help())), loc!(specialize(EExpr::Str, string_literal_help())),
loc!(specialize(EExpr::Number, positive_number_literal_help())), loc!(specialize(EExpr::Number, positive_number_literal_help())),
loc!(specialize( loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
EExpr::Lambda,
closure_help(min_indent, multi_backpassing)
)),
loc!(record_literal_help(min_indent)), loc!(record_literal_help(min_indent)),
loc!(specialize(EExpr::List, list_literal_help(min_indent))), loc!(specialize(EExpr::List, list_literal_help(min_indent))),
loc!(map_with_arena!( loc!(map_with_arena!(
@ -183,17 +215,14 @@ fn parse_loc_term<'a>(
fn loc_possibly_negative_or_negated_term<'a>( fn loc_possibly_negative_or_negated_term<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
) -> impl Parser<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> impl Parser<'a, Located<Expr<'a>>, EExpr<'a>> {
one_of![ one_of![
|arena, state: State<'a>| { |arena, state: State<'a>| {
let initial = state; let initial = state;
let (_, (loc_op, loc_expr), state) = and!(loc!(unary_negate()), |a, s| parse_loc_term( let (_, (loc_op, loc_expr), state) = and!(loc!(unary_negate()), |a, s| parse_loc_term(
min_indent, min_indent, options, a, s
multi_backpassing,
a,
s
)) ))
.parse(arena, state)?; .parse(arena, state)?;
@ -205,7 +234,7 @@ fn loc_possibly_negative_or_negated_term<'a>(
loc!(specialize(EExpr::Number, number_literal_help())), loc!(specialize(EExpr::Number, number_literal_help())),
loc!(map_with_arena!( loc!(map_with_arena!(
and!(loc!(word1(b'!', EExpr::Start)), |a, s| { 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<_>, _)| { |arena: &'a Bump, (loc_op, loc_expr): (Located<_>, _)| {
Expr::UnaryOp( Expr::UnaryOp(
@ -216,7 +245,7 @@ fn loc_possibly_negative_or_negated_term<'a>(
)), )),
|arena, state| { |arena, state| {
// TODO use parse_loc_term_better // 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>( fn parse_expr_start<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
one_of![ one_of![
loc!(specialize( loc!(specialize(EExpr::If, if_expr_help(min_indent, options))),
EExpr::If,
if_expr_help(min_indent, multi_backpassing)
)),
loc!(specialize( loc!(specialize(
EExpr::When, EExpr::When,
when::expr_help(min_indent, multi_backpassing) when::expr_help(min_indent, options)
)), )),
loc!(specialize( loc!(specialize(EExpr::Lambda, closure_help(min_indent, options))),
EExpr::Lambda, loc!(move |a, s| parse_expr_operator_chain(min_indent, options, start, a, s)),
closure_help(min_indent, multi_backpassing)
)),
loc!(move |a, s| parse_expr_operator_chain(min_indent, multi_backpassing, start, a, s)),
fail_expr_start_e() fail_expr_start_e()
] ]
.parse(arena, state) .parse(arena, state)
@ -286,13 +309,13 @@ fn parse_expr_start<'a>(
fn parse_expr_operator_chain<'a>( fn parse_expr_operator_chain<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let (_, expr, state) = 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 initial = state;
let end = state.get_position(); let end = state.get_position();
@ -309,14 +332,7 @@ fn parse_expr_operator_chain<'a>(
end, end,
}; };
parse_expr_end( parse_expr_end(min_indent, options, start, expr_state, arena, state)
min_indent,
multi_backpassing,
start,
expr_state,
arena,
state,
)
} }
} }
} }
@ -688,7 +704,7 @@ struct DefState<'a> {
} }
fn parse_defs_end<'a>( fn parse_defs_end<'a>(
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
mut def_state: DefState<'a>, mut def_state: DefState<'a>,
arena: &'a Bump, arena: &'a Bump,
@ -743,7 +759,7 @@ fn parse_defs_end<'a>(
loc_def_expr, 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)) => { Ok((_, BinOp::HasType, state)) => {
let (_, ann_type, state) = specialize( let (_, ann_type, state) = specialize(
@ -765,7 +781,7 @@ fn parse_defs_end<'a>(
ann_type, 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)), _ => Ok((MadeProgress, def_state, initial)),
@ -774,7 +790,7 @@ fn parse_defs_end<'a>(
} }
fn parse_defs_expr<'a>( fn parse_defs_expr<'a>(
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
def_state: DefState<'a>, def_state: DefState<'a>,
arena: &'a Bump, arena: &'a Bump,
@ -782,7 +798,7 @@ fn parse_defs_expr<'a>(
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let min_indent = start.col; 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), Err(bad) => Err(bad),
Ok((_, def_state, state)) => { Ok((_, def_state, state)) => {
// this is no def, because there is no `=` or `:`; parse as an expr // 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>( fn parse_expr_operator<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
mut expr_state: ExprState<'a>, mut expr_state: ExprState<'a>,
loc_op: Located<BinOp>, loc_op: Located<BinOp>,
@ -835,8 +851,7 @@ fn parse_expr_operator<'a>(
BinOp::Minus if expr_state.end != op_start && op_end == new_start => { BinOp::Minus if expr_state.end != op_start && op_end == new_start => {
// negative terms // negative terms
let (_, negated_expr, state) = let (_, negated_expr, state) = parse_loc_term(min_indent, options, arena, state)?;
parse_loc_term(min_indent, multi_backpassing, arena, state)?;
let new_end = state.get_position(); let new_end = state.get_position();
let arg = numeric_negate_expression( let arg = numeric_negate_expression(
@ -859,14 +874,7 @@ fn parse_expr_operator<'a>(
expr_state.spaces_after = spaces; expr_state.spaces_after = spaces;
expr_state.end = new_end; expr_state.end = new_end;
parse_expr_end( parse_expr_end(min_indent, options, start, expr_state, arena, state)
min_indent,
multi_backpassing,
start,
expr_state,
arena,
state,
)
} }
BinOp::Assignment => { BinOp::Assignment => {
let expr_region = expr_state.expr.region; let expr_region = expr_state.expr.region;
@ -915,7 +923,7 @@ fn parse_expr_operator<'a>(
spaces_after: &[], spaces_after: &[],
}; };
parse_defs_expr(multi_backpassing, start, def_state, arena, state) parse_defs_expr(options, start, def_state, arena, state)
} }
BinOp::Backpassing => { BinOp::Backpassing => {
let expr_region = expr_state.expr.region; let expr_region = expr_state.expr.region;
@ -1062,11 +1070,9 @@ fn parse_expr_operator<'a>(
spaces_after: &[], 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) _ => match loc_possibly_negative_or_negated_term(min_indent, options).parse(arena, state) {
.parse(arena, state)
{
Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)),
Ok((_, mut new_expr, state)) => { Ok((_, mut new_expr, state)) => {
let new_end = state.get_position(); let new_end = state.get_position();
@ -1104,14 +1110,7 @@ fn parse_expr_operator<'a>(
expr_state.spaces_after = spaces; expr_state.spaces_after = spaces;
// TODO new start? // TODO new start?
parse_expr_end( parse_expr_end(min_indent, options, start, expr_state, arena, state)
min_indent,
multi_backpassing,
start,
expr_state,
arena,
state,
)
} }
} }
} }
@ -1124,7 +1123,7 @@ fn parse_expr_operator<'a>(
fn parse_expr_end<'a>( fn parse_expr_end<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
start: Position, start: Position,
mut expr_state: ExprState<'a>, mut expr_state: ExprState<'a>,
arena: &'a Bump, arena: &'a Bump,
@ -1132,7 +1131,7 @@ fn parse_expr_end<'a>(
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let parser = skip_first!( let parser = skip_first!(
crate::blankspace::check_indent(min_indent, EExpr::IndentEnd), 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) { match parser.parse(arena, state) {
@ -1163,14 +1162,7 @@ fn parse_expr_end<'a>(
expr_state.end = new_end; expr_state.end = new_end;
expr_state.spaces_after = new_spaces; expr_state.spaces_after = new_spaces;
parse_expr_end( parse_expr_end(min_indent, options, start, expr_state, arena, state)
min_indent,
multi_backpassing,
start,
expr_state,
arena,
state,
)
} }
} }
} }
@ -1183,19 +1175,12 @@ fn parse_expr_end<'a>(
expr_state.consume_spaces(arena); expr_state.consume_spaces(arena);
expr_state.initial = before_op; expr_state.initial = before_op;
parse_expr_operator( parse_expr_operator(
min_indent, min_indent, options, start, expr_state, loc_op, arena, state,
multi_backpassing,
start,
expr_state,
loc_op,
arena,
state,
) )
} }
Err((NoProgress, _, mut state)) => { Err((NoProgress, _, mut state)) => {
// try multi-backpassing // 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.bytes = &state.bytes[1..];
state.column += 1; state.column += 1;
@ -1256,6 +1241,12 @@ fn parse_expr_end<'a>(
Ok((MadeProgress, ret, state)) 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 { } else {
// roll back space parsing // roll back space parsing
let state = expr_state.initial; let state = expr_state.initial;
@ -1273,7 +1264,15 @@ fn parse_loc_expr<'a>(
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> ParseResult<'a, Located<Expr<'a>>, 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>( 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, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> ParseResult<'a, Located<Expr<'a>>, 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>( fn parse_loc_expr_with_options<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> { ) -> ParseResult<'a, Located<Expr<'a>>, EExpr<'a>> {
let start = state.get_position(); 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. /// 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<Def<'a>>>, E
space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?;
let start = state.get_position(); 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) = let (_, final_space, state) =
space0_e(start.col, EExpr::Space, EExpr::IndentEnd).parse(arena, 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<Def<'a>>>, E
fn closure_help<'a>( fn closure_help<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, ELambda<'a>> { ) -> impl Parser<'a, Expr<'a>, ELambda<'a>> {
map_with_arena!( map_with_arena!(
skip_first!( skip_first!(
@ -1510,7 +1522,7 @@ fn closure_help<'a>(
// Parse the body // Parse the body
space0_before_e( space0_before_e(
specialize_ref(ELambda::Body, move |arena, state| { 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, min_indent,
ELambda::Space, ELambda::Space,
@ -1535,7 +1547,7 @@ mod when {
/// Parser for when expressions. /// Parser for when expressions.
pub fn expr_help<'a>( pub fn expr_help<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, When<'a>> { ) -> impl Parser<'a, Expr<'a>, When<'a>> {
then( then(
and!( and!(
@ -1543,7 +1555,7 @@ mod when {
skip_second!( skip_second!(
space0_around_ee( space0_around_ee(
specialize_ref(When::Condition, move |arena, state| { 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, min_indent,
When::Space, When::Space,
@ -1566,7 +1578,7 @@ mod when {
// Everything in the branches must be indented at least as much as the case itself. // Everything in the branches must be indented at least as much as the case itself.
let min_indent = case_indent; 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(( Ok((
progress.or(p1), 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>> { fn branches<'a>(
move |arena, state| { 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); 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.) // 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. // 2. Parse the other branches. Their indentation levels must be == the first branch's.
let (_, (loc_first_patterns, loc_first_guard), state) = let (_, ((pattern_indent_level, loc_first_patterns), loc_first_guard), mut state) =
branch_alternatives(min_indent).parse(arena, state)?; branch_alternatives(min_indent, options, None).parse(arena, state)?;
let loc_first_pattern = loc_first_patterns.first().unwrap(); let original_indent = pattern_indent_level;
let original_indent = loc_first_pattern.region.start_col;
let indented_more = original_indent + 1; state.indent_col = pattern_indent_level;
// Parse the first "->" and the expression after it. // Parse the first "->" and the expression after it.
let (_, loc_first_expr, mut state) = 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. // Record this as the first branch, then optionally parse additional branches.
branches.push(arena.alloc(WhenBranch { branches.push(arena.alloc(WhenBranch {
@ -1613,19 +1630,21 @@ mod when {
let branch_parser = map!( let branch_parser = map!(
and!( and!(
then( then(
branch_alternatives(min_indent), branch_alternatives(min_indent, options, Some(pattern_indent_level)),
move |_arena, state, _, (loc_patterns, loc_guard)| { move |_arena, state, _, ((indent_col, loc_patterns), loc_guard)| {
match alternatives_indented_correctly(&loc_patterns, original_indent) { if pattern_indent_level == indent_col {
Ok(()) => Ok((MadeProgress, (loc_patterns, loc_guard), state)), Ok((MadeProgress, (loc_patterns, loc_guard), state))
Err(indent) => Err(( } else {
let indent = pattern_indent_level - indent_col;
Err((
MadeProgress, MadeProgress,
When::PatternAlignment(indent, state.line, state.column), When::PatternAlignment(indent, state.line, state.column),
state, state,
)), ))
} }
}, },
), ),
branch_result(indented_more) branch_result(original_indent + 1)
), ),
|((patterns, guard), expr)| { |((patterns, guard), expr)| {
let patterns: Vec<'a, _> = patterns; 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. /// Parsing alternative patterns in when branches.
fn branch_alternatives<'a>( fn branch_alternatives<'a>(
min_indent: u16, min_indent: u16,
) -> impl Parser<'a, (Vec<'a, Located<Pattern<'a>>>, Option<Located<Expr<'a>>>), When<'a>> { options: ExprParseOptions,
pattern_indent_level: Option<u16>,
) -> impl Parser<
'a,
(
(Col, Vec<'a, Located<Pattern<'a>>>),
Option<Located<Expr<'a>>>,
),
When<'a>,
> {
let options = ExprParseOptions {
check_for_arrow: false,
..options
};
and!( and!(
sep_by1(word1(b'|', When::Bar), |arena, state| { branch_alternatives_help(min_indent, pattern_indent_level),
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,
))
}),
one_of![ one_of![
map!( map!(
skip_first!( skip_first!(
@ -1696,7 +1711,7 @@ mod when {
// TODO we should require space before the expression but not after // TODO we should require space before the expression but not after
space0_around_ee( space0_around_ee(
specialize_ref(When::IfGuard, move |arena, state| { 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, min_indent,
When::Space, When::Space,
@ -1711,22 +1726,103 @@ mod when {
) )
} }
/// Check if alternatives of a when branch are indented correctly. fn branch_single_alternative<'a>(
fn alternatives_indented_correctly<'a>( min_indent: u16,
loc_patterns: &'a Vec<'a, Located<Pattern<'a>>>, ) -> impl Parser<'a, Located<Pattern<'a>>, When<'a>> {
original_indent: u16, move |arena, state| {
) -> Result<(), u16> { let (_, spaces, state) =
let (first, rest) = loc_patterns.split_first().unwrap(); backtrackable(space0_e(min_indent, When::Space, When::IndentPattern))
let first_indented_correctly = first.region.start_col == original_indent; .parse(arena, state)?;
if first_indented_correctly {
for when_pattern in rest.iter() { let (_, loc_pattern, state) = space0_after_e(
if when_pattern.region.start_col < original_indent { specialize(When::Pattern, crate::pattern::loc_pattern_help(min_indent)),
return Err(original_indent - when_pattern.region.start_col); 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<u16>,
) -> impl Parser<'a, (Col, Vec<'a, Located<Pattern<'a>>>), 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>( fn if_expr_help<'a>(
min_indent: u16, min_indent: u16,
multi_backpassing: MultiBackpassing, options: ExprParseOptions,
) -> impl Parser<'a, Expr<'a>, If<'a>> { ) -> impl Parser<'a, Expr<'a>, If<'a>> {
move |arena: &'a Bump, state| { move |arena: &'a Bump, state| {
let (_, _, state) = parser::keyword_e(keyword::IF, If::If).parse(arena, 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( let (_, else_branch, state) = space0_before_e(
specialize_ref(If::ElseBranch, move |arena, state| { 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, min_indent,
If::Space, If::Space,

View file

@ -42,15 +42,15 @@ pub enum PackageOrPath<'a> {
#[derive(Clone, PartialEq, Eq, Debug, Hash)] #[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub struct ModuleName<'a>(&'a str); pub struct ModuleName<'a>(&'a str);
impl<'a> Into<&'a str> for ModuleName<'a> { impl<'a> From<ModuleName<'a>> for &'a str {
fn into(self) -> &'a str { fn from(name: ModuleName<'a>) -> Self {
self.0 name.0
} }
} }
impl<'a> Into<InlinableString> for ModuleName<'a> { impl<'a> From<ModuleName<'a>> for InlinableString {
fn into(self) -> InlinableString { fn from(name: ModuleName<'a>) -> InlinableString {
self.0.into() name.0.into()
} }
} }

View file

@ -185,7 +185,7 @@ pub enum SyntaxError<'a> {
ReservedKeyword(Region), ReservedKeyword(Region),
ArgumentsBeforeEquals(Region), ArgumentsBeforeEquals(Region),
NotYetImplemented(String), NotYetImplemented(String),
TODO, Todo,
Type(Type<'a>), Type(Type<'a>),
Pattern(EPattern<'a>), Pattern(EPattern<'a>),
Expr(EExpr<'a>), Expr(EExpr<'a>),
@ -381,6 +381,7 @@ pub type Col = u16;
pub enum EExpr<'a> { pub enum EExpr<'a> {
Start(Row, Col), Start(Row, Col),
End(Row, Col), End(Row, Col),
BadExprEnd(Row, Col),
Space(BadInputError, Row, Col), Space(BadInputError, Row, Col),
Dot(Row, Col), Dot(Row, Col),
@ -930,8 +931,8 @@ where
state = next_state; state = next_state;
buf.push(next_output); buf.push(next_output);
} }
Err((element_progress, fail, state)) => { Err((_, fail, state)) => {
return Err((element_progress, fail, state)); return Err((MadeProgress, fail, state));
} }
} }
} }

View file

@ -62,8 +62,8 @@ pub fn loc_pattern_help<'a>(
EPattern::Record, EPattern::Record,
crate::pattern::record_pattern_help(min_indent) crate::pattern::record_pattern_help(min_indent)
)), )),
loc!(number_pattern_help()),
loc!(string_pattern_help()), loc!(string_pattern_help()),
loc!(number_pattern_help())
) )
} }

View file

@ -2550,6 +2550,247 @@ mod test_parse {
assert_eq!(Ok(expected), actual); 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] #[test]
fn when_with_records() { fn when_with_records() {
let arena = Bump::new(); let arena = Bump::new();
@ -2599,6 +2840,47 @@ mod test_parse {
assert_eq!(Ok(expected), actual); 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] #[test]
fn when_with_alternative_patterns() { fn when_with_alternative_patterns() {
let arena = Bump::new(); let arena = Bump::new();
@ -2620,9 +2902,9 @@ mod test_parse {
let pattern2_alt = let pattern2_alt =
Pattern::SpaceBefore(arena.alloc(StrLiteral(PlainLine("bar"))), newlines); Pattern::SpaceBefore(arena.alloc(StrLiteral(PlainLine("bar"))), newlines);
let loc_pattern2 = Located::new(2, 2, 1, 6, pattern2); 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 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 { let branch2 = &*arena.alloc(WhenBranch {
patterns: arena.alloc([loc_pattern2, loc_pattern2_alt]), patterns: arena.alloc([loc_pattern2, loc_pattern2_alt]),
value: loc_expr2, value: loc_expr2,
@ -2642,7 +2924,7 @@ mod test_parse {
when x is when x is
"blah" | "blop" -> 1 "blah" | "blop" -> 1
"foo" | "foo" |
"bar" -> 2 "bar" -> 2
"# "#
), ),
); );

View file

@ -272,22 +272,31 @@ fn to_expr_report<'a>(
]) ])
.indent(4), .indent(4),
])], ])],
b"->" => vec![alloc.stack(vec![ b"->" => match context {
alloc.concat(vec![ Context::InNode(Node::WhenBranch, _row, _col, _) => {
alloc.reflow("The arrow "), return to_unexpected_arrow_report(
alloc.parser_suggestion("->"), alloc, filename, *row, *col, start_row, start_col,
alloc.reflow(" is only used to define cases in a "), );
alloc.keyword("when"), }
alloc.reflow("."), _ => {
]), vec![alloc.stack(vec![
alloc alloc.concat(vec![
.vcat(vec![ alloc.reflow("The arrow "),
alloc.text("when color is"), alloc.parser_suggestion("->"),
alloc.text("Red -> \"stop!\"").indent(4), alloc.reflow(" is only used to define cases in a "),
alloc.text("Green -> \"go!\"").indent(4), alloc.keyword("when"),
]) alloc.reflow("."),
.indent(4), ]),
])], alloc
.vcat(vec![
alloc.text("when color is"),
alloc.text("Red -> \"stop!\"").indent(4),
alloc.text("Green -> \"go!\"").indent(4),
])
.indent(4),
])]
}
},
b"!" => vec![ b"!" => vec![
alloc.reflow("The boolean negation operator "), alloc.reflow("The boolean negation operator "),
alloc.parser_suggestion("!"), alloc.parser_suggestion("!"),
@ -458,6 +467,27 @@ fn to_expr_report<'a>(
*col, *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) => { EExpr::Colon(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col); let surroundings = Region::from_rows_cols(start_row, start_col, *row, *col);
let region = Region::from_row_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) => { 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(',')) => { Next::Other(Some(',')) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_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_row: Row,
start_col: Col, start_col: Col,
message: RocDocBuilder<'a>, 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> { ) -> Report<'a> {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col); 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![ let doc = alloc.stack(vec![
alloc.concat(vec![ alloc.concat(vec![
alloc.reflow(r"I was partway through parsing a "), alloc.reflow(r"I am parsing a "),
alloc.keyword("when"), 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), 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), note_for_when_error(alloc),
]); ]);
Report { Report {
filename, filename,
doc, doc,
title: "UNFINISHED WHEN".to_string(), title: "UNEXPECTED ARROW".to_string(),
} }
} }

View file

@ -4967,7 +4967,6 @@ mod test_reporting {
#[test] #[test]
fn empty_or_pattern() { fn empty_or_pattern() {
// this should get better with time
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
@ -4981,29 +4980,16 @@ mod test_reporting {
), ),
indoc!( indoc!(
r#" r#"
MISSING EXPRESSION UNFINISHED PATTERN
I am partway through parsing a definition, but I got stuck here: I just started parsing a pattern, but I got stuck here:
1 when Just 4 is
2 Just 4 | -> 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#" r#"
when 4 is when 4 is
5 -> 2 5 -> 2
_ -> 2 2 -> 2
"# "#
), ),
indoc!( indoc!(
r#" 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: #[test]
fn when_over_indented_underscore() {
3 _ -> 2 report_problem_as(
^ indoc!(
r#"
I suspect this is a pattern that is not indented enough? (by 2 spaces) 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. Note: Here is an example of a valid `when` expression for reference.
when List.first plants is when List.first plants is
Ok n -> Ok n ->
n n
Err _ -> Err _ ->
200 200
Notice the indentation. All patterns are aligned, and each branch is Notice the indentation. All patterns are aligned, and each branch is
indented a bit more than the corresponding pattern. That is important! indented a bit more than the corresponding pattern. That is important!
"# "#
@ -5809,21 +5877,18 @@ mod test_reporting {
), ),
indoc!( indoc!(
r#" r#"
MISSING FINAL EXPRESSION UNKNOWN OPERATOR
I am partway through parsing a definition's final expression, but I This looks like an operator, but it's not one I recognize!
got stuck here:
1 main = 5 -> 3 1 main = 5 -> 3
^ ^^
This definition is missing a final expression. A nested definition The arrow -> is only used to define cases in a `when`.
must be followed by either another definition, or an expression
when color is
x = 4 Red -> "stop!"
y = 2 Green -> "go!"
x + y
"# "#
), ),
) )

View file

@ -2258,3 +2258,21 @@ fn backpassing_result() {
i64 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
);
}

View file

@ -93,9 +93,9 @@ impl VarStore {
} }
} }
impl Into<Variable> for VarStore { impl From<VarStore> for Variable {
fn into(self) -> Variable { fn from(store: VarStore) -> Self {
Variable(self.next) Variable(store.next)
} }
} }
@ -139,9 +139,9 @@ impl fmt::Debug for OptVariable {
} }
} }
impl Into<Option<Variable>> for OptVariable { impl From<OptVariable> for Option<Variable> {
fn into(self) -> Option<Variable> { fn from(opt_var: OptVariable) -> Self {
self.into_variable() opt_var.into_variable()
} }
} }
@ -180,9 +180,9 @@ impl Variable {
} }
} }
impl Into<OptVariable> for Variable { impl From<Variable> for OptVariable {
fn into(self) -> OptVariable { fn from(var: Variable) -> Self {
OptVariable(self.0) OptVariable(var.0)
} }
} }
@ -483,9 +483,9 @@ impl fmt::Debug for Rank {
} }
} }
impl Into<usize> for Rank { impl From<Rank> for usize {
fn into(self) -> usize { fn from(rank: Rank) -> Self {
self.0 rank.0
} }
} }

View file

@ -56,6 +56,29 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
modules: files_docs, 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 // Register handlebars template
let mut handlebars = handlebars::Handlebars::new(); let mut handlebars = handlebars::Handlebars::new();
handlebars handlebars
@ -75,7 +98,7 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
.expect("TODO gracefully handle writing file failing"); .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( pub fn files_to_documentations(

View file

@ -66,16 +66,14 @@ a:hover {
} }
.pkg-and-logo { .pkg-and-logo {
min-width: 0; min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
/* necessary for text-overflow: ellipsis to work in descendants */
display: flex; display: flex;
align-items: center; align-items: center;
height: 100%; height: 100%;
background-color: var(--top-bar-bg); background-color: var(--top-bar-bg);
} }
.pkg-and-logo a, .pkg-and-logo a, .pkg-and-logo a:visited {
.pkg-and-logo a:visited {
color: var(--top-bar-fg); color: var(--top-bar-fg);
} }
@ -84,18 +82,11 @@ a:hover {
text-decoration: none; text-decoration: none;
} }
.main-container {
min-width: 0;
/* necessary for text-overflow: ellipsis to work in descendants */
}
.search-button { .search-button {
flex-shrink: 0; flex-shrink: 0; /* always shrink the package name before these; they have a relatively constrained length */
/* always shrink the package name before these; they have a relatively constrained length */
padding: 12px 18px; padding: 12px 18px;
margin-right: 42px; margin-right: 42px;
display: none; display: none; /* only show this in the mobile view */
/* only show this in the mobile view */
} }
.version { .version {
@ -127,6 +118,8 @@ main {
line-height: 1.85em; line-height: 1.85em;
margin-top: 2px; margin-top: 2px;
padding: 48px; padding: 48px;
min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
} }
#sidebar-nav { #sidebar-nav {
@ -160,13 +153,11 @@ main {
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
flex-wrap: nowrap; flex-wrap: nowrap;
flex-grow: 1;
box-sizing: border-box; box-sizing: border-box;
font-family: var(--font-sans); font-family: var(--font-sans);
font-size: 24px; font-size: 24px;
height: 100%; height: 100%;
min-width: 0; min-width: 0; /* necessary for text-overflow: ellipsis to work in descendants */
/* necessary for text-overflow: ellipsis to work in descendants */
} }
.top-header-triangle { .top-header-triangle {
@ -181,8 +172,8 @@ main {
} }
p { p {
overflow-wrap: break-word; overflow-wrap: break-word;
margin: 24px 0; margin: 24px 0;
} }
footer { footer {
@ -240,8 +231,7 @@ footer p {
margin-bottom: 48px; margin-bottom: 48px;
} }
.module-name a, .module-name a, .module-name a:visited {
.module-name a:visited {
color: inherit; color: inherit;
} }
@ -259,8 +249,7 @@ footer p {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
a, a, a:visited {
a:visited {
color: var(--link-color); color: var(--link-color);
} }
@ -325,20 +314,19 @@ pre code {
height: 0; height: 0;
} }
#module-search, #module-search, #module-search:focus {
#module-search:focus {
opacity: 1; opacity: 1;
padding: 12px 16px; padding: 12px 16px;
height: 48px; height: 48px;
} }
/* Show the "Search" label link when the text input has a placeholder */ /* 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; display: flex;
} }
/* Hide the "Search" label link when the text input has focus */ /* Hide the "Search" label link when the text input has focus */
#module-search:focus+#search-link { #module-search:focus + #search-link {
display: none; 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 { .search-button {
display: block; display: block; /* This is only visible in mobile. */
/* This is only visible in mobile. */
} }
.top-header { .top-header {
justify-content: space-between;
width: auto; width: auto;
} }
@ -443,21 +431,19 @@ pre code {
} }
main { main {
grid-column-start: none;
grid-column-end: none;
grid-row-start: above-footer;
grid-row-end: above-footer;
padding: 18px; padding: 18px;
font-size: 16px; font-size: 16px;
} }
.container { #sidebar-nav {
margin: 0; grid-column-start: none;
min-width: 320px; grid-column-end: none;
max-width: 100%; grid-row-start: sidebar;
} grid-row-end: sidebar;
.content {
flex-direction: column;
}
.sidebar {
margin-top: 0; margin-top: 0;
padding-left: 0; padding-left: 0;
width: auto; width: auto;
@ -478,12 +464,30 @@ pre code {
font-size: 16px; font-size: 16px;
} }
.top-header { body {
justify-content: space-between; 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 { .top-header-triangle {
/* Display the sidebar below <main> without affecting tab index */ display: none;
flex-direction: column-reverse;
} }
}
.pkg-and-logo {
width: 100%;
}
.pkg-full-name {
flex-grow: 1;
}
.pkg-full-name a {
padding-top: 24px;
padding-bottom: 12px;
}
}

View file

@ -6,9 +6,9 @@
<title>The Roc Programming Language</title> <title>The Roc Programming Language</title>
<meta name="description" content="A language for building fast, reliable software."> <meta name="description" content="A language for building fast, reliable software.">
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<link rel="icon" href="/favicon.svg"> <link rel="icon" href="favicon.svg">
<script type="text/javascript" src="/search.js" defer></script> <script type="text/javascript" src="search.js" defer></script>
<link rel="stylesheet" href="/styles.css"> <link rel="stylesheet" href="styles.css">
</head> </head>
<body> <body>

View file

@ -96,14 +96,12 @@ pub fn expr2_to_markup<'a, 'b>(
new_markup_node(text, node_id, HighlightStyle::Variable, markup_node_pool) new_markup_node(text, node_id, HighlightStyle::Variable, markup_node_pool)
} }
Expr2::List { elems, .. } => { Expr2::List { elems, .. } => {
let mut children_ids = Vec::new(); let mut children_ids = vec![new_markup_node(
children_ids.push(new_markup_node(
"[ ".to_string(), "[ ".to_string(),
node_id, node_id,
HighlightStyle::Bracket, HighlightStyle::Bracket,
markup_node_pool, markup_node_pool,
)); )];
for (idx, node_id) in elems.iter_node_ids().enumerate() { for (idx, node_id) in elems.iter_node_ids().enumerate() {
let sub_expr2 = env.pool.get(node_id); 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) markup_node_pool.add(list_node)
} }
Expr2::Record { fields, .. } => { Expr2::Record { fields, .. } => {
let mut children_ids = Vec::new(); let mut children_ids = vec![new_markup_node(
children_ids.push(new_markup_node(
"{ ".to_string(), "{ ".to_string(),
node_id, node_id,
HighlightStyle::Bracket, HighlightStyle::Bracket,
markup_node_pool, markup_node_pool,
)); )];
for (idx, field_node_id) in fields.iter_node_ids().enumerate() { 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); let (pool_field_name, _, sub_expr2_node_id) = env.pool.get(field_node_id);

View file

@ -1,6 +1,6 @@
#![warn(clippy::all, clippy::dbg_macro)] #![warn(clippy::all, clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
#[cfg_attr(test, macro_use)] #[cfg_attr(test, macro_use)]
extern crate indoc; extern crate indoc;

View file

@ -59,7 +59,10 @@ pub export fn main() u8 {
call_the_closure(function_pointer, closure_data_pointer); call_the_closure(function_pointer, closure_data_pointer);
} else { } 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; var ts2: std.os.timespec = undefined;

View file

@ -502,11 +502,11 @@ pub enum RocCallResult<T> {
Failure(*mut c_char), Failure(*mut c_char),
} }
impl<T: Sized> Into<Result<T, &'static str>> for RocCallResult<T> { impl<T: Sized> From<RocCallResult<T>> for Result<T, &'static str> {
fn into(self) -> Result<T, &'static str> { fn from(call_result: RocCallResult<T>) -> Self {
use RocCallResult::*; use RocCallResult::*;
match self { match call_result {
Success(value) => Ok(value), Success(value) => Ok(value),
Failure(failure) => Err({ Failure(failure) => Err({
let msg = unsafe { let msg = unsafe {
@ -529,11 +529,11 @@ impl<T: Sized> Into<Result<T, &'static str>> for RocCallResult<T> {
} }
} }
impl<'a, T: Sized + Copy> Into<Result<T, &'a str>> for &'a RocCallResult<T> { impl<'a, T: Sized + Copy> From<&'a RocCallResult<T>> for Result<T, &'a str> {
fn into(self) -> Result<T, &'a str> { fn from(call_result: &'a RocCallResult<T>) -> Self {
use RocCallResult::*; use RocCallResult::*;
match self { match call_result {
Success(value) => Ok(*value), Success(value) => Ok(*value),
Failure(failure) => Err({ Failure(failure) => Err({
let msg = unsafe { let msg = unsafe {

View file

@ -31,6 +31,7 @@
//! The best way to see how it is used is to read the `tests.rs` file; //! The best way to see how it is used is to read the `tests.rs` file;
//! search for e.g. `UnitKey`. //! search for e.g. `UnitKey`.
use std::cmp::Ordering;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use std::marker; use std::marker;
use std::ops::Range; use std::ops::Range;
@ -361,17 +362,23 @@ impl<S: UnificationStore> UnificationTable<S> {
} }
}; };
self.redirect_root(new_rank, redirected, new_root, new_value); 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 { } else {
// If equal, redirect one to the other and increment the match rank_a.cmp(&rank_b) {
// other's rank. Ordering::Greater => {
self.redirect_root(rank_a + 1, key_a, key_b, new_value); // 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);
}
}
} }
} }