mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 21:39:07 +00:00
Merge branch 'main' of github.com:roc-lang/roc into rust-1-77-2-upgrade
This commit is contained in:
commit
fb7fa99b2c
631 changed files with 21948 additions and 16419 deletions
|
@ -50,8 +50,15 @@ pub fn legacy_host_file(target: Target, platform_main_roc: &Path) -> PathBuf {
|
|||
.replace(roc_linker::PRECOMPILED_HOST_EXT, lib_ext);
|
||||
|
||||
let lib_path = platform_main_roc.with_file_name(file_name);
|
||||
|
||||
let default_host_path: PathBuf = platform_main_roc
|
||||
.with_file_name("libhost")
|
||||
.with_extension(lib_ext);
|
||||
|
||||
if lib_path.exists() {
|
||||
lib_path
|
||||
} else if default_host_path.exists() {
|
||||
default_host_path
|
||||
} else {
|
||||
let obj_ext = target.object_file_ext();
|
||||
lib_path.with_extension(obj_ext)
|
||||
|
@ -1132,6 +1139,8 @@ fn link_macos(
|
|||
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/roc-lang/roc/pull/554#discussion_r496370840
|
||||
"-framework",
|
||||
"Security",
|
||||
"-framework",
|
||||
"SystemConfiguration",
|
||||
// Output
|
||||
"-o",
|
||||
output_path.to_str().unwrap(), // app
|
||||
|
|
|
@ -735,9 +735,14 @@ pub fn build_file<'a>(
|
|||
let compilation_start = Instant::now();
|
||||
|
||||
// Step 1: compile the app and generate the .o file
|
||||
let loaded =
|
||||
roc_load::load_and_monomorphize(arena, app_module_path.clone(), roc_cache_dir, load_config)
|
||||
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
|
||||
let loaded = roc_load::load_and_monomorphize(
|
||||
arena,
|
||||
app_module_path.clone(),
|
||||
None,
|
||||
roc_cache_dir,
|
||||
load_config,
|
||||
)
|
||||
.map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
|
||||
|
||||
build_loaded_file(
|
||||
arena,
|
||||
|
@ -1187,6 +1192,7 @@ fn build_and_preprocess_host_lowlevel(
|
|||
pub fn check_file<'a>(
|
||||
arena: &'a Bump,
|
||||
roc_file_path: PathBuf,
|
||||
opt_main_path: Option<PathBuf>,
|
||||
emit_timings: bool,
|
||||
roc_cache_dir: RocCacheDir<'_>,
|
||||
threading: Threading,
|
||||
|
@ -1209,8 +1215,13 @@ pub fn check_file<'a>(
|
|||
threading,
|
||||
exec_mode: ExecutionMode::Check,
|
||||
};
|
||||
let mut loaded =
|
||||
roc_load::load_and_typecheck(arena, roc_file_path, roc_cache_dir, load_config)?;
|
||||
let mut loaded = roc_load::load_and_typecheck(
|
||||
arena,
|
||||
roc_file_path,
|
||||
opt_main_path,
|
||||
roc_cache_dir,
|
||||
load_config,
|
||||
)?;
|
||||
|
||||
let buf = &mut String::with_capacity(1024);
|
||||
|
||||
|
@ -1292,6 +1303,7 @@ pub fn build_str_test<'a>(
|
|||
PathBuf::from("valgrind_test.roc"),
|
||||
app_module_source,
|
||||
app_module_path.to_path_buf(),
|
||||
None,
|
||||
roc_cache_dir,
|
||||
load_config,
|
||||
)
|
||||
|
|
|
@ -80,7 +80,7 @@ It's one thing to actually write these functions, it's _another_ thing to let th
|
|||
|
||||
## Specifying how we pass args to the function
|
||||
|
||||
### builtins/mono/src/borrow.rs
|
||||
### builtins/mono/src/inc_dec.rs
|
||||
|
||||
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for your builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const std = @import("std");
|
||||
const utils = @import("utils.zig");
|
||||
const str = @import("str.zig");
|
||||
const UpdateMode = utils.UpdateMode;
|
||||
const mem = std.mem;
|
||||
const math = std.math;
|
||||
|
@ -1033,3 +1034,34 @@ test "listConcat: non-unique with unique overlapping" {
|
|||
|
||||
try expect(concatted.eql(wanted));
|
||||
}
|
||||
|
||||
pub fn listConcatUtf8(
|
||||
list: RocList,
|
||||
string: str.RocStr,
|
||||
) callconv(.C) RocList {
|
||||
if (string.len() == 0) {
|
||||
return list;
|
||||
} else {
|
||||
const combined_length = list.len() + string.len();
|
||||
|
||||
// List U8 has alignment 1 and element_width 1
|
||||
var result = list.reallocate(1, combined_length, 1);
|
||||
// We just allocated combined_length, which is > 0 because string.len() > 0
|
||||
var bytes = result.bytes orelse unreachable;
|
||||
@memcpy(bytes[list.len()..combined_length], string.asU8ptr()[0..string.len()]);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
test "listConcatUtf8" {
|
||||
const list = RocList.fromSlice(u8, &[_]u8{ 1, 2, 3, 4 });
|
||||
defer list.decref(1);
|
||||
const string_bytes = "🐦";
|
||||
const string = str.RocStr.init(string_bytes, string_bytes.len);
|
||||
defer string.decref();
|
||||
const ret = listConcatUtf8(list, string);
|
||||
const expected = RocList.fromSlice(u8, &[_]u8{ 1, 2, 3, 4, 240, 159, 144, 166 });
|
||||
defer expected.decref(1);
|
||||
try expect(ret.eql(expected));
|
||||
}
|
||||
|
|
|
@ -85,6 +85,7 @@ comptime {
|
|||
exportListFn(list.listCapacity, "capacity");
|
||||
exportListFn(list.listAllocationPtr, "allocation_ptr");
|
||||
exportListFn(list.listReleaseExcessCapacity, "release_excess_capacity");
|
||||
exportListFn(list.listConcatUtf8, "concat_utf8");
|
||||
}
|
||||
|
||||
// Num Module
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
interface Bool
|
||||
exposes [Bool, Eq, true, false, and, or, not, isEq, isNotEq]
|
||||
imports []
|
||||
module [Bool, Eq, true, false, and, or, not, isEq, isNotEq]
|
||||
|
||||
## Defines a type that can be compared for total equality.
|
||||
##
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
## - Holding unknown Roc types when developing [platforms](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform).
|
||||
## - To improve performance in rare cases.
|
||||
##
|
||||
interface Box
|
||||
exposes [box, unbox]
|
||||
imports []
|
||||
module [box, unbox]
|
||||
|
||||
## Allocates a value on the heap. Boxing is an expensive process as it copies
|
||||
## the value from the stack to the heap. This may provide a performance
|
||||
|
|
|
@ -1,55 +1,53 @@
|
|||
interface Decode
|
||||
exposes [
|
||||
DecodeError,
|
||||
DecodeResult,
|
||||
Decoder,
|
||||
Decoding,
|
||||
DecoderFormatting,
|
||||
decoder,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
u128,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
i128,
|
||||
f32,
|
||||
f64,
|
||||
dec,
|
||||
bool,
|
||||
string,
|
||||
list,
|
||||
record,
|
||||
tuple,
|
||||
custom,
|
||||
decodeWith,
|
||||
fromBytesPartial,
|
||||
fromBytes,
|
||||
mapResult,
|
||||
]
|
||||
imports [
|
||||
List,
|
||||
Result.{ Result },
|
||||
Num.{
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
F32,
|
||||
F64,
|
||||
Dec,
|
||||
},
|
||||
Bool.{ Bool },
|
||||
]
|
||||
module [
|
||||
DecodeError,
|
||||
DecodeResult,
|
||||
Decoder,
|
||||
Decoding,
|
||||
DecoderFormatting,
|
||||
decoder,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
u128,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
i128,
|
||||
f32,
|
||||
f64,
|
||||
dec,
|
||||
bool,
|
||||
string,
|
||||
list,
|
||||
record,
|
||||
tuple,
|
||||
custom,
|
||||
decodeWith,
|
||||
fromBytesPartial,
|
||||
fromBytes,
|
||||
mapResult,
|
||||
]
|
||||
|
||||
import List
|
||||
import Result exposing [Result]
|
||||
import Num exposing [
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
F32,
|
||||
F64,
|
||||
Dec,
|
||||
]
|
||||
import Bool exposing [Bool]
|
||||
|
||||
## Error types when decoding a `List U8` of utf-8 bytes using a [Decoder]
|
||||
DecodeError : [TooShort]
|
||||
|
@ -103,7 +101,7 @@ DecoderFormatting implements
|
|||
## `Skip` if the field is not a part of the decoded record.
|
||||
##
|
||||
## `finalizer` should produce the record value from the decoded `state`.
|
||||
record : state, (state, Str -> [Keep (Decoder state fmt), Skip]), (state -> Result val DecodeError) -> Decoder val fmt where fmt implements DecoderFormatting
|
||||
record : state, (state, Str -> [Keep (Decoder state fmt), Skip]), (state, fmt -> Result val DecodeError) -> Decoder val fmt where fmt implements DecoderFormatting
|
||||
|
||||
## `tuple state stepElem finalizer` decodes a tuple element-by-element.
|
||||
##
|
||||
|
|
|
@ -1,43 +1,41 @@
|
|||
interface Dict
|
||||
exposes [
|
||||
Dict,
|
||||
empty,
|
||||
withCapacity,
|
||||
single,
|
||||
clear,
|
||||
capacity,
|
||||
reserve,
|
||||
releaseExcessCapacity,
|
||||
len,
|
||||
isEmpty,
|
||||
get,
|
||||
contains,
|
||||
insert,
|
||||
remove,
|
||||
update,
|
||||
walk,
|
||||
walkUntil,
|
||||
keepIf,
|
||||
dropIf,
|
||||
toList,
|
||||
fromList,
|
||||
keys,
|
||||
values,
|
||||
insertAll,
|
||||
keepShared,
|
||||
removeAll,
|
||||
map,
|
||||
joinMap,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool, Eq },
|
||||
Result.{ Result },
|
||||
List,
|
||||
Str,
|
||||
Num.{ U64, F32, U32, U8, I8 },
|
||||
Hash.{ Hasher, Hash },
|
||||
Inspect.{ Inspect, Inspector, InspectFormatter },
|
||||
]
|
||||
module [
|
||||
Dict,
|
||||
empty,
|
||||
withCapacity,
|
||||
single,
|
||||
clear,
|
||||
capacity,
|
||||
reserve,
|
||||
releaseExcessCapacity,
|
||||
len,
|
||||
isEmpty,
|
||||
get,
|
||||
contains,
|
||||
insert,
|
||||
remove,
|
||||
update,
|
||||
walk,
|
||||
walkUntil,
|
||||
keepIf,
|
||||
dropIf,
|
||||
toList,
|
||||
fromList,
|
||||
keys,
|
||||
values,
|
||||
insertAll,
|
||||
keepShared,
|
||||
removeAll,
|
||||
map,
|
||||
joinMap,
|
||||
]
|
||||
|
||||
import Bool exposing [Bool, Eq]
|
||||
import Result exposing [Result]
|
||||
import List
|
||||
import Str
|
||||
import Num exposing [U64, F32, U32, U8]
|
||||
import Hash exposing [Hasher, Hash]
|
||||
import Inspect exposing [Inspect, Inspector, InspectFormatter]
|
||||
|
||||
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you
|
||||
## associate keys with values.
|
||||
|
|
|
@ -1,51 +1,49 @@
|
|||
interface Encode
|
||||
exposes [
|
||||
Encoder,
|
||||
Encoding,
|
||||
toEncoder,
|
||||
EncoderFormatting,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
u128,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
i128,
|
||||
f32,
|
||||
f64,
|
||||
dec,
|
||||
bool,
|
||||
string,
|
||||
list,
|
||||
record,
|
||||
tag,
|
||||
tuple,
|
||||
custom,
|
||||
appendWith,
|
||||
append,
|
||||
toBytes,
|
||||
]
|
||||
imports [
|
||||
Num.{
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
F32,
|
||||
F64,
|
||||
Dec,
|
||||
},
|
||||
Bool.{ Bool },
|
||||
]
|
||||
module [
|
||||
Encoder,
|
||||
Encoding,
|
||||
toEncoder,
|
||||
EncoderFormatting,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
u128,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
i128,
|
||||
f32,
|
||||
f64,
|
||||
dec,
|
||||
bool,
|
||||
string,
|
||||
list,
|
||||
record,
|
||||
tag,
|
||||
tuple,
|
||||
custom,
|
||||
appendWith,
|
||||
append,
|
||||
toBytes,
|
||||
]
|
||||
|
||||
import Num exposing [
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
F32,
|
||||
F64,
|
||||
Dec,
|
||||
]
|
||||
import Bool exposing [Bool]
|
||||
|
||||
Encoder fmt := List U8, fmt -> List U8 where fmt implements EncoderFormatting
|
||||
|
||||
|
|
|
@ -1,31 +1,42 @@
|
|||
interface Hash
|
||||
exposes [
|
||||
Hash,
|
||||
Hasher,
|
||||
hash,
|
||||
addBytes,
|
||||
addU8,
|
||||
addU16,
|
||||
addU32,
|
||||
addU64,
|
||||
addU128,
|
||||
hashBool,
|
||||
hashI8,
|
||||
hashI16,
|
||||
hashI32,
|
||||
hashI64,
|
||||
hashI128,
|
||||
hashDec,
|
||||
complete,
|
||||
hashStrBytes,
|
||||
hashList,
|
||||
hashUnordered,
|
||||
] imports [
|
||||
Bool.{ Bool },
|
||||
List,
|
||||
Str,
|
||||
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, Dec },
|
||||
]
|
||||
module [
|
||||
Hash,
|
||||
Hasher,
|
||||
hash,
|
||||
addBytes,
|
||||
addU8,
|
||||
addU16,
|
||||
addU32,
|
||||
addU64,
|
||||
addU128,
|
||||
hashBool,
|
||||
hashI8,
|
||||
hashI16,
|
||||
hashI32,
|
||||
hashI64,
|
||||
hashI128,
|
||||
hashDec,
|
||||
complete,
|
||||
hashStrBytes,
|
||||
hashList,
|
||||
hashUnordered,
|
||||
]
|
||||
|
||||
import Bool exposing [Bool]
|
||||
import List
|
||||
import Str
|
||||
import Num exposing [
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
Dec,
|
||||
]
|
||||
|
||||
## A value that can be hashed.
|
||||
Hash implements
|
||||
|
|
|
@ -1,46 +1,44 @@
|
|||
interface Inspect
|
||||
exposes [
|
||||
Inspect,
|
||||
Inspector,
|
||||
InspectFormatter,
|
||||
ElemWalker,
|
||||
KeyValWalker,
|
||||
inspect,
|
||||
init,
|
||||
list,
|
||||
set,
|
||||
dict,
|
||||
tag,
|
||||
tuple,
|
||||
record,
|
||||
bool,
|
||||
str,
|
||||
function,
|
||||
opaque,
|
||||
u8,
|
||||
i8,
|
||||
u16,
|
||||
i16,
|
||||
u32,
|
||||
i32,
|
||||
u64,
|
||||
i64,
|
||||
u128,
|
||||
i128,
|
||||
f32,
|
||||
f64,
|
||||
dec,
|
||||
custom,
|
||||
apply,
|
||||
toInspector,
|
||||
toStr,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool },
|
||||
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
|
||||
List,
|
||||
Str,
|
||||
]
|
||||
module [
|
||||
Inspect,
|
||||
Inspector,
|
||||
InspectFormatter,
|
||||
ElemWalker,
|
||||
KeyValWalker,
|
||||
inspect,
|
||||
init,
|
||||
list,
|
||||
set,
|
||||
dict,
|
||||
tag,
|
||||
tuple,
|
||||
record,
|
||||
bool,
|
||||
str,
|
||||
function,
|
||||
opaque,
|
||||
u8,
|
||||
i8,
|
||||
u16,
|
||||
i16,
|
||||
u32,
|
||||
i32,
|
||||
u64,
|
||||
i64,
|
||||
u128,
|
||||
i128,
|
||||
f32,
|
||||
f64,
|
||||
dec,
|
||||
custom,
|
||||
apply,
|
||||
toInspector,
|
||||
toStr,
|
||||
]
|
||||
|
||||
import Bool exposing [Bool]
|
||||
import Num exposing [U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec]
|
||||
import List
|
||||
import Str
|
||||
|
||||
KeyValWalker state collection key val : collection, state, (state, key, val -> state) -> state
|
||||
ElemWalker state collection elem : collection, state, (state, elem -> state) -> state
|
||||
|
|
|
@ -1,81 +1,80 @@
|
|||
interface List
|
||||
exposes [
|
||||
isEmpty,
|
||||
get,
|
||||
set,
|
||||
replace,
|
||||
update,
|
||||
append,
|
||||
appendIfOk,
|
||||
prepend,
|
||||
prependIfOk,
|
||||
map,
|
||||
len,
|
||||
withCapacity,
|
||||
walkBackwards,
|
||||
concat,
|
||||
first,
|
||||
single,
|
||||
repeat,
|
||||
reverse,
|
||||
join,
|
||||
keepIf,
|
||||
contains,
|
||||
sum,
|
||||
walk,
|
||||
last,
|
||||
keepOks,
|
||||
keepErrs,
|
||||
mapWithIndex,
|
||||
map2,
|
||||
map3,
|
||||
product,
|
||||
walkWithIndex,
|
||||
walkUntil,
|
||||
walkWithIndexUntil,
|
||||
walkFrom,
|
||||
walkFromUntil,
|
||||
range,
|
||||
sortWith,
|
||||
swap,
|
||||
dropAt,
|
||||
min,
|
||||
max,
|
||||
map4,
|
||||
mapTry,
|
||||
walkTry,
|
||||
joinMap,
|
||||
any,
|
||||
takeFirst,
|
||||
takeLast,
|
||||
dropFirst,
|
||||
dropLast,
|
||||
findFirst,
|
||||
findLast,
|
||||
findFirstIndex,
|
||||
findLastIndex,
|
||||
sublist,
|
||||
intersperse,
|
||||
split,
|
||||
splitFirst,
|
||||
splitLast,
|
||||
startsWith,
|
||||
endsWith,
|
||||
all,
|
||||
dropIf,
|
||||
sortAsc,
|
||||
sortDesc,
|
||||
reserve,
|
||||
releaseExcessCapacity,
|
||||
walkBackwardsUntil,
|
||||
countIf,
|
||||
chunksOf,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool, Eq },
|
||||
Result.{ Result },
|
||||
Num.{ U64, Num, Int },
|
||||
]
|
||||
module [
|
||||
isEmpty,
|
||||
get,
|
||||
set,
|
||||
replace,
|
||||
update,
|
||||
append,
|
||||
appendIfOk,
|
||||
prepend,
|
||||
prependIfOk,
|
||||
map,
|
||||
len,
|
||||
withCapacity,
|
||||
walkBackwards,
|
||||
concat,
|
||||
first,
|
||||
single,
|
||||
repeat,
|
||||
reverse,
|
||||
join,
|
||||
keepIf,
|
||||
contains,
|
||||
sum,
|
||||
walk,
|
||||
last,
|
||||
keepOks,
|
||||
keepErrs,
|
||||
mapWithIndex,
|
||||
map2,
|
||||
map3,
|
||||
product,
|
||||
walkWithIndex,
|
||||
walkUntil,
|
||||
walkWithIndexUntil,
|
||||
walkFrom,
|
||||
walkFromUntil,
|
||||
range,
|
||||
sortWith,
|
||||
swap,
|
||||
dropAt,
|
||||
min,
|
||||
max,
|
||||
map4,
|
||||
mapTry,
|
||||
walkTry,
|
||||
joinMap,
|
||||
any,
|
||||
takeFirst,
|
||||
takeLast,
|
||||
dropFirst,
|
||||
dropLast,
|
||||
findFirst,
|
||||
findLast,
|
||||
findFirstIndex,
|
||||
findLastIndex,
|
||||
sublist,
|
||||
intersperse,
|
||||
split,
|
||||
splitFirst,
|
||||
splitLast,
|
||||
startsWith,
|
||||
endsWith,
|
||||
all,
|
||||
dropIf,
|
||||
sortAsc,
|
||||
sortDesc,
|
||||
reserve,
|
||||
releaseExcessCapacity,
|
||||
walkBackwardsUntil,
|
||||
countIf,
|
||||
chunksOf,
|
||||
concatUtf8,
|
||||
]
|
||||
|
||||
import Bool exposing [Bool, Eq]
|
||||
import Result exposing [Result]
|
||||
import Num exposing [U64, Num, U8]
|
||||
|
||||
## ## Types
|
||||
##
|
||||
|
@ -1326,3 +1325,12 @@ iterBackwardsHelp = \list, state, f, prevIndex ->
|
|||
Break b -> Break b
|
||||
else
|
||||
Continue state
|
||||
|
||||
## Concatenates the bytes of a string encoded as utf8 to a list of bytes.
|
||||
## ```roc
|
||||
## expect (List.concatUtf8 [1, 2, 3, 4] "🐦") == [1, 2, 3, 4, 240, 159, 144, 166]
|
||||
## ```
|
||||
concatUtf8 : List U8, Str -> List U8
|
||||
|
||||
expect (List.concatUtf8 [1, 2, 3, 4] "🐦") == [1, 2, 3, 4, 240, 159, 144, 166]
|
||||
|
||||
|
|
|
@ -1,166 +1,168 @@
|
|||
interface Num
|
||||
exposes [
|
||||
Num,
|
||||
Int,
|
||||
Frac,
|
||||
Integer,
|
||||
FloatingPoint,
|
||||
I128,
|
||||
I64,
|
||||
I32,
|
||||
I16,
|
||||
I8,
|
||||
U128,
|
||||
U64,
|
||||
U32,
|
||||
U16,
|
||||
U8,
|
||||
Signed128,
|
||||
Signed64,
|
||||
Signed32,
|
||||
Signed16,
|
||||
Signed8,
|
||||
Unsigned128,
|
||||
Unsigned64,
|
||||
Unsigned32,
|
||||
Unsigned16,
|
||||
Unsigned8,
|
||||
Dec,
|
||||
F64,
|
||||
F32,
|
||||
Decimal,
|
||||
Binary32,
|
||||
Binary64,
|
||||
e,
|
||||
pi,
|
||||
tau,
|
||||
abs,
|
||||
absDiff,
|
||||
neg,
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
min,
|
||||
max,
|
||||
isLt,
|
||||
isLte,
|
||||
isGt,
|
||||
isGte,
|
||||
isApproxEq,
|
||||
sin,
|
||||
cos,
|
||||
tan,
|
||||
atan,
|
||||
acos,
|
||||
asin,
|
||||
isZero,
|
||||
isEven,
|
||||
isOdd,
|
||||
toFrac,
|
||||
isPositive,
|
||||
isNegative,
|
||||
isNaN,
|
||||
isInfinite,
|
||||
isFinite,
|
||||
rem,
|
||||
remChecked,
|
||||
div,
|
||||
divChecked,
|
||||
sqrt,
|
||||
sqrtChecked,
|
||||
log,
|
||||
logChecked,
|
||||
round,
|
||||
ceiling,
|
||||
floor,
|
||||
compare,
|
||||
pow,
|
||||
powInt,
|
||||
countLeadingZeroBits,
|
||||
countTrailingZeroBits,
|
||||
countOneBits,
|
||||
addWrap,
|
||||
addChecked,
|
||||
addSaturated,
|
||||
bitwiseAnd,
|
||||
bitwiseXor,
|
||||
bitwiseOr,
|
||||
bitwiseNot,
|
||||
shiftLeftBy,
|
||||
shiftRightBy,
|
||||
shiftRightZfBy,
|
||||
subWrap,
|
||||
subChecked,
|
||||
subSaturated,
|
||||
mulWrap,
|
||||
mulSaturated,
|
||||
mulChecked,
|
||||
intCast,
|
||||
divCeil,
|
||||
divCeilChecked,
|
||||
divTrunc,
|
||||
divTruncChecked,
|
||||
toStr,
|
||||
isMultipleOf,
|
||||
minI8,
|
||||
maxI8,
|
||||
minU8,
|
||||
maxU8,
|
||||
minI16,
|
||||
maxI16,
|
||||
minU16,
|
||||
maxU16,
|
||||
minI32,
|
||||
maxI32,
|
||||
minU32,
|
||||
maxU32,
|
||||
minI64,
|
||||
maxI64,
|
||||
minU64,
|
||||
maxU64,
|
||||
minI128,
|
||||
maxI128,
|
||||
minU128,
|
||||
maxU128,
|
||||
minF32,
|
||||
maxF32,
|
||||
minF64,
|
||||
maxF64,
|
||||
toI8,
|
||||
toI8Checked,
|
||||
toI16,
|
||||
toI16Checked,
|
||||
toI32,
|
||||
toI32Checked,
|
||||
toI64,
|
||||
toI64Checked,
|
||||
toI128,
|
||||
toI128Checked,
|
||||
toU8,
|
||||
toU8Checked,
|
||||
toU16,
|
||||
toU16Checked,
|
||||
toU32,
|
||||
toU32Checked,
|
||||
toU64,
|
||||
toU64Checked,
|
||||
toU128,
|
||||
toU128Checked,
|
||||
toF32,
|
||||
toF32Checked,
|
||||
toF64,
|
||||
toF64Checked,
|
||||
withoutDecimalPoint,
|
||||
withDecimalPoint,
|
||||
f32ToParts,
|
||||
f64ToParts,
|
||||
f32FromParts,
|
||||
f64FromParts,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool },
|
||||
Result.{ Result },
|
||||
]
|
||||
module [
|
||||
Num,
|
||||
Int,
|
||||
Frac,
|
||||
Integer,
|
||||
FloatingPoint,
|
||||
I128,
|
||||
I64,
|
||||
I32,
|
||||
I16,
|
||||
I8,
|
||||
U128,
|
||||
U64,
|
||||
U32,
|
||||
U16,
|
||||
U8,
|
||||
Signed128,
|
||||
Signed64,
|
||||
Signed32,
|
||||
Signed16,
|
||||
Signed8,
|
||||
Unsigned128,
|
||||
Unsigned64,
|
||||
Unsigned32,
|
||||
Unsigned16,
|
||||
Unsigned8,
|
||||
Dec,
|
||||
F64,
|
||||
F32,
|
||||
Decimal,
|
||||
Binary32,
|
||||
Binary64,
|
||||
e,
|
||||
pi,
|
||||
tau,
|
||||
abs,
|
||||
absDiff,
|
||||
neg,
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
min,
|
||||
max,
|
||||
isLt,
|
||||
isLte,
|
||||
isGt,
|
||||
isGte,
|
||||
isApproxEq,
|
||||
sin,
|
||||
cos,
|
||||
tan,
|
||||
atan,
|
||||
acos,
|
||||
asin,
|
||||
isZero,
|
||||
isEven,
|
||||
isOdd,
|
||||
toFrac,
|
||||
isPositive,
|
||||
isNegative,
|
||||
isNaN,
|
||||
isInfinite,
|
||||
isFinite,
|
||||
rem,
|
||||
remChecked,
|
||||
div,
|
||||
divChecked,
|
||||
sqrt,
|
||||
sqrtChecked,
|
||||
log,
|
||||
logChecked,
|
||||
round,
|
||||
ceiling,
|
||||
floor,
|
||||
compare,
|
||||
pow,
|
||||
powInt,
|
||||
countLeadingZeroBits,
|
||||
countTrailingZeroBits,
|
||||
countOneBits,
|
||||
addWrap,
|
||||
addChecked,
|
||||
addSaturated,
|
||||
bitwiseAnd,
|
||||
bitwiseXor,
|
||||
bitwiseOr,
|
||||
bitwiseNot,
|
||||
shiftLeftBy,
|
||||
shiftRightBy,
|
||||
shiftRightZfBy,
|
||||
subWrap,
|
||||
subChecked,
|
||||
subSaturated,
|
||||
mulWrap,
|
||||
mulSaturated,
|
||||
mulChecked,
|
||||
intCast,
|
||||
divCeil,
|
||||
divCeilChecked,
|
||||
divTrunc,
|
||||
divTruncChecked,
|
||||
toStr,
|
||||
isMultipleOf,
|
||||
minI8,
|
||||
maxI8,
|
||||
minU8,
|
||||
maxU8,
|
||||
minI16,
|
||||
maxI16,
|
||||
minU16,
|
||||
maxU16,
|
||||
minI32,
|
||||
maxI32,
|
||||
minU32,
|
||||
maxU32,
|
||||
minI64,
|
||||
maxI64,
|
||||
minU64,
|
||||
maxU64,
|
||||
minI128,
|
||||
maxI128,
|
||||
minU128,
|
||||
maxU128,
|
||||
minF32,
|
||||
maxF32,
|
||||
minF64,
|
||||
maxF64,
|
||||
toI8,
|
||||
toI8Checked,
|
||||
toI16,
|
||||
toI16Checked,
|
||||
toI32,
|
||||
toI32Checked,
|
||||
toI64,
|
||||
toI64Checked,
|
||||
toI128,
|
||||
toI128Checked,
|
||||
toU8,
|
||||
toU8Checked,
|
||||
toU16,
|
||||
toU16Checked,
|
||||
toU32,
|
||||
toU32Checked,
|
||||
toU64,
|
||||
toU64Checked,
|
||||
toU128,
|
||||
toU128Checked,
|
||||
toF32,
|
||||
toF32Checked,
|
||||
toF64,
|
||||
toF64Checked,
|
||||
withoutDecimalPoint,
|
||||
withDecimalPoint,
|
||||
f32ToParts,
|
||||
f64ToParts,
|
||||
f32FromParts,
|
||||
f64FromParts,
|
||||
nanF32,
|
||||
nanF64,
|
||||
infinityF32,
|
||||
infinityF64,
|
||||
]
|
||||
|
||||
import Bool exposing [Bool]
|
||||
import Result exposing [Result]
|
||||
|
||||
## Represents a number that could be either an [Int] or a [Frac].
|
||||
##
|
||||
|
@ -1001,7 +1003,7 @@ bitwiseNot = \n ->
|
|||
## ```roc
|
||||
## shiftLeftBy 0b0000_0011 2 == 0b0000_1100
|
||||
##
|
||||
## 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
|
||||
## 0b0000_0101 |> shiftLeftBy 2 == 0b0001_0100
|
||||
## ```
|
||||
## In some languages `shiftLeftBy` is implemented as a binary operator `<<`.
|
||||
shiftLeftBy : Int a, U8 -> Int a
|
||||
|
@ -1435,3 +1437,19 @@ f32FromParts : { sign : Bool, exponent : U8, fraction : U32 } -> F32
|
|||
## The fraction should not be bigger than 0x000F_FFFF_FFFF_FFFF, any bigger value will be truncated.
|
||||
## The exponent should not be bigger than 0x07FF, any bigger value will be truncated.
|
||||
f64FromParts : { sign : Bool, exponent : U16, fraction : U64 } -> F64
|
||||
|
||||
## The value for not-a-number for a [F32] according to the IEEE 754 standard.
|
||||
nanF32 : F32
|
||||
nanF32 = 0.0f32 / 0.0
|
||||
|
||||
## The value for not-a-number for a [F64] according to the IEEE 754 standard.
|
||||
nanF64 : F64
|
||||
nanF64 = 0.0f64 / 0.0
|
||||
|
||||
## The value for infinity for a [F32] according to the IEEE 754 standard.
|
||||
infinityF32 : F32
|
||||
infinityF32 = 1.0f32 / 0.0
|
||||
|
||||
## The value for infinity for a [F64] according to the IEEE 754 standard.
|
||||
infinityF64 : F64
|
||||
infinityF64 = 1.0f64 / 0.0
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
interface Result
|
||||
exposes [Result, isOk, isErr, map, mapErr, try, onErr, withDefault]
|
||||
imports [Bool.{ Bool }]
|
||||
module [Result, isOk, isErr, map, mapErr, try, onErr, withDefault]
|
||||
|
||||
import Bool exposing [Bool]
|
||||
|
||||
## The result of an operation that could fail: either the operation went
|
||||
## okay, or else there was an error of some sort.
|
||||
|
|
|
@ -1,37 +1,35 @@
|
|||
interface Set
|
||||
exposes [
|
||||
Set,
|
||||
empty,
|
||||
withCapacity,
|
||||
reserve,
|
||||
releaseExcessCapacity,
|
||||
single,
|
||||
walk,
|
||||
walkUntil,
|
||||
keepIf,
|
||||
dropIf,
|
||||
insert,
|
||||
len,
|
||||
isEmpty,
|
||||
capacity,
|
||||
remove,
|
||||
contains,
|
||||
toList,
|
||||
fromList,
|
||||
union,
|
||||
intersection,
|
||||
difference,
|
||||
map,
|
||||
joinMap,
|
||||
]
|
||||
imports [
|
||||
List,
|
||||
Bool.{ Bool, Eq },
|
||||
Dict.{ Dict },
|
||||
Num.{ U64 },
|
||||
Hash.{ Hash, Hasher },
|
||||
Inspect.{ Inspect, Inspector, InspectFormatter },
|
||||
]
|
||||
module [
|
||||
Set,
|
||||
empty,
|
||||
withCapacity,
|
||||
reserve,
|
||||
releaseExcessCapacity,
|
||||
single,
|
||||
walk,
|
||||
walkUntil,
|
||||
keepIf,
|
||||
dropIf,
|
||||
insert,
|
||||
len,
|
||||
isEmpty,
|
||||
capacity,
|
||||
remove,
|
||||
contains,
|
||||
toList,
|
||||
fromList,
|
||||
union,
|
||||
intersection,
|
||||
difference,
|
||||
map,
|
||||
joinMap,
|
||||
]
|
||||
|
||||
import List
|
||||
import Bool exposing [Bool, Eq]
|
||||
import Dict
|
||||
import Num exposing [U64]
|
||||
import Hash exposing [Hash, Hasher]
|
||||
import Inspect exposing [Inspect, Inspector, InspectFormatter]
|
||||
|
||||
## Provides a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type))
|
||||
## type which stores a collection of unique values, without any ordering
|
||||
|
|
|
@ -326,55 +326,53 @@
|
|||
## If a situation like this comes up, a slice can be turned into a separate string by using [`Str.concat`](https://www.roc-lang.org/builtins/Str#concat) to concatenate the slice onto an empty string (or one created with [`Str.withCapacity`](https://www.roc-lang.org/builtins/Str#withCapacity)).
|
||||
##
|
||||
## Currently, the only way to get seamless slices of strings is by calling certain `Str` functions which return them. In general, `Str` functions which accept a string and return a subset of that string tend to do this. [`Str.trim`](https://www.roc-lang.org/builtins/Str#trim) is another example of a function which returns a seamless slice.
|
||||
interface Str
|
||||
exposes [
|
||||
Utf8Problem,
|
||||
Utf8ByteProblem,
|
||||
concat,
|
||||
isEmpty,
|
||||
joinWith,
|
||||
split,
|
||||
repeat,
|
||||
countUtf8Bytes,
|
||||
toUtf8,
|
||||
fromUtf8,
|
||||
startsWith,
|
||||
endsWith,
|
||||
trim,
|
||||
trimStart,
|
||||
trimEnd,
|
||||
toDec,
|
||||
toF64,
|
||||
toF32,
|
||||
toU128,
|
||||
toI128,
|
||||
toU64,
|
||||
toI64,
|
||||
toU32,
|
||||
toI32,
|
||||
toU16,
|
||||
toI16,
|
||||
toU8,
|
||||
toI8,
|
||||
replaceEach,
|
||||
replaceFirst,
|
||||
replaceLast,
|
||||
splitFirst,
|
||||
splitLast,
|
||||
walkUtf8,
|
||||
walkUtf8WithIndex,
|
||||
reserve,
|
||||
releaseExcessCapacity,
|
||||
withCapacity,
|
||||
withPrefix,
|
||||
contains,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool, Eq },
|
||||
Result.{ Result },
|
||||
List,
|
||||
Num.{ Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
|
||||
]
|
||||
module [
|
||||
Utf8Problem,
|
||||
Utf8ByteProblem,
|
||||
concat,
|
||||
isEmpty,
|
||||
joinWith,
|
||||
split,
|
||||
repeat,
|
||||
countUtf8Bytes,
|
||||
toUtf8,
|
||||
fromUtf8,
|
||||
startsWith,
|
||||
endsWith,
|
||||
trim,
|
||||
trimStart,
|
||||
trimEnd,
|
||||
toDec,
|
||||
toF64,
|
||||
toF32,
|
||||
toU128,
|
||||
toI128,
|
||||
toU64,
|
||||
toI64,
|
||||
toU32,
|
||||
toI32,
|
||||
toU16,
|
||||
toI16,
|
||||
toU8,
|
||||
toI8,
|
||||
replaceEach,
|
||||
replaceFirst,
|
||||
replaceLast,
|
||||
splitFirst,
|
||||
splitLast,
|
||||
walkUtf8,
|
||||
walkUtf8WithIndex,
|
||||
reserve,
|
||||
releaseExcessCapacity,
|
||||
withCapacity,
|
||||
withPrefix,
|
||||
contains,
|
||||
]
|
||||
|
||||
import Bool exposing [Bool]
|
||||
import Result exposing [Result]
|
||||
import List
|
||||
import Num exposing [Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec]
|
||||
|
||||
Utf8ByteProblem : [
|
||||
InvalidStartByte,
|
||||
|
|
|
@ -1,44 +1,18 @@
|
|||
## THIS MODULE IS DEPRECATED AND CURRENTLY IN THE PROCESS OF BEING REMOVED
|
||||
## FROM STD LIBRARY
|
||||
interface TotallyNotJson
|
||||
exposes [
|
||||
Json,
|
||||
json,
|
||||
jsonWithOptions,
|
||||
]
|
||||
imports [
|
||||
List,
|
||||
Str,
|
||||
Result.{ Result },
|
||||
Encode,
|
||||
Encode.{
|
||||
Encoder,
|
||||
EncoderFormatting,
|
||||
appendWith,
|
||||
},
|
||||
Decode,
|
||||
Decode.{
|
||||
DecoderFormatting,
|
||||
DecodeResult,
|
||||
},
|
||||
Num.{
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
F32,
|
||||
F64,
|
||||
Dec,
|
||||
},
|
||||
Bool.{ Bool, Eq },
|
||||
Result,
|
||||
]
|
||||
module [
|
||||
Json,
|
||||
json,
|
||||
jsonWithOptions,
|
||||
]
|
||||
|
||||
import List
|
||||
import Str
|
||||
import Result
|
||||
import Encode exposing [EncoderFormatting, appendWith]
|
||||
import Decode exposing [DecoderFormatting, DecodeResult]
|
||||
import Num exposing [U8, U16, U64, F32, F64, Dec]
|
||||
import Bool exposing [Bool]
|
||||
|
||||
## An opaque type with the `EncoderFormatting` and
|
||||
## `DecoderFormatting` abilities.
|
||||
|
@ -232,14 +206,20 @@ escapedByteToJson = \b ->
|
|||
encodeList = \lst, encodeElem ->
|
||||
Encode.custom \bytes, @Json {} ->
|
||||
writeList = \{ buffer, elemsLeft }, elem ->
|
||||
bufferWithElem = appendWith buffer (encodeElem elem) (@Json {})
|
||||
bufferWithSuffix =
|
||||
if elemsLeft > 1 then
|
||||
List.append bufferWithElem (Num.toU8 ',')
|
||||
else
|
||||
bufferWithElem
|
||||
beforeBufferLen = buffer |> List.len
|
||||
|
||||
{ buffer: bufferWithSuffix, elemsLeft: elemsLeft - 1 }
|
||||
bufferWithElem = appendWith buffer (encodeElem elem) (@Json {})
|
||||
# If our encoder returned [] we just skip the elem
|
||||
if bufferWithElem |> List.len == beforeBufferLen then
|
||||
{ buffer: bufferWithElem, elemsLeft: elemsLeft - 1 }
|
||||
else
|
||||
bufferWithSuffix =
|
||||
if elemsLeft > 1 then
|
||||
List.append bufferWithElem (Num.toU8 ',')
|
||||
else
|
||||
bufferWithElem
|
||||
|
||||
{ buffer: bufferWithSuffix, elemsLeft: elemsLeft - 1 }
|
||||
|
||||
head = List.append bytes (Num.toU8 '[')
|
||||
{ buffer: withList } = List.walk lst { buffer: head, elemsLeft: List.len lst } writeList
|
||||
|
@ -249,21 +229,27 @@ encodeList = \lst, encodeElem ->
|
|||
encodeRecord = \fields ->
|
||||
Encode.custom \bytes, @Json {} ->
|
||||
writeRecord = \{ buffer, fieldsLeft }, { key, value } ->
|
||||
fieldName = key
|
||||
bufferWithKeyValue =
|
||||
List.append buffer (Num.toU8 '"')
|
||||
|> List.concat (Str.toUtf8 fieldName)
|
||||
|> List.append (Num.toU8 '"')
|
||||
|> List.append (Num.toU8 ':') # Note we need to encode using the json config here
|
||||
|> appendWith value (@Json {})
|
||||
|
||||
bufferWithSuffix =
|
||||
if fieldsLeft > 1 then
|
||||
List.append bufferWithKeyValue (Num.toU8 ',')
|
||||
else
|
||||
bufferWithKeyValue
|
||||
fieldValue = [] |> appendWith value (json)
|
||||
# If our encoder returned [] we just skip the field
|
||||
if fieldValue == [] then
|
||||
{ buffer, fieldsLeft: fieldsLeft - 1 }
|
||||
else
|
||||
fieldName = key
|
||||
bufferWithKeyValue =
|
||||
List.append buffer (Num.toU8 '"')
|
||||
|> List.concat (Str.toUtf8 fieldName)
|
||||
|> List.append (Num.toU8 '"')
|
||||
|> List.append (Num.toU8 ':') # Note we need to encode using the json config here
|
||||
|> List.concat fieldValue
|
||||
|
||||
{ buffer: bufferWithSuffix, fieldsLeft: fieldsLeft - 1 }
|
||||
bufferWithSuffix =
|
||||
if fieldsLeft > 1 then
|
||||
List.append bufferWithKeyValue (Num.toU8 ',')
|
||||
else
|
||||
bufferWithKeyValue
|
||||
|
||||
{ buffer: bufferWithSuffix, fieldsLeft: fieldsLeft - 1 }
|
||||
|
||||
bytesHead = List.append bytes (Num.toU8 '{')
|
||||
{ buffer: bytesWithRecord } = List.walk fields { buffer: bytesHead, fieldsLeft: List.len fields } writeRecord
|
||||
|
@ -273,16 +259,21 @@ encodeRecord = \fields ->
|
|||
encodeTuple = \elems ->
|
||||
Encode.custom \bytes, @Json {} ->
|
||||
writeTuple = \{ buffer, elemsLeft }, elemEncoder ->
|
||||
bufferWithElem =
|
||||
appendWith buffer elemEncoder (@Json {})
|
||||
beforeBufferLen = buffer |> List.len
|
||||
|
||||
bufferWithSuffix =
|
||||
if elemsLeft > 1 then
|
||||
List.append bufferWithElem (Num.toU8 ',')
|
||||
else
|
||||
bufferWithElem
|
||||
bufferWithElem = appendWith buffer (elemEncoder) (@Json {})
|
||||
|
||||
{ buffer: bufferWithSuffix, elemsLeft: elemsLeft - 1 }
|
||||
# If our encoder returned [] we just skip the elem
|
||||
if bufferWithElem |> List.len == beforeBufferLen then
|
||||
{ buffer: bufferWithElem, elemsLeft: elemsLeft - 1 }
|
||||
else
|
||||
bufferWithSuffix =
|
||||
if elemsLeft > 1 then
|
||||
List.append bufferWithElem (Num.toU8 ',')
|
||||
else
|
||||
bufferWithElem
|
||||
|
||||
{ buffer: bufferWithSuffix, elemsLeft: elemsLeft - 1 }
|
||||
|
||||
bytesHead = List.append bytes (Num.toU8 '[')
|
||||
{ buffer: bytesWithRecord } = List.walk elems { buffer: bytesHead, elemsLeft: List.len elems } writeTuple
|
||||
|
@ -1273,7 +1264,7 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
|
|||
rest = List.dropFirst bytesAfterValue n
|
||||
|
||||
# Build final record from decoded fields and values
|
||||
when finalizer updatedRecord is
|
||||
when finalizer updatedRecord json is
|
||||
Ok val -> { result: Ok val, rest }
|
||||
Err e -> { result: Err e, rest }
|
||||
|
||||
|
|
|
@ -1,3 +1,15 @@
|
|||
package "builtins"
|
||||
exposes [Str, Num, Bool, Result, List, Dict, Set, Decode, Encode, Hash, Box, TotallyNotJson, Inspect]
|
||||
packages {}
|
||||
package [
|
||||
Str,
|
||||
Num,
|
||||
Bool,
|
||||
Result,
|
||||
List,
|
||||
Dict,
|
||||
Set,
|
||||
Decode,
|
||||
Encode,
|
||||
Hash,
|
||||
Box,
|
||||
TotallyNotJson,
|
||||
Inspect,
|
||||
] {}
|
||||
|
|
|
@ -390,6 +390,7 @@ pub const LIST_RESERVE: &str = "roc_builtins.list.reserve";
|
|||
pub const LIST_CAPACITY: &str = "roc_builtins.list.capacity";
|
||||
pub const LIST_ALLOCATION_PTR: &str = "roc_builtins.list.allocation_ptr";
|
||||
pub const LIST_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.list.release_excess_capacity";
|
||||
pub const LIST_CONCAT_UTF8: &str = "roc_builtins.list.concat_utf8";
|
||||
|
||||
pub const DEC_ABS: &str = "roc_builtins.dec.abs";
|
||||
pub const DEC_ACOS: &str = "roc_builtins.dec.acos";
|
||||
|
|
|
@ -17,6 +17,7 @@ roc_problem = { path = "../problem" }
|
|||
roc_region = { path = "../region" }
|
||||
roc_serialize = { path = "../serialize" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_test_utils = { path = "../../test_utils" }
|
||||
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::env::Env;
|
||||
use crate::procedure::References;
|
||||
use crate::procedure::{QualifiedReference, References};
|
||||
use crate::scope::{PendingAbilitiesInScope, Scope};
|
||||
use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet};
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
|
@ -17,7 +17,7 @@ use roc_types::types::{
|
|||
pub struct Annotation {
|
||||
pub typ: Type,
|
||||
pub introduced_variables: IntroducedVariables,
|
||||
pub references: VecSet<Symbol>,
|
||||
pub references: References,
|
||||
pub aliases: VecMap<Symbol, Alias>,
|
||||
}
|
||||
|
||||
|
@ -28,9 +28,7 @@ impl Annotation {
|
|||
references: &mut References,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
) {
|
||||
for symbol in self.references.iter() {
|
||||
references.insert_type_lookup(*symbol);
|
||||
}
|
||||
references.union_mut(&self.references);
|
||||
|
||||
introduced_variables.union(&self.introduced_variables);
|
||||
|
||||
|
@ -291,7 +289,7 @@ pub(crate) fn canonicalize_annotation(
|
|||
annotation_for: AnnotationFor,
|
||||
) -> Annotation {
|
||||
let mut introduced_variables = IntroducedVariables::default();
|
||||
let mut references = VecSet::default();
|
||||
let mut references = References::new();
|
||||
let mut aliases = VecMap::default();
|
||||
|
||||
let (annotation, region) = match annotation {
|
||||
|
@ -381,13 +379,17 @@ pub(crate) fn make_apply_symbol(
|
|||
scope: &mut Scope,
|
||||
module_name: &str,
|
||||
ident: &str,
|
||||
references: &mut References,
|
||||
) -> Result<Symbol, Type> {
|
||||
if module_name.is_empty() {
|
||||
// Since module_name was empty, this is an unqualified type.
|
||||
// Look it up in scope!
|
||||
|
||||
match scope.lookup_str(ident, region) {
|
||||
Ok(symbol) => Ok(symbol),
|
||||
Ok(symbol) => {
|
||||
references.insert_type_lookup(symbol, QualifiedReference::Unqualified);
|
||||
Ok(symbol)
|
||||
}
|
||||
Err(problem) => {
|
||||
env.problem(roc_problem::can::Problem::RuntimeError(problem));
|
||||
|
||||
|
@ -396,7 +398,10 @@ pub(crate) fn make_apply_symbol(
|
|||
}
|
||||
} else {
|
||||
match env.qualified_lookup(scope, module_name, ident, region) {
|
||||
Ok(symbol) => Ok(symbol),
|
||||
Ok(symbol) => {
|
||||
references.insert_type_lookup(symbol, QualifiedReference::Qualified);
|
||||
Ok(symbol)
|
||||
}
|
||||
Err(problem) => {
|
||||
// Either the module wasn't imported, or
|
||||
// it was imported but it doesn't expose this ident.
|
||||
|
@ -537,7 +542,7 @@ fn can_annotation_help(
|
|||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
references: &mut References,
|
||||
) -> Type {
|
||||
use roc_parse::ast::TypeAnnotation::*;
|
||||
|
||||
|
@ -580,15 +585,14 @@ fn can_annotation_help(
|
|||
Type::Function(args, Box::new(closure), Box::new(ret))
|
||||
}
|
||||
Apply(module_name, ident, type_arguments) => {
|
||||
let symbol = match make_apply_symbol(env, region, scope, module_name, ident) {
|
||||
let symbol = match make_apply_symbol(env, region, scope, module_name, ident, references)
|
||||
{
|
||||
Err(problem) => return problem,
|
||||
Ok(symbol) => symbol,
|
||||
};
|
||||
|
||||
let mut args = Vec::new();
|
||||
|
||||
references.insert(symbol);
|
||||
|
||||
if scope.abilities_store.is_ability(symbol) {
|
||||
let fresh_ty_var = find_fresh_var_name(introduced_variables);
|
||||
|
||||
|
@ -744,11 +748,13 @@ fn can_annotation_help(
|
|||
let mut vars = Vec::with_capacity(loc_vars.len());
|
||||
let mut lowercase_vars: Vec<Loc<AliasVar>> = Vec::with_capacity(loc_vars.len());
|
||||
|
||||
references.insert(symbol);
|
||||
references.insert_type_lookup(symbol, QualifiedReference::Unqualified);
|
||||
|
||||
for loc_var in *loc_vars {
|
||||
let var = match loc_var.value {
|
||||
Pattern::Identifier(name) if name.chars().next().unwrap().is_lowercase() => {
|
||||
Pattern::Identifier { ident: name, .. }
|
||||
if name.chars().next().unwrap().is_lowercase() =>
|
||||
{
|
||||
name
|
||||
}
|
||||
_ => unreachable!("I thought this was validated during parsing"),
|
||||
|
@ -1055,7 +1061,7 @@ fn canonicalize_has_clause(
|
|||
introduced_variables: &mut IntroducedVariables,
|
||||
clause: &Loc<roc_parse::ast::ImplementsClause<'_>>,
|
||||
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
||||
references: &mut VecSet<Symbol>,
|
||||
references: &mut References,
|
||||
) -> Result<(), Type> {
|
||||
let Loc {
|
||||
region,
|
||||
|
@ -1078,7 +1084,7 @@ fn canonicalize_has_clause(
|
|||
{
|
||||
let ability = match ability {
|
||||
TypeAnnotation::Apply(module_name, ident, _type_arguments) => {
|
||||
let symbol = make_apply_symbol(env, region, scope, module_name, ident)?;
|
||||
let symbol = make_apply_symbol(env, region, scope, module_name, ident, references)?;
|
||||
|
||||
// Ability defined locally, whose members we are constructing right now...
|
||||
if !pending_abilities_in_scope.contains_key(&symbol)
|
||||
|
@ -1096,7 +1102,6 @@ fn canonicalize_has_clause(
|
|||
}
|
||||
};
|
||||
|
||||
references.insert(ability);
|
||||
let already_seen = can_abilities.insert(ability);
|
||||
|
||||
if already_seen {
|
||||
|
@ -1130,7 +1135,7 @@ fn can_extension_type(
|
|||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
references: &mut References,
|
||||
opt_ext: &Option<&Loc<TypeAnnotation>>,
|
||||
ext_problem_kind: roc_problem::can::ExtensionTypeKind,
|
||||
) -> (Type, ExtImplicitOpenness) {
|
||||
|
@ -1333,7 +1338,7 @@ fn can_assigned_fields<'a>(
|
|||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
references: &mut References,
|
||||
) -> SendMap<Lowercase, RecordField<Type>> {
|
||||
use roc_parse::ast::AssignedField::*;
|
||||
use roc_types::types::RecordField::*;
|
||||
|
@ -1448,7 +1453,7 @@ fn can_assigned_tuple_elems(
|
|||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
references: &mut References,
|
||||
) -> VecMap<usize, Type> {
|
||||
let mut elem_types = VecMap::with_capacity(elems.len());
|
||||
|
||||
|
@ -1482,7 +1487,7 @@ fn can_tags<'a>(
|
|||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
local_aliases: &mut VecMap<Symbol, Alias>,
|
||||
references: &mut VecSet<Symbol>,
|
||||
references: &mut References,
|
||||
) -> Vec<(TagName, Vec<Type>)> {
|
||||
let mut tag_types = Vec::with_capacity(tags.len());
|
||||
|
||||
|
|
|
@ -150,6 +150,7 @@ map_symbol_to_lowlevel_and_arity! {
|
|||
ListSwap; LIST_SWAP; 3,
|
||||
ListGetCapacity; LIST_CAPACITY; 1,
|
||||
ListReleaseExcessCapacity; LIST_RELEASE_EXCESS_CAPACITY; 1,
|
||||
ListConcatUtf8; LIST_CONCAT_UTF8; 2,
|
||||
|
||||
ListGetUnsafe; DICT_LIST_GET_UNSAFE; 2,
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ use roc_collections::{ImSet, MutMap, SendMap};
|
|||
use roc_error_macros::internal_error;
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_module::ident::QualifiedModuleName;
|
||||
use roc_module::symbol::IdentId;
|
||||
use roc_module::symbol::ModuleId;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
@ -52,6 +54,10 @@ use roc_types::types::MemberImpl;
|
|||
use roc_types::types::OptAbleType;
|
||||
use roc_types::types::{Alias, Type};
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Def {
|
||||
|
@ -158,6 +164,12 @@ enum PendingValueDef<'a> {
|
|||
&'a Loc<ast::TypeAnnotation<'a>>,
|
||||
&'a Loc<ast::Expr<'a>>,
|
||||
),
|
||||
/// Ingested file
|
||||
IngestedFile(
|
||||
Loc<Pattern>,
|
||||
Option<Loc<ast::TypeAnnotation<'a>>>,
|
||||
Loc<ast::StrLiteral<'a>>,
|
||||
),
|
||||
}
|
||||
|
||||
impl PendingValueDef<'_> {
|
||||
|
@ -166,6 +178,7 @@ impl PendingValueDef<'_> {
|
|||
PendingValueDef::AnnotationOnly(_, loc_pattern, _) => loc_pattern,
|
||||
PendingValueDef::Body(loc_pattern, _) => loc_pattern,
|
||||
PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern,
|
||||
PendingValueDef::IngestedFile(loc_pattern, _, _) => loc_pattern,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -357,9 +370,7 @@ fn canonicalize_alias<'a>(
|
|||
);
|
||||
|
||||
// Record all the annotation's references in output.references.lookups
|
||||
for symbol in can_ann.references {
|
||||
output.references.insert_type_lookup(symbol);
|
||||
}
|
||||
output.references.union_mut(&can_ann.references);
|
||||
|
||||
let mut can_vars: Vec<Loc<AliasVar>> = Vec::with_capacity(vars.len());
|
||||
let mut is_phantom = false;
|
||||
|
@ -428,36 +439,54 @@ fn canonicalize_alias<'a>(
|
|||
return Err(());
|
||||
}
|
||||
|
||||
let num_unbound = named.len() + wildcards.len() + inferred.len();
|
||||
if num_unbound > 0 {
|
||||
let one_occurrence = named
|
||||
.iter()
|
||||
.map(|nv| Loc::at(nv.first_seen(), nv.variable()))
|
||||
.chain(wildcards)
|
||||
.chain(inferred)
|
||||
.next()
|
||||
.unwrap()
|
||||
.region;
|
||||
// Report errors for wildcards (*), underscores (_), and named vars that weren't declared.
|
||||
let mut no_problems = true;
|
||||
|
||||
env.problems.push(Problem::UnboundTypeVariable {
|
||||
if let Some(loc_var) = wildcards.first() {
|
||||
env.problems.push(Problem::WildcardNotAllowed {
|
||||
typ: symbol,
|
||||
num_unbound,
|
||||
one_occurrence,
|
||||
num_wildcards: wildcards.len(),
|
||||
one_occurrence: loc_var.region,
|
||||
kind,
|
||||
});
|
||||
|
||||
// Bail out
|
||||
return Err(());
|
||||
no_problems = false;
|
||||
}
|
||||
|
||||
Ok(create_alias(
|
||||
symbol,
|
||||
name.region,
|
||||
can_vars.clone(),
|
||||
infer_ext_in_output,
|
||||
can_ann.typ,
|
||||
kind,
|
||||
))
|
||||
if let Some(loc_var) = inferred.first() {
|
||||
env.problems.push(Problem::UnderscoreNotAllowed {
|
||||
typ: symbol,
|
||||
num_underscores: inferred.len(),
|
||||
one_occurrence: loc_var.region,
|
||||
kind,
|
||||
});
|
||||
|
||||
no_problems = false;
|
||||
}
|
||||
|
||||
if let Some(nv) = named.first() {
|
||||
env.problems.push(Problem::UndeclaredTypeVar {
|
||||
typ: symbol,
|
||||
num_unbound: named.len(),
|
||||
one_occurrence: nv.first_seen(),
|
||||
kind,
|
||||
});
|
||||
|
||||
no_problems = false;
|
||||
}
|
||||
|
||||
if no_problems {
|
||||
Ok(create_alias(
|
||||
symbol,
|
||||
name.region,
|
||||
can_vars.clone(),
|
||||
infer_ext_in_output,
|
||||
can_ann.typ,
|
||||
kind,
|
||||
))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Canonicalizes a claimed ability implementation like `{ eq }` or `{ eq: myEq }`.
|
||||
|
@ -495,7 +524,7 @@ fn canonicalize_claimed_ability_impl<'a>(
|
|||
// OPTION-1: The implementation identifier is the only identifier of that name in the
|
||||
// scope. For example,
|
||||
//
|
||||
// interface F imports [] exposes []
|
||||
// module []
|
||||
//
|
||||
// Hello := {} implements [Encoding.{ toEncoder }]
|
||||
//
|
||||
|
@ -507,7 +536,9 @@ fn canonicalize_claimed_ability_impl<'a>(
|
|||
// OPTION-2: The implementation identifier is a unique shadow of the ability member,
|
||||
// which has also been explicitly imported. For example,
|
||||
//
|
||||
// interface F imports [Encoding.{ toEncoder }] exposes []
|
||||
// module []
|
||||
//
|
||||
// import Encoding exposing [toEncoder]
|
||||
//
|
||||
// Hello := {} implements [Encoding.{ toEncoder }]
|
||||
//
|
||||
|
@ -703,6 +734,8 @@ fn canonicalize_opaque<'a>(
|
|||
AliasKind::Opaque,
|
||||
)?;
|
||||
|
||||
let mut references = References::new();
|
||||
|
||||
let mut derived_defs = Vec::new();
|
||||
if let Some(has_abilities) = has_abilities {
|
||||
let has_abilities = has_abilities.value.collection();
|
||||
|
@ -721,7 +754,8 @@ fn canonicalize_opaque<'a>(
|
|||
// Op := {} has [Eq]
|
||||
let (ability, members) = match ability.value {
|
||||
ast::TypeAnnotation::Apply(module_name, ident, []) => {
|
||||
match make_apply_symbol(env, region, scope, module_name, ident) {
|
||||
match make_apply_symbol(env, region, scope, module_name, ident, &mut references)
|
||||
{
|
||||
Ok(ability) => {
|
||||
let opt_members = scope
|
||||
.abilities_store
|
||||
|
@ -914,6 +948,8 @@ fn canonicalize_opaque<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
output.references.union_mut(&references);
|
||||
|
||||
Ok(CanonicalizedOpaque {
|
||||
opaque_def: alias,
|
||||
derived_defs,
|
||||
|
@ -928,7 +964,12 @@ pub(crate) fn canonicalize_defs<'a>(
|
|||
scope: &mut Scope,
|
||||
loc_defs: &'a mut roc_parse::ast::Defs<'a>,
|
||||
pattern_type: PatternType,
|
||||
) -> (CanDefs, Output, MutMap<Symbol, Region>) {
|
||||
) -> (
|
||||
CanDefs,
|
||||
Output,
|
||||
MutMap<Symbol, Region>,
|
||||
Vec<IntroducedImport>,
|
||||
) {
|
||||
// Canonicalizing defs while detecting shadowing involves a multi-step process:
|
||||
//
|
||||
// 1. Go through each of the patterns.
|
||||
|
@ -978,6 +1019,7 @@ pub(crate) fn canonicalize_defs<'a>(
|
|||
env,
|
||||
var_store,
|
||||
value_def,
|
||||
region,
|
||||
scope,
|
||||
&pending_abilities_in_scope,
|
||||
&mut output,
|
||||
|
@ -1034,7 +1076,12 @@ fn canonicalize_value_defs<'a>(
|
|||
pattern_type: PatternType,
|
||||
mut aliases: VecMap<Symbol, Alias>,
|
||||
mut symbols_introduced: MutMap<Symbol, Region>,
|
||||
) -> (CanDefs, Output, MutMap<Symbol, Region>) {
|
||||
) -> (
|
||||
CanDefs,
|
||||
Output,
|
||||
MutMap<Symbol, Region>,
|
||||
Vec<IntroducedImport>,
|
||||
) {
|
||||
// Canonicalize all the patterns, record shadowing problems, and store
|
||||
// the ast::Expr values in pending_exprs for further canonicalization
|
||||
// once we've finished assembling the entire scope.
|
||||
|
@ -1043,6 +1090,8 @@ fn canonicalize_value_defs<'a>(
|
|||
let mut pending_expects = Vec::with_capacity(value_defs.len());
|
||||
let mut pending_expect_fx = Vec::with_capacity(value_defs.len());
|
||||
|
||||
let mut imports_introduced = Vec::with_capacity(value_defs.len());
|
||||
|
||||
for loc_pending_def in value_defs {
|
||||
match loc_pending_def.value {
|
||||
PendingValue::Def(pending_def) => {
|
||||
|
@ -1062,6 +1111,11 @@ fn canonicalize_value_defs<'a>(
|
|||
PendingValue::ExpectFx(pending_expect) => {
|
||||
pending_expect_fx.push(pending_expect);
|
||||
}
|
||||
PendingValue::ModuleImport(introduced_import) => {
|
||||
imports_introduced.push(introduced_import);
|
||||
}
|
||||
PendingValue::InvalidIngestedFile => { /* skip */ }
|
||||
PendingValue::ImportNameConflict => { /* skip */ }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1171,7 +1225,7 @@ fn canonicalize_value_defs<'a>(
|
|||
aliases,
|
||||
};
|
||||
|
||||
(can_defs, output, symbols_introduced)
|
||||
(can_defs, output, symbols_introduced, imports_introduced)
|
||||
}
|
||||
|
||||
struct CanonicalizedTypeDefs<'a> {
|
||||
|
@ -1385,9 +1439,7 @@ fn resolve_abilities(
|
|||
);
|
||||
|
||||
// Record all the annotation's references in output.references.lookups
|
||||
for symbol in member_annot.references {
|
||||
output.references.insert_type_lookup(symbol);
|
||||
}
|
||||
output.references.union_mut(&member_annot.references);
|
||||
|
||||
// What variables in the annotation are bound to the parent ability, and what variables
|
||||
// are bound to some other ability?
|
||||
|
@ -2302,6 +2354,75 @@ fn canonicalize_pending_value_def<'a>(
|
|||
None,
|
||||
)
|
||||
}
|
||||
IngestedFile(loc_pattern, opt_loc_ann, path_literal) => {
|
||||
let relative_path =
|
||||
if let ast::StrLiteral::PlainLine(ingested_path) = path_literal.value {
|
||||
ingested_path
|
||||
} else {
|
||||
todo!(
|
||||
"Only plain strings are supported. Other cases should be made impossible here"
|
||||
);
|
||||
};
|
||||
|
||||
let mut file_path: PathBuf = env.module_path.into();
|
||||
// Remove the header file name and push the new path.
|
||||
file_path.pop();
|
||||
file_path.push(relative_path);
|
||||
|
||||
let mut bytes = vec![];
|
||||
|
||||
let expr = match fs::File::open(&file_path)
|
||||
.and_then(|mut file| file.read_to_end(&mut bytes))
|
||||
{
|
||||
Ok(_) => Expr::IngestedFile(file_path.into(), Arc::new(bytes), var_store.fresh()),
|
||||
Err(e) => {
|
||||
env.problems.push(Problem::FileProblem {
|
||||
filename: file_path.to_path_buf(),
|
||||
error: e.kind(),
|
||||
});
|
||||
|
||||
Expr::RuntimeError(RuntimeError::ReadIngestedFileError {
|
||||
filename: file_path.to_path_buf(),
|
||||
error: e.kind(),
|
||||
region: path_literal.region,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let loc_expr = Loc::at(path_literal.region, expr);
|
||||
|
||||
let opt_loc_can_ann = if let Some(loc_ann) = opt_loc_ann {
|
||||
let can_ann = canonicalize_annotation(
|
||||
env,
|
||||
scope,
|
||||
&loc_ann.value,
|
||||
loc_ann.region,
|
||||
var_store,
|
||||
pending_abilities_in_scope,
|
||||
AnnotationFor::Value,
|
||||
);
|
||||
|
||||
output.references.union_mut(&can_ann.references);
|
||||
|
||||
Some(Loc::at(loc_ann.region, can_ann))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let def = single_can_def(
|
||||
loc_pattern,
|
||||
loc_expr,
|
||||
var_store.fresh(),
|
||||
opt_loc_can_ann,
|
||||
SendMap::default(),
|
||||
);
|
||||
|
||||
DefOutput {
|
||||
output,
|
||||
references: DefReferences::Value(References::new()),
|
||||
def,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Disallow ability specializations that aren't on the toplevel (note: we might loosen this
|
||||
|
@ -2460,7 +2581,7 @@ pub fn can_defs_with_return<'a>(
|
|||
loc_defs: &'a mut Defs<'a>,
|
||||
loc_ret: &'a Loc<ast::Expr<'a>>,
|
||||
) -> (Expr, Output) {
|
||||
let (unsorted, defs_output, symbols_introduced) = canonicalize_defs(
|
||||
let (unsorted, defs_output, symbols_introduced, imports_introduced) = canonicalize_defs(
|
||||
env,
|
||||
Output::default(),
|
||||
var_store,
|
||||
|
@ -2494,6 +2615,8 @@ pub fn can_defs_with_return<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
report_unused_imports(imports_introduced, &output.references, env, scope);
|
||||
|
||||
let mut loc_expr: Loc<Expr> = ret_expr;
|
||||
|
||||
for declaration in declarations.into_iter().rev() {
|
||||
|
@ -2503,6 +2626,28 @@ pub fn can_defs_with_return<'a>(
|
|||
(loc_expr.value, output)
|
||||
}
|
||||
|
||||
pub fn report_unused_imports(
|
||||
imports_introduced: Vec<IntroducedImport>,
|
||||
references: &References,
|
||||
env: &mut Env<'_>,
|
||||
scope: &mut Scope,
|
||||
) {
|
||||
for import in imports_introduced {
|
||||
if references.has_module_lookup(import.module_id) {
|
||||
for (symbol, region) in &import.exposed_symbols {
|
||||
if !references.has_unqualified_type_or_value_lookup(*symbol)
|
||||
&& !scope.abilities_store.is_specialization_name(*symbol)
|
||||
&& !import.is_task(env)
|
||||
{
|
||||
env.problem(Problem::UnusedImport(*symbol, *region));
|
||||
}
|
||||
}
|
||||
} else if !import.is_task(env) {
|
||||
env.problem(Problem::UnusedModuleImport(import.module_id, import.region));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decl_to_let(decl: Declaration, loc_ret: Loc<Expr>) -> Loc<Expr> {
|
||||
match decl {
|
||||
Declaration::Declare(def) => {
|
||||
|
@ -2570,7 +2715,7 @@ fn to_pending_alias_or_opaque<'a>(
|
|||
|
||||
for loc_var in vars.iter() {
|
||||
match loc_var.value {
|
||||
ast::Pattern::Identifier(name)
|
||||
ast::Pattern::Identifier { ident: name, .. }
|
||||
if name.chars().next().unwrap().is_lowercase() =>
|
||||
{
|
||||
let lowercase = Lowercase::from(name);
|
||||
|
@ -2750,7 +2895,10 @@ enum PendingValue<'a> {
|
|||
Dbg(PendingExpectOrDbg<'a>),
|
||||
Expect(PendingExpectOrDbg<'a>),
|
||||
ExpectFx(PendingExpectOrDbg<'a>),
|
||||
ModuleImport(IntroducedImport),
|
||||
SignatureDefMismatch,
|
||||
InvalidIngestedFile,
|
||||
ImportNameConflict,
|
||||
}
|
||||
|
||||
struct PendingExpectOrDbg<'a> {
|
||||
|
@ -2758,10 +2906,28 @@ struct PendingExpectOrDbg<'a> {
|
|||
preceding_comment: Region,
|
||||
}
|
||||
|
||||
pub struct IntroducedImport {
|
||||
module_id: ModuleId,
|
||||
region: Region,
|
||||
exposed_symbols: Vec<(Symbol, Region)>,
|
||||
}
|
||||
|
||||
impl IntroducedImport {
|
||||
pub fn is_task(&self, env: &Env<'_>) -> bool {
|
||||
// Temporarily needed for `!` convenience. Can be removed when Task becomes a builtin.
|
||||
match env.qualified_module_ids.get_name(self.module_id) {
|
||||
Some(name) => name.as_inner().as_str() == "Task",
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn to_pending_value_def<'a>(
|
||||
env: &mut Env<'a>,
|
||||
var_store: &mut VarStore,
|
||||
def: &'a ast::ValueDef<'a>,
|
||||
region: Region,
|
||||
scope: &mut Scope,
|
||||
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
||||
output: &mut Output,
|
||||
|
@ -2874,6 +3040,118 @@ fn to_pending_value_def<'a>(
|
|||
condition,
|
||||
preceding_comment: *preceding_comment,
|
||||
}),
|
||||
|
||||
ModuleImport(module_import) => {
|
||||
let qualified_module_name: QualifiedModuleName = module_import.name.value.into();
|
||||
let module_name = qualified_module_name.module.clone();
|
||||
let pq_module_name = qualified_module_name.into_pq_module_name(env.opt_shorthand);
|
||||
|
||||
let module_id = env
|
||||
.qualified_module_ids
|
||||
.get_id(&pq_module_name)
|
||||
.expect("Module id should have been added in load");
|
||||
|
||||
let name_with_alias = match module_import.alias {
|
||||
Some(alias) => ModuleName::from(alias.item.value.as_str()),
|
||||
None => module_name.clone(),
|
||||
};
|
||||
|
||||
if let Err(existing_import) =
|
||||
scope
|
||||
.modules
|
||||
.insert(name_with_alias.clone(), module_id, region)
|
||||
{
|
||||
env.problems.push(Problem::ImportNameConflict {
|
||||
name: name_with_alias,
|
||||
is_alias: module_import.alias.is_some(),
|
||||
new_module_id: module_id,
|
||||
new_import_region: region,
|
||||
existing_import,
|
||||
});
|
||||
|
||||
return PendingValue::ImportNameConflict;
|
||||
}
|
||||
|
||||
let exposed_names = module_import
|
||||
.exposed
|
||||
.map(|kw| kw.item.items)
|
||||
.unwrap_or_default();
|
||||
|
||||
if exposed_names.is_empty() && !env.home.is_builtin() && module_id.is_automatically_imported() {
|
||||
env.problems.push(Problem::ExplicitBuiltinImport(module_id, region));
|
||||
}
|
||||
|
||||
let exposed_ids = env
|
||||
.dep_idents
|
||||
.get(&module_id)
|
||||
.expect("Module id should have been added in load");
|
||||
|
||||
let mut exposed_symbols = Vec::with_capacity(exposed_names.len());
|
||||
|
||||
for loc_name in exposed_names {
|
||||
let exposed_name = loc_name.value.item();
|
||||
let name = exposed_name.as_str();
|
||||
let ident = Ident::from(name);
|
||||
|
||||
match exposed_ids.get_id(name) {
|
||||
Some(ident_id) => {
|
||||
let symbol = Symbol::new(module_id, ident_id);
|
||||
exposed_symbols.push((symbol, loc_name.region));
|
||||
|
||||
if let Err((_shadowed_symbol, existing_symbol_region)) = scope.import_symbol(ident, symbol, loc_name.region) {
|
||||
if symbol.is_automatically_imported() {
|
||||
env.problem(Problem::ExplicitBuiltinTypeImport(
|
||||
symbol,
|
||||
loc_name.region,
|
||||
));
|
||||
} else {
|
||||
env.problem(Problem::ImportShadowsSymbol {
|
||||
region: loc_name.region,
|
||||
new_symbol: symbol,
|
||||
existing_symbol_region,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
env.problem(Problem::RuntimeError(RuntimeError::ValueNotExposed {
|
||||
module_name: module_name.clone(),
|
||||
ident,
|
||||
region: loc_name.region,
|
||||
exposed_values: exposed_ids.exposed_values(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PendingValue::ModuleImport(IntroducedImport {
|
||||
module_id,
|
||||
region,
|
||||
exposed_symbols,
|
||||
})
|
||||
}
|
||||
IngestedFileImport(ingested_file) => {
|
||||
let loc_name = ingested_file.name.item;
|
||||
|
||||
let symbol = match scope.introduce(loc_name.value.into(), loc_name.region) {
|
||||
Ok(symbol ) => symbol,
|
||||
Err((original, shadow, _)) => {
|
||||
env.problem(Problem::Shadowing {
|
||||
original_region: original.region,
|
||||
shadow,
|
||||
kind: ShadowKind::Variable
|
||||
});
|
||||
|
||||
return PendingValue::InvalidIngestedFile;
|
||||
}
|
||||
};
|
||||
|
||||
let loc_pattern = Loc::at(loc_name.region, Pattern::Identifier(symbol));
|
||||
|
||||
PendingValue::Def(PendingValueDef::IngestedFile(loc_pattern, ingested_file.annotation.map(|ann| ann.annotation), ingested_file.path))
|
||||
}
|
||||
Stmt(_) => internal_error!("a Stmt was not desugared correctly, should have been converted to a Body(...) in desguar"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,10 @@ fn to_encoder<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
|
|||
let opaque_ref = alloc_pat(ast::Pattern::OpaqueRef(at_opaque));
|
||||
let opaque_apply_pattern = ast::Pattern::Apply(
|
||||
opaque_ref,
|
||||
&*env
|
||||
.arena
|
||||
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(payload))]),
|
||||
&*env.arena.alloc([Loc::at(
|
||||
DERIVED_REGION,
|
||||
ast::Pattern::Identifier { ident: payload },
|
||||
)]),
|
||||
);
|
||||
|
||||
// Encode.toEncoder payload
|
||||
|
@ -96,8 +97,8 @@ fn decoder<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
|
|||
// Decode.mapResult (Decode.decodeWith bytes Decode.decoder fmt) @Opaq
|
||||
let custom_closure = ast::Expr::Closure(
|
||||
env.arena.alloc([
|
||||
Loc::at(DERIVED_REGION, ast::Pattern::Identifier(bytes)),
|
||||
Loc::at(DERIVED_REGION, ast::Pattern::Identifier(fmt)),
|
||||
Loc::at(DERIVED_REGION, ast::Pattern::Identifier { ident: bytes }),
|
||||
Loc::at(DERIVED_REGION, ast::Pattern::Identifier { ident: fmt }),
|
||||
]),
|
||||
alloc_expr(call_map_result),
|
||||
);
|
||||
|
@ -127,9 +128,10 @@ fn hash<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
|
|||
let opaque_ref = alloc_pat(ast::Pattern::OpaqueRef(at_opaque));
|
||||
let opaque_apply_pattern = ast::Pattern::Apply(
|
||||
opaque_ref,
|
||||
&*env
|
||||
.arena
|
||||
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(payload))]),
|
||||
&*env.arena.alloc([Loc::at(
|
||||
DERIVED_REGION,
|
||||
ast::Pattern::Identifier { ident: payload },
|
||||
)]),
|
||||
);
|
||||
|
||||
// Hash.hash hasher payload
|
||||
|
@ -154,7 +156,7 @@ fn hash<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
|
|||
// \hasher, @Opaq payload -> Hash.hash hasher payload
|
||||
ast::Expr::Closure(
|
||||
env.arena.alloc([
|
||||
Loc::at(DERIVED_REGION, ast::Pattern::Identifier(hasher)),
|
||||
Loc::at(DERIVED_REGION, ast::Pattern::Identifier { ident: hasher }),
|
||||
Loc::at(DERIVED_REGION, opaque_apply_pattern),
|
||||
]),
|
||||
call_member,
|
||||
|
@ -172,16 +174,18 @@ fn is_eq<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
|
|||
// \@Opaq payload1
|
||||
let opaque1 = ast::Pattern::Apply(
|
||||
opaque_ref,
|
||||
&*env
|
||||
.arena
|
||||
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(payload1))]),
|
||||
&*env.arena.alloc([Loc::at(
|
||||
DERIVED_REGION,
|
||||
ast::Pattern::Identifier { ident: payload1 },
|
||||
)]),
|
||||
);
|
||||
// \@Opaq payload2
|
||||
let opaque2 = ast::Pattern::Apply(
|
||||
opaque_ref,
|
||||
&*env
|
||||
.arena
|
||||
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(payload2))]),
|
||||
&*env.arena.alloc([Loc::at(
|
||||
DERIVED_REGION,
|
||||
ast::Pattern::Identifier { ident: payload2 },
|
||||
)]),
|
||||
);
|
||||
|
||||
// Bool.isEq payload1 payload2
|
||||
|
@ -224,9 +228,10 @@ fn to_inspector<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
|
|||
let opaque_ref = alloc_pat(ast::Pattern::OpaqueRef(at_opaque));
|
||||
let opaque_apply_pattern = ast::Pattern::Apply(
|
||||
opaque_ref,
|
||||
&*env
|
||||
.arena
|
||||
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(payload))]),
|
||||
&*env.arena.alloc([Loc::at(
|
||||
DERIVED_REGION,
|
||||
ast::Pattern::Identifier { ident: payload },
|
||||
)]),
|
||||
);
|
||||
|
||||
// Inspect.toInspector payload
|
||||
|
@ -276,8 +281,10 @@ fn to_inspector<'a>(env: &mut Env<'a>, at_opaque: &'a str) -> ast::Expr<'a> {
|
|||
));
|
||||
|
||||
let custom_closure = alloc_expr(ast::Expr::Closure(
|
||||
env.arena
|
||||
.alloc([Loc::at(DERIVED_REGION, ast::Pattern::Identifier(fmt))]),
|
||||
env.arena.alloc([Loc::at(
|
||||
DERIVED_REGION,
|
||||
ast::Pattern::Identifier { ident: fmt },
|
||||
)]),
|
||||
apply_opaque_inspector,
|
||||
));
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#![allow(clippy::manual_map)]
|
||||
|
||||
use crate::suffixed::{apply_task_await, unwrap_suffixed_expression, EUnwrapped};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_error_macros::internal_error;
|
||||
|
@ -8,8 +9,8 @@ use roc_module::called_via::{BinOp, CalledVia};
|
|||
use roc_module::ident::ModuleName;
|
||||
use roc_parse::ast::Expr::{self, *};
|
||||
use roc_parse::ast::{
|
||||
AssignedField, Collection, Pattern, RecordBuilderField, StrLiteral, StrSegment, ValueDef,
|
||||
WhenBranch,
|
||||
AssignedField, Collection, ModuleImportParams, Pattern, RecordBuilderField, StrLiteral,
|
||||
StrSegment, ValueDef, WhenBranch,
|
||||
};
|
||||
use roc_region::all::{LineInfo, Loc, Region};
|
||||
|
||||
|
@ -92,9 +93,10 @@ fn desugar_value_def<'a>(
|
|||
ann_pattern,
|
||||
ann_type,
|
||||
comment: *comment,
|
||||
body_pattern,
|
||||
body_pattern: desugar_loc_pattern(arena, body_pattern, src, line_info, module_path),
|
||||
body_expr: desugar_expr(arena, body_expr, src, line_info, module_path),
|
||||
},
|
||||
|
||||
Dbg {
|
||||
condition,
|
||||
preceding_comment,
|
||||
|
@ -128,6 +130,40 @@ fn desugar_value_def<'a>(
|
|||
preceding_comment: *preceding_comment,
|
||||
}
|
||||
}
|
||||
ModuleImport(roc_parse::ast::ModuleImport {
|
||||
before_name,
|
||||
name,
|
||||
params,
|
||||
alias,
|
||||
exposed,
|
||||
}) => {
|
||||
let desugared_params =
|
||||
params.map(|ModuleImportParams { before, params }| ModuleImportParams {
|
||||
before,
|
||||
params: desugar_field_collection(arena, params, src, line_info, module_path),
|
||||
});
|
||||
|
||||
ModuleImport(roc_parse::ast::ModuleImport {
|
||||
before_name,
|
||||
name: *name,
|
||||
params: desugared_params,
|
||||
alias: *alias,
|
||||
exposed: *exposed,
|
||||
})
|
||||
}
|
||||
IngestedFileImport(_) => *def,
|
||||
|
||||
Stmt(stmt_expr) => {
|
||||
// desugar into a Body({}, stmt_expr)
|
||||
let loc_pattern = arena.alloc(Loc::at(
|
||||
stmt_expr.region,
|
||||
Pattern::RecordDestructure(Collection::empty()),
|
||||
));
|
||||
Body(
|
||||
loc_pattern,
|
||||
desugar_expr(arena, stmt_expr, src, line_info, module_path),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,227 +173,109 @@ pub fn desugar_defs_node_values<'a>(
|
|||
src: &'a str,
|
||||
line_info: &mut Option<LineInfo>,
|
||||
module_path: &str,
|
||||
top_level_def: bool,
|
||||
) {
|
||||
for value_def in defs.value_defs.iter_mut() {
|
||||
*value_def = desugar_value_def(arena, arena.alloc(*value_def), src, line_info, module_path);
|
||||
}
|
||||
}
|
||||
|
||||
fn desugar_defs_node_suffixed<'a>(
|
||||
arena: &'a Bump,
|
||||
loc_expr: &'a Loc<Expr<'a>>,
|
||||
) -> &'a Loc<Expr<'a>> {
|
||||
match loc_expr.value {
|
||||
Defs(defs, loc_ret) => {
|
||||
match defs.search_suffixed_defs() {
|
||||
None => loc_expr,
|
||||
Some((tag_index, value_index)) => {
|
||||
if defs.value_defs.len() == 1 {
|
||||
// We have only one value_def and it must be Suffixed
|
||||
// replace Defs with an Apply(Task.await) and Closure of loc_return
|
||||
|
||||
debug_assert!(
|
||||
value_index == 0,
|
||||
"we have only one value_def and so it must be Suffixed "
|
||||
);
|
||||
|
||||
// Unwrap Suffixed def within Apply, and the pattern so we can use in the call to Task.await
|
||||
let (suffixed_sub_apply_loc, pattern) = unwrap_suffixed_def_and_pattern(
|
||||
arena,
|
||||
loc_expr.region,
|
||||
defs.value_defs[0],
|
||||
);
|
||||
|
||||
// Create Closure for the result of the recursion,
|
||||
// use the pattern from our Suffixed Def as closure argument
|
||||
let closure_expr = Closure(arena.alloc([*pattern]), loc_ret);
|
||||
|
||||
// Apply arguments to Task.await, first is the unwrapped Suffix expr second is the Closure
|
||||
let mut task_await_apply_args: Vec<&'a Loc<Expr<'a>>> = Vec::new_in(arena);
|
||||
|
||||
task_await_apply_args
|
||||
.push(arena.alloc(Loc::at(loc_expr.region, suffixed_sub_apply_loc)));
|
||||
task_await_apply_args
|
||||
.push(arena.alloc(Loc::at(loc_expr.region, closure_expr)));
|
||||
|
||||
arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Apply(
|
||||
arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: Var {
|
||||
module_name: ModuleName::TASK,
|
||||
ident: "await",
|
||||
},
|
||||
}),
|
||||
arena.alloc(task_await_apply_args),
|
||||
CalledVia::BangSuffix,
|
||||
),
|
||||
))
|
||||
} else if value_index == 0 {
|
||||
// We have a Suffixed in first index, and also other nodes in Defs
|
||||
// pop the first Suffixed and recurse on Defs (without first) to handle any other Suffixed
|
||||
// the result will be wrapped in an Apply(Task.await) and Closure
|
||||
|
||||
debug_assert!(
|
||||
defs.value_defs.len() > 1,
|
||||
"we know we have other Defs that will need to be considered"
|
||||
);
|
||||
|
||||
// Unwrap Suffixed def within Apply, and the pattern so we can use in the call to Task.await
|
||||
let (suffixed_sub_apply_loc, pattern) = unwrap_suffixed_def_and_pattern(
|
||||
arena,
|
||||
loc_expr.region,
|
||||
defs.value_defs[0],
|
||||
);
|
||||
|
||||
// Get a mutable copy of the defs
|
||||
let mut copied_defs = defs.clone();
|
||||
|
||||
// Remove the suffixed def
|
||||
copied_defs.remove_value_def(tag_index);
|
||||
|
||||
// Recurse using new Defs to get new expression
|
||||
let new_loc_expr = desugar_defs_node_suffixed(
|
||||
arena,
|
||||
arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Defs(arena.alloc(copied_defs), loc_ret),
|
||||
)),
|
||||
);
|
||||
|
||||
// Create Closure for the result of the recursion,
|
||||
// use the pattern from our Suffixed Def as closure argument
|
||||
let closure_expr = Closure(arena.alloc([*pattern]), new_loc_expr);
|
||||
|
||||
// Apply arguments to Task.await, first is the unwrapped Suffix expr second is the Closure
|
||||
let mut task_await_apply_args: Vec<&'a Loc<Expr<'a>>> = Vec::new_in(arena);
|
||||
|
||||
task_await_apply_args
|
||||
.push(arena.alloc(Loc::at(loc_expr.region, suffixed_sub_apply_loc)));
|
||||
task_await_apply_args
|
||||
.push(arena.alloc(Loc::at(loc_expr.region, closure_expr)));
|
||||
|
||||
arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Apply(
|
||||
arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: Var {
|
||||
module_name: ModuleName::TASK,
|
||||
ident: "await",
|
||||
},
|
||||
}),
|
||||
arena.alloc(task_await_apply_args),
|
||||
CalledVia::BangSuffix,
|
||||
),
|
||||
))
|
||||
} else {
|
||||
// The first Suffixed is in the middle of our Defs
|
||||
// We will keep the defs before the Suffixed in our Defs node
|
||||
// We take the defs after the Suffixed and create a new Defs node using the current loc_return
|
||||
// Then recurse on the new Defs node, wrap the result in an Apply(Task.await) and Closure,
|
||||
// which will become the new loc_return
|
||||
|
||||
let (before, after) = {
|
||||
let values = defs.split_values_either_side_of(tag_index);
|
||||
(values.before, values.after)
|
||||
};
|
||||
|
||||
// If there are no defs after, then just use loc_ret as we dont need a Defs node
|
||||
let defs_after_suffixed_desugared = {
|
||||
if !after.is_empty() {
|
||||
desugar_defs_node_suffixed(
|
||||
arena,
|
||||
arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Defs(arena.alloc(after), loc_ret),
|
||||
)),
|
||||
)
|
||||
} else {
|
||||
loc_ret
|
||||
}
|
||||
};
|
||||
|
||||
// Unwrap Suffixed def within Apply, and the pattern so we can use in the call to Task.await
|
||||
let (suffixed_sub_apply_loc, pattern) = unwrap_suffixed_def_and_pattern(
|
||||
arena,
|
||||
loc_expr.region,
|
||||
defs.value_defs[value_index],
|
||||
);
|
||||
|
||||
// Create Closure for the result of the recursion,
|
||||
// use the pattern from our Suffixed Def as closure argument
|
||||
let closure_expr =
|
||||
Closure(arena.alloc([*pattern]), defs_after_suffixed_desugared);
|
||||
|
||||
// Apply arguments to Task.await, first is the unwrapped Suffix expr second is the Closure
|
||||
let mut task_await_apply_args: Vec<&'a Loc<Expr<'a>>> = Vec::new_in(arena);
|
||||
|
||||
task_await_apply_args
|
||||
.push(arena.alloc(Loc::at(loc_expr.region, suffixed_sub_apply_loc)));
|
||||
task_await_apply_args
|
||||
.push(arena.alloc(Loc::at(loc_expr.region, closure_expr)));
|
||||
|
||||
let new_loc_return = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Apply(
|
||||
arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: Var {
|
||||
module_name: ModuleName::TASK,
|
||||
ident: "await",
|
||||
},
|
||||
}),
|
||||
arena.alloc(task_await_apply_args),
|
||||
CalledVia::BangSuffix,
|
||||
),
|
||||
));
|
||||
|
||||
arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Defs(arena.alloc(before), new_loc_return),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
// `desugar_defs_node_values` is called recursively in `desugar_expr`
|
||||
// and we only want to unwrap suffixed nodes if they are a top level def.
|
||||
//
|
||||
// check here first so we only unwrap the expressions once, and after they have
|
||||
// been desugared
|
||||
if top_level_def {
|
||||
for value_def in defs.value_defs.iter_mut() {
|
||||
*value_def = desugar_value_def_suffixed(arena, *value_def);
|
||||
}
|
||||
_ => unreachable!(
|
||||
"should only be passed a Defs node as it is called from within desugar_expr for Defs"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap Suffixed def within Apply, and the pattern so we can use in the call to Task.await
|
||||
fn unwrap_suffixed_def_and_pattern<'a>(
|
||||
arena: &'a Bump,
|
||||
region: Region,
|
||||
value_def: ValueDef<'a>,
|
||||
) -> (
|
||||
roc_parse::ast::Expr<'a>,
|
||||
&'a Loc<roc_parse::ast::Pattern<'a>>,
|
||||
) {
|
||||
/// For each top-level ValueDef in our module, we will unwrap any suffixed
|
||||
/// expressions
|
||||
///
|
||||
/// e.g. `say! "hi"` desugars to `Task.await (say "hi") \{} -> ...`
|
||||
pub fn desugar_value_def_suffixed<'a>(arena: &'a Bump, value_def: ValueDef<'a>) -> ValueDef<'a> {
|
||||
use ValueDef::*;
|
||||
|
||||
match value_def {
|
||||
ValueDef::Body(pattern, suffixed_expression) => match suffixed_expression.value {
|
||||
// The Suffixed has arguments applied e.g. `Stdout.line! "Hello World"`
|
||||
Apply(sub_loc, suffixed_args, called_via) => match sub_loc.value {
|
||||
Suffixed(sub_expr) => (
|
||||
Apply(
|
||||
arena.alloc(Loc::at(region, *sub_expr)),
|
||||
suffixed_args,
|
||||
called_via,
|
||||
Body(loc_pattern, loc_expr) => {
|
||||
// note called_from_def is passed as `false` as this is a top_level_def
|
||||
match unwrap_suffixed_expression(arena, loc_expr, None) {
|
||||
Ok(new_expr) => Body(loc_pattern, new_expr),
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
}) => desugar_value_def_suffixed(
|
||||
arena,
|
||||
Body(
|
||||
loc_pattern,
|
||||
apply_task_await(arena, loc_expr.region, sub_arg, sub_pat, sub_new),
|
||||
),
|
||||
pattern,
|
||||
),
|
||||
_ => unreachable!("should have a suffixed Apply inside Body def"),
|
||||
},
|
||||
// The Suffixed has NIL arguments applied e.g. `Stdin.line!`
|
||||
Suffixed(sub_expr) => (*sub_expr, pattern),
|
||||
_ => {
|
||||
unreachable!("should have a suffixed Apply inside Body def")
|
||||
Err(..) => Body(
|
||||
loc_pattern,
|
||||
arena.alloc(Loc::at(loc_expr.region, MalformedSuffixed(loc_expr))),
|
||||
),
|
||||
}
|
||||
},
|
||||
_ => unreachable!("should have a suffixed Body def"),
|
||||
}
|
||||
ann @ Annotation(_, _) => ann,
|
||||
AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment,
|
||||
body_pattern,
|
||||
body_expr,
|
||||
} => {
|
||||
// note called_from_def is passed as `false` as this is a top_level_def
|
||||
match unwrap_suffixed_expression(arena, body_expr, None) {
|
||||
Ok(new_expr) => AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment,
|
||||
body_pattern,
|
||||
body_expr: new_expr,
|
||||
},
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
}) => desugar_value_def_suffixed(
|
||||
arena,
|
||||
AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment,
|
||||
body_pattern,
|
||||
body_expr: apply_task_await(
|
||||
arena,
|
||||
body_expr.region,
|
||||
sub_arg,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
),
|
||||
},
|
||||
),
|
||||
Err(..) => AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment,
|
||||
body_pattern,
|
||||
body_expr: arena.alloc(Loc::at(body_expr.region, MalformedSuffixed(body_expr))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO support desugaring of Dbg, Expect, and ExpectFx
|
||||
Dbg { .. } | Expect { .. } | ExpectFx { .. } => value_def,
|
||||
ModuleImport { .. } | IngestedFileImport(_) => value_def,
|
||||
|
||||
Stmt(..) => {
|
||||
internal_error!(
|
||||
"this should have been desugared into a Body(..) before this call in desugar_expr"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -380,12 +298,12 @@ pub fn desugar_expr<'a>(
|
|||
| Underscore { .. }
|
||||
| MalformedIdent(_, _)
|
||||
| MalformedClosure
|
||||
| MalformedSuffixed(..)
|
||||
| PrecedenceConflict { .. }
|
||||
| MultipleRecordBuilders { .. }
|
||||
| UnappliedRecordBuilder { .. }
|
||||
| Tag(_)
|
||||
| OpaqueRef(_)
|
||||
| IngestedFile(_, _)
|
||||
| Crash => loc_expr,
|
||||
|
||||
Str(str_literal) => match str_literal {
|
||||
|
@ -436,6 +354,15 @@ pub fn desugar_expr<'a>(
|
|||
|
||||
arena.alloc(Loc { region, value })
|
||||
}
|
||||
// desugar the sub_expression, but leave the TaskAwaitBang as this will
|
||||
// be unwrapped later in desugar_value_def_suffixed
|
||||
TaskAwaitBang(sub_expr) => {
|
||||
let intermediate = arena.alloc(Loc::at(loc_expr.region, **sub_expr));
|
||||
let new_sub_loc_expr = desugar_expr(arena, intermediate, src, line_info, module_path);
|
||||
let new_sub_expr = arena.alloc(new_sub_loc_expr.value);
|
||||
|
||||
arena.alloc(Loc::at(loc_expr.region, TaskAwaitBang(new_sub_expr)))
|
||||
}
|
||||
RecordAccess(sub_expr, paths) => {
|
||||
let region = loc_expr.region;
|
||||
let loc_sub_expr = Loc {
|
||||
|
@ -471,15 +398,7 @@ pub fn desugar_expr<'a>(
|
|||
})
|
||||
}
|
||||
Record(fields) => {
|
||||
let mut allocated = Vec::with_capacity_in(fields.len(), arena);
|
||||
for field in fields.iter() {
|
||||
let value = desugar_field(arena, &field.value, src, line_info, module_path);
|
||||
allocated.push(Loc {
|
||||
value,
|
||||
region: field.region,
|
||||
});
|
||||
}
|
||||
let fields = fields.replace_items(allocated.into_bump_slice());
|
||||
let fields = desugar_field_collection(arena, *fields, src, line_info, module_path);
|
||||
arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: Record(fields),
|
||||
|
@ -581,14 +500,10 @@ pub fn desugar_expr<'a>(
|
|||
),
|
||||
Defs(defs, loc_ret) => {
|
||||
let mut defs = (*defs).clone();
|
||||
desugar_defs_node_values(arena, &mut defs, src, line_info, module_path);
|
||||
desugar_defs_node_values(arena, &mut defs, src, line_info, module_path, false);
|
||||
let loc_ret = desugar_expr(arena, loc_ret, src, line_info, module_path);
|
||||
|
||||
// Desugar any Suffixed nodes
|
||||
desugar_defs_node_suffixed(
|
||||
arena,
|
||||
arena.alloc(Loc::at(loc_expr.region, Defs(arena.alloc(defs), loc_ret))),
|
||||
)
|
||||
arena.alloc(Loc::at(loc_expr.region, Defs(arena.alloc(defs), loc_ret)))
|
||||
}
|
||||
Apply(loc_fn, loc_args, called_via) => {
|
||||
let mut desugared_args = Vec::with_capacity_in(loc_args.len(), arena);
|
||||
|
@ -839,31 +754,31 @@ pub fn desugar_expr<'a>(
|
|||
region: loc_expr.region,
|
||||
})
|
||||
}
|
||||
LowLevelDbg(_, _, _) => unreachable!("Only exists after desugaring"),
|
||||
Suffixed(expr) => {
|
||||
// Rewrite `Suffixed(BinOps([args...], Var(...)))` to `BinOps([args...], Suffixed(Var(...)))`
|
||||
// This is to handle cases like e.g. `"Hello" |> line!`
|
||||
if let BinOps(args, sub_expr) = expr {
|
||||
return desugar_expr(
|
||||
arena,
|
||||
|
||||
// Replace an empty final def with a `Task.ok {}`
|
||||
EmptyDefsFinal => {
|
||||
let mut apply_args: Vec<&'a Loc<Expr<'a>>> = Vec::new_in(arena);
|
||||
apply_args
|
||||
.push(arena.alloc(Loc::at(loc_expr.region, Expr::Record(Collection::empty()))));
|
||||
|
||||
arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Expr::Apply(
|
||||
arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
BinOps(
|
||||
args,
|
||||
arena.alloc(Loc::at(sub_expr.region, Suffixed(&sub_expr.value))),
|
||||
),
|
||||
Expr::Var {
|
||||
module_name: ModuleName::TASK,
|
||||
ident: "ok",
|
||||
},
|
||||
)),
|
||||
src,
|
||||
line_info,
|
||||
module_path,
|
||||
);
|
||||
}
|
||||
|
||||
// Suffixed are also desugared in Defs
|
||||
// Any nodes that don't get desugared will be caught by canonicalize_expr
|
||||
// and we can handle those cases as required
|
||||
loc_expr
|
||||
arena.alloc(apply_args),
|
||||
CalledVia::BangSuffix,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
// note this only exists after desugaring
|
||||
LowLevelDbg(_, _, _) => loc_expr,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -917,6 +832,24 @@ fn desugar_str_segments<'a>(
|
|||
.into_bump_slice()
|
||||
}
|
||||
|
||||
fn desugar_field_collection<'a>(
|
||||
arena: &'a Bump,
|
||||
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
|
||||
src: &'a str,
|
||||
line_info: &mut Option<LineInfo>,
|
||||
module_path: &str,
|
||||
) -> Collection<'a, Loc<AssignedField<'a, Expr<'a>>>> {
|
||||
let mut allocated = Vec::with_capacity_in(fields.len(), arena);
|
||||
|
||||
for field in fields.iter() {
|
||||
let value = desugar_field(arena, &field.value, src, line_info, module_path);
|
||||
|
||||
allocated.push(Loc::at(field.region, value));
|
||||
}
|
||||
|
||||
fields.replace_items(allocated.into_bump_slice())
|
||||
}
|
||||
|
||||
fn desugar_field<'a>(
|
||||
arena: &'a Bump,
|
||||
field: &'a AssignedField<'a, Expr<'a>>,
|
||||
|
@ -1009,7 +942,7 @@ fn desugar_pattern<'a>(
|
|||
use roc_parse::ast::Pattern::*;
|
||||
|
||||
match pattern {
|
||||
Identifier(_)
|
||||
Identifier { .. }
|
||||
| Tag(_)
|
||||
| OpaqueRef(_)
|
||||
| NumLiteral(_)
|
||||
|
@ -1168,7 +1101,7 @@ fn record_builder_arg<'a>(
|
|||
|
||||
for label in apply_field_names.iter().rev() {
|
||||
let name = arena.alloc("#".to_owned() + label.value);
|
||||
let ident = roc_parse::ast::Pattern::Identifier(name);
|
||||
let ident = roc_parse::ast::Pattern::Identifier { ident: name };
|
||||
|
||||
let arg_pattern = arena.alloc(Loc {
|
||||
value: ident,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::procedure::References;
|
||||
use crate::scope::Scope;
|
||||
use bumpalo::Bump;
|
||||
use roc_collections::{MutMap, VecSet};
|
||||
use roc_module::ident::{Ident, Lowercase, ModuleName};
|
||||
use roc_module::symbol::{IdentIdsByModule, ModuleId, ModuleIds, Symbol};
|
||||
use roc_module::ident::{Ident, ModuleName};
|
||||
use roc_module::symbol::{IdentIdsByModule, ModuleId, PQModuleName, PackageModuleIds, Symbol};
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
||||
|
@ -13,9 +15,11 @@ pub struct Env<'a> {
|
|||
/// are assumed to be relative to this path.
|
||||
pub home: ModuleId,
|
||||
|
||||
pub module_path: &'a Path,
|
||||
|
||||
pub dep_idents: &'a IdentIdsByModule,
|
||||
|
||||
pub module_ids: &'a ModuleIds,
|
||||
pub qualified_module_ids: &'a PackageModuleIds<'a>,
|
||||
|
||||
/// Problems we've encountered along the way, which will be reported to the user at the end.
|
||||
pub problems: Vec<Problem>,
|
||||
|
@ -35,26 +39,32 @@ pub struct Env<'a> {
|
|||
pub top_level_symbols: VecSet<Symbol>,
|
||||
|
||||
pub arena: &'a Bump,
|
||||
|
||||
pub opt_shorthand: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Env<'a> {
|
||||
pub fn new(
|
||||
arena: &'a Bump,
|
||||
home: ModuleId,
|
||||
module_path: &'a Path,
|
||||
dep_idents: &'a IdentIdsByModule,
|
||||
module_ids: &'a ModuleIds,
|
||||
qualified_module_ids: &'a PackageModuleIds<'a>,
|
||||
opt_shorthand: Option<&'a str>,
|
||||
) -> Env<'a> {
|
||||
Env {
|
||||
arena,
|
||||
home,
|
||||
module_path,
|
||||
dep_idents,
|
||||
module_ids,
|
||||
qualified_module_ids,
|
||||
problems: Vec::new(),
|
||||
closures: MutMap::default(),
|
||||
qualified_value_lookups: VecSet::default(),
|
||||
qualified_type_lookups: VecSet::default(),
|
||||
tailcallable_symbol: None,
|
||||
top_level_symbols: VecSet::default(),
|
||||
opt_shorthand,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,17 +82,20 @@ impl<'a> Env<'a> {
|
|||
|
||||
let module_name = ModuleName::from(module_name_str);
|
||||
|
||||
match self.module_ids.get_id(&module_name) {
|
||||
match scope.modules.get_id(&module_name) {
|
||||
Some(module_id) => self.qualified_lookup_help(scope, module_id, ident, region),
|
||||
None => Err(RuntimeError::ModuleNotImported {
|
||||
module_name,
|
||||
imported_modules: self
|
||||
.module_ids
|
||||
.available_modules()
|
||||
module_name: module_name.clone(),
|
||||
imported_modules: scope
|
||||
.modules
|
||||
.available_names()
|
||||
.map(|string| string.as_ref().into())
|
||||
.collect(),
|
||||
region,
|
||||
module_exists: false,
|
||||
module_exists: self
|
||||
.qualified_module_ids
|
||||
.get_id(&PQModuleName::Unqualified(module_name))
|
||||
.is_some(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +107,11 @@ impl<'a> Env<'a> {
|
|||
ident: &str,
|
||||
region: Region,
|
||||
) -> Result<Symbol, RuntimeError> {
|
||||
self.qualified_lookup_help(scope, module_id, ident, region)
|
||||
if !scope.modules.has_id(module_id) {
|
||||
Err(self.module_exists_but_not_imported(scope, module_id, region))
|
||||
} else {
|
||||
self.qualified_lookup_help(scope, module_id, ident, region)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns Err if the symbol resolved, but it was not exposed by the given module
|
||||
|
@ -153,43 +170,46 @@ impl<'a> Env<'a> {
|
|||
|
||||
Ok(symbol)
|
||||
}
|
||||
None => {
|
||||
let exposed_values = exposed_ids
|
||||
.ident_strs()
|
||||
.filter(|(_, ident)| ident.starts_with(|c: char| c.is_lowercase()))
|
||||
.map(|(_, ident)| Lowercase::from(ident))
|
||||
.collect();
|
||||
Err(RuntimeError::ValueNotExposed {
|
||||
module_name: self
|
||||
.module_ids
|
||||
.get_name(module_id)
|
||||
.expect("Module ID known, but not in the module IDs somehow")
|
||||
.clone(),
|
||||
ident: Ident::from(ident),
|
||||
region,
|
||||
exposed_values,
|
||||
})
|
||||
}
|
||||
None => Err(RuntimeError::ValueNotExposed {
|
||||
module_name: self
|
||||
.qualified_module_ids
|
||||
.get_name(module_id)
|
||||
.expect("Module ID known, but not in the module IDs somehow")
|
||||
.as_inner()
|
||||
.clone(),
|
||||
ident: Ident::from(ident),
|
||||
region,
|
||||
exposed_values: exposed_ids.exposed_values(),
|
||||
}),
|
||||
},
|
||||
None => Err(RuntimeError::ModuleNotImported {
|
||||
module_name: self
|
||||
.module_ids
|
||||
.get_name(module_id)
|
||||
.expect("Module ID known, but not in the module IDs somehow")
|
||||
.clone(),
|
||||
imported_modules: self
|
||||
.dep_idents
|
||||
.keys()
|
||||
.filter_map(|module_id| self.module_ids.get_name(*module_id))
|
||||
.map(|module_name| module_name.as_ref().into())
|
||||
.collect(),
|
||||
region,
|
||||
module_exists: true,
|
||||
}),
|
||||
_ => Err(self.module_exists_but_not_imported(scope, module_id, region)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn module_exists_but_not_imported(
|
||||
&self,
|
||||
scope: &Scope,
|
||||
module_id: ModuleId,
|
||||
region: Region,
|
||||
) -> RuntimeError {
|
||||
RuntimeError::ModuleNotImported {
|
||||
module_name: self
|
||||
.qualified_module_ids
|
||||
.get_name(module_id)
|
||||
.expect("Module ID known, but not in the module IDs somehow")
|
||||
.as_inner()
|
||||
.clone(),
|
||||
imported_modules: scope
|
||||
.modules
|
||||
.available_names()
|
||||
.map(|string| string.as_ref().into())
|
||||
.collect(),
|
||||
region,
|
||||
module_exists: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn problem(&mut self, problem: Problem) {
|
||||
self.problems.push(problem)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::num::{
|
|||
int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumBound,
|
||||
};
|
||||
use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern, PermitShadows};
|
||||
use crate::procedure::References;
|
||||
use crate::procedure::{QualifiedReference, References};
|
||||
use crate::scope::Scope;
|
||||
use crate::traverse::{walk_expr, Visitor};
|
||||
use roc_collections::soa::Index;
|
||||
|
@ -27,8 +27,6 @@ use roc_types::num::SingleQuoteBound;
|
|||
use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
|
||||
use roc_types::types::{Alias, Category, IndexOrField, LambdaSet, OptAbleVar, Type};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::{char, u32};
|
||||
|
@ -623,6 +621,9 @@ pub fn canonicalize_expr<'a>(
|
|||
use Expr::*;
|
||||
|
||||
let (expr, output) = match expr {
|
||||
&ast::Expr::EmptyDefsFinal => {
|
||||
internal_error!("EmptyDefsFinal should have been desugared")
|
||||
}
|
||||
&ast::Expr::Num(str) => {
|
||||
let answer = num_expr_from_result(var_store, finish_parsing_num(str), region, env);
|
||||
|
||||
|
@ -739,48 +740,6 @@ pub fn canonicalize_expr<'a>(
|
|||
|
||||
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
|
||||
|
||||
ast::Expr::IngestedFile(file_path, _) => match File::open(file_path) {
|
||||
Ok(mut file) => {
|
||||
let mut bytes = vec![];
|
||||
match file.read_to_end(&mut bytes) {
|
||||
Ok(_) => (
|
||||
Expr::IngestedFile(
|
||||
file_path.to_path_buf().into(),
|
||||
Arc::new(bytes),
|
||||
var_store.fresh(),
|
||||
),
|
||||
Output::default(),
|
||||
),
|
||||
Err(e) => {
|
||||
env.problems.push(Problem::FileProblem {
|
||||
filename: file_path.to_path_buf(),
|
||||
error: e.kind(),
|
||||
});
|
||||
|
||||
// This will not manifest as a real runtime error and is just returned to have a value here.
|
||||
// The pushed FileProblem will be fatal to compilation.
|
||||
(
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::NoImplementation),
|
||||
Output::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
env.problems.push(Problem::FileProblem {
|
||||
filename: file_path.to_path_buf(),
|
||||
error: e.kind(),
|
||||
});
|
||||
|
||||
// This will not manifest as a real runtime error and is just returned to have a value here.
|
||||
// The pushed FileProblem will be fatal to compilation.
|
||||
(
|
||||
Expr::RuntimeError(roc_problem::can::RuntimeError::NoImplementation),
|
||||
Output::default(),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
ast::Expr::SingleQuote(string) => {
|
||||
let mut it = string.chars().peekable();
|
||||
if let Some(char) = it.next() {
|
||||
|
@ -882,7 +841,9 @@ pub fn canonicalize_expr<'a>(
|
|||
}
|
||||
Ok((name, opaque_def)) => {
|
||||
let argument = Box::new(args.pop().unwrap());
|
||||
output.references.insert_type_lookup(name);
|
||||
output
|
||||
.references
|
||||
.insert_type_lookup(name, QualifiedReference::Unqualified);
|
||||
|
||||
let (type_arguments, lambda_set_variables, specialized_def_type) =
|
||||
freshen_opaque_def(var_store, opaque_def);
|
||||
|
@ -1166,6 +1127,7 @@ pub fn canonicalize_expr<'a>(
|
|||
output,
|
||||
)
|
||||
}
|
||||
ast::Expr::TaskAwaitBang(..) => internal_error!("a Expr::TaskAwaitBang expression was not completely removed in desugar_value_def_suffixed"),
|
||||
ast::Expr::Tag(tag) => {
|
||||
let variant_var = var_store.fresh();
|
||||
let ext_var = var_store.fresh();
|
||||
|
@ -1193,7 +1155,9 @@ pub fn canonicalize_expr<'a>(
|
|||
}
|
||||
Ok((name, opaque_def)) => {
|
||||
let mut output = Output::default();
|
||||
output.references.insert_type_lookup(name);
|
||||
output
|
||||
.references
|
||||
.insert_type_lookup(name, QualifiedReference::Unqualified);
|
||||
|
||||
let (type_arguments, lambda_set_variables, specialized_def_type) =
|
||||
freshen_opaque_def(var_store, opaque_def);
|
||||
|
@ -1375,6 +1339,10 @@ pub fn canonicalize_expr<'a>(
|
|||
|
||||
(RuntimeError(problem), Output::default())
|
||||
}
|
||||
ast::Expr::MalformedSuffixed(..) => {
|
||||
use roc_problem::can::RuntimeError::*;
|
||||
(RuntimeError(MalformedSuffixed(region)), Output::default())
|
||||
}
|
||||
ast::Expr::MultipleRecordBuilders(sub_expr) => {
|
||||
use roc_problem::can::RuntimeError::*;
|
||||
|
||||
|
@ -1441,12 +1409,6 @@ pub fn canonicalize_expr<'a>(
|
|||
bad_expr
|
||||
);
|
||||
}
|
||||
bad_expr @ ast::Expr::Suffixed(_) => {
|
||||
internal_error!(
|
||||
"A suffixed expression did not get desugared somehow: {:#?}",
|
||||
bad_expr
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// At the end, diff used_idents and defined_idents to see which were unused.
|
||||
|
@ -1890,7 +1852,9 @@ fn canonicalize_var_lookup(
|
|||
// Look it up in scope!
|
||||
match scope.lookup_str(ident, region) {
|
||||
Ok(symbol) => {
|
||||
output.references.insert_value_lookup(symbol);
|
||||
output
|
||||
.references
|
||||
.insert_value_lookup(symbol, QualifiedReference::Unqualified);
|
||||
|
||||
if scope.abilities_store.is_ability_member_name(symbol) {
|
||||
AbilityMember(
|
||||
|
@ -1913,7 +1877,9 @@ fn canonicalize_var_lookup(
|
|||
// Look it up in the env!
|
||||
match env.qualified_lookup(scope, module_name, ident, region) {
|
||||
Ok(symbol) => {
|
||||
output.references.insert_value_lookup(symbol);
|
||||
output
|
||||
.references
|
||||
.insert_value_lookup(symbol, QualifiedReference::Qualified);
|
||||
|
||||
if scope.abilities_store.is_ability_member_name(symbol) {
|
||||
AbilityMember(
|
||||
|
@ -2424,10 +2390,10 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
|||
| ast::Expr::Expect(_, _)
|
||||
| ast::Expr::When(_, _)
|
||||
| ast::Expr::Backpassing(_, _, _)
|
||||
| ast::Expr::IngestedFile(_, _)
|
||||
| ast::Expr::SpaceBefore(_, _)
|
||||
| ast::Expr::Str(StrLiteral::Block(_))
|
||||
| ast::Expr::SpaceAfter(_, _) => false,
|
||||
| ast::Expr::SpaceAfter(_, _)
|
||||
| ast::Expr::EmptyDefsFinal => false,
|
||||
// These can contain subexpressions, so we need to recursively check those
|
||||
ast::Expr::Str(StrLiteral::Line(segments)) => {
|
||||
segments.iter().all(|segment| match segment {
|
||||
|
@ -2453,13 +2419,15 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
|||
.iter()
|
||||
.all(|loc_field| is_valid_interpolation(&loc_field.value)),
|
||||
ast::Expr::MultipleRecordBuilders(loc_expr)
|
||||
| ast::Expr::MalformedSuffixed(loc_expr)
|
||||
| ast::Expr::UnappliedRecordBuilder(loc_expr)
|
||||
| ast::Expr::PrecedenceConflict(PrecedenceConflict { expr: loc_expr, .. })
|
||||
| ast::Expr::UnaryOp(loc_expr, _)
|
||||
| ast::Expr::Closure(_, loc_expr) => is_valid_interpolation(&loc_expr.value),
|
||||
ast::Expr::TupleAccess(sub_expr, _)
|
||||
| ast::Expr::ParensAround(sub_expr)
|
||||
| ast::Expr::RecordAccess(sub_expr, _) => is_valid_interpolation(sub_expr),
|
||||
| ast::Expr::RecordAccess(sub_expr, _)
|
||||
| ast::Expr::TaskAwaitBang(sub_expr) => is_valid_interpolation(sub_expr),
|
||||
ast::Expr::Apply(loc_expr, args, _called_via) => {
|
||||
is_valid_interpolation(&loc_expr.value)
|
||||
&& args
|
||||
|
@ -2512,7 +2480,6 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
|
|||
ast::RecordBuilderField::SpaceBefore(_, _)
|
||||
| ast::RecordBuilderField::SpaceAfter(_, _) => false,
|
||||
}),
|
||||
ast::Expr::Suffixed(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ pub mod pattern;
|
|||
pub mod procedure;
|
||||
pub mod scope;
|
||||
pub mod string;
|
||||
pub mod suffixed;
|
||||
pub mod traverse;
|
||||
|
||||
pub use derive::DERIVED_REGION;
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::abilities::{AbilitiesStore, ImplKey, PendingAbilitiesStore, ResolvedImpl};
|
||||
use crate::annotation::{canonicalize_annotation, AnnotationFor};
|
||||
use crate::def::{canonicalize_defs, Def};
|
||||
use crate::def::{canonicalize_defs, report_unused_imports, Def};
|
||||
use crate::effect_module::HostedGeneratedFunctions;
|
||||
use crate::env::Env;
|
||||
use crate::expr::{
|
||||
ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives,
|
||||
};
|
||||
use crate::pattern::{BindingsFromPattern, Pattern};
|
||||
use crate::procedure::References;
|
||||
use crate::scope::Scope;
|
||||
use bumpalo::Bump;
|
||||
use roc_collections::{MutMap, SendMap, VecMap, VecSet};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, ModuleIds, Symbol};
|
||||
use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, PackageModuleIds, Symbol};
|
||||
use roc_parse::ast::{Defs, TypeAnnotation};
|
||||
use roc_parse::header::HeaderType;
|
||||
use roc_parse::pattern::PatternType;
|
||||
|
@ -127,7 +130,6 @@ pub struct Module {
|
|||
pub exposed_imports: MutMap<Symbol, Region>,
|
||||
pub exposed_symbols: VecSet<Symbol>,
|
||||
pub referenced_values: VecSet<Symbol>,
|
||||
pub referenced_types: VecSet<Symbol>,
|
||||
/// all aliases. `bool` indicates whether it is exposed
|
||||
pub aliases: MutMap<Symbol, (bool, Alias)>,
|
||||
pub rigid_variables: RigidVariables,
|
||||
|
@ -152,7 +154,6 @@ pub struct ModuleOutput {
|
|||
pub exposed_symbols: VecSet<Symbol>,
|
||||
pub problems: Vec<Problem>,
|
||||
pub referenced_values: VecSet<Symbol>,
|
||||
pub referenced_types: VecSet<Symbol>,
|
||||
pub symbols_from_requires: Vec<(Loc<Symbol>, Loc<Type>)>,
|
||||
pub pending_derives: PendingDerives,
|
||||
pub scope: Scope,
|
||||
|
@ -275,21 +276,38 @@ pub fn canonicalize_module_defs<'a>(
|
|||
loc_defs: &'a mut Defs<'a>,
|
||||
header_type: &roc_parse::header::HeaderType,
|
||||
home: ModuleId,
|
||||
module_path: &str,
|
||||
module_path: &'a str,
|
||||
src: &'a str,
|
||||
module_ids: &'a ModuleIds,
|
||||
qualified_module_ids: &'a PackageModuleIds<'a>,
|
||||
exposed_ident_ids: IdentIds,
|
||||
dep_idents: &'a IdentIdsByModule,
|
||||
aliases: MutMap<Symbol, Alias>,
|
||||
imported_abilities_state: PendingAbilitiesStore,
|
||||
exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
||||
initial_scope: MutMap<Ident, (Symbol, Region)>,
|
||||
exposed_symbols: VecSet<Symbol>,
|
||||
symbols_from_requires: &[(Loc<Symbol>, Loc<TypeAnnotation<'a>>)],
|
||||
var_store: &mut VarStore,
|
||||
opt_shorthand: Option<&'a str>,
|
||||
) -> ModuleOutput {
|
||||
let mut can_exposed_imports = MutMap::default();
|
||||
let mut scope = Scope::new(home, exposed_ident_ids, imported_abilities_state);
|
||||
let mut env = Env::new(arena, home, dep_idents, module_ids);
|
||||
let mut scope = Scope::new(
|
||||
home,
|
||||
qualified_module_ids
|
||||
.get_name(home)
|
||||
.expect("home module not found")
|
||||
.as_inner()
|
||||
.to_owned(),
|
||||
exposed_ident_ids,
|
||||
imported_abilities_state,
|
||||
);
|
||||
let mut env = Env::new(
|
||||
arena,
|
||||
home,
|
||||
arena.alloc(Path::new(module_path)),
|
||||
dep_idents,
|
||||
qualified_module_ids,
|
||||
opt_shorthand,
|
||||
);
|
||||
|
||||
for (name, alias) in aliases.into_iter() {
|
||||
scope.add_alias(
|
||||
|
@ -312,30 +330,26 @@ pub fn canonicalize_module_defs<'a>(
|
|||
// visited a BinOp node we'd recursively try to apply this to each of its nested
|
||||
// operators, and then again on *their* nested operators, ultimately applying the
|
||||
// rules multiple times unnecessarily.
|
||||
crate::desugar::desugar_defs_node_values(arena, loc_defs, src, &mut None, module_path);
|
||||
|
||||
crate::desugar::desugar_defs_node_values(arena, loc_defs, src, &mut None, module_path, true);
|
||||
|
||||
let mut rigid_variables = RigidVariables::default();
|
||||
|
||||
// Exposed values are treated like defs that appear before any others, e.g.
|
||||
//
|
||||
// imports [Foo.{ bar, baz }]
|
||||
//
|
||||
// ...is basically the same as if we'd added these extra defs at the start of the module:
|
||||
//
|
||||
// bar = Foo.bar
|
||||
// baz = Foo.baz
|
||||
// Iniital scope values are treated like defs that appear before any others.
|
||||
// They include builtin types that are automatically imported, and for a platform
|
||||
// package, the required values from the app.
|
||||
//
|
||||
// Here we essentially add those "defs" to "the beginning of the module"
|
||||
// by canonicalizing them right before we canonicalize the actual ast::Def nodes.
|
||||
for (ident, (symbol, region)) in exposed_imports {
|
||||
for (ident, (symbol, region)) in initial_scope {
|
||||
let first_char = ident.as_inline_str().as_str().chars().next().unwrap();
|
||||
|
||||
if first_char.is_lowercase() {
|
||||
match scope.import(ident, symbol, region) {
|
||||
match scope.import_symbol(ident, symbol, region) {
|
||||
Ok(()) => {
|
||||
// Add an entry to exposed_imports using the current module's name
|
||||
// as the key; e.g. if this is the Foo module and we have
|
||||
// exposes [Bar.{ baz }] then insert Foo.baz as the key, so when
|
||||
// Bar exposes [baz] then insert Foo.baz as the key, so when
|
||||
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
|
||||
can_exposed_imports.insert(symbol, region);
|
||||
}
|
||||
|
@ -354,7 +368,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
|
||||
// but now we know this symbol by a different identifier, so we still need to add it to
|
||||
// the scope
|
||||
match scope.import(ident, symbol, region) {
|
||||
match scope.import_symbol(ident, symbol, region) {
|
||||
Ok(()) => {
|
||||
// here we do nothing special
|
||||
}
|
||||
|
@ -368,7 +382,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
let (defs, output, symbols_introduced) = canonicalize_defs(
|
||||
let (defs, output, symbols_introduced, imports_introduced) = canonicalize_defs(
|
||||
&mut env,
|
||||
Output::default(),
|
||||
var_store,
|
||||
|
@ -409,18 +423,15 @@ pub fn canonicalize_module_defs<'a>(
|
|||
}
|
||||
|
||||
let mut referenced_values = VecSet::default();
|
||||
let mut referenced_types = VecSet::default();
|
||||
|
||||
// Gather up all the symbols that were referenced across all the defs' lookups.
|
||||
referenced_values.extend(output.references.value_lookups().copied());
|
||||
referenced_types.extend(output.references.type_lookups().copied());
|
||||
|
||||
// Gather up all the symbols that were referenced across all the defs' calls.
|
||||
referenced_values.extend(output.references.calls().copied());
|
||||
|
||||
// Gather up all the symbols that were referenced from other modules.
|
||||
referenced_values.extend(env.qualified_value_lookups.iter().copied());
|
||||
referenced_types.extend(env.qualified_type_lookups.iter().copied());
|
||||
|
||||
// NOTE previously we inserted builtin defs into the list of defs here
|
||||
// this is now done later, in file.rs.
|
||||
|
@ -432,6 +443,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
|
||||
let new_output = Output {
|
||||
aliases: output.aliases,
|
||||
references: output.references,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@ -481,6 +493,8 @@ pub fn canonicalize_module_defs<'a>(
|
|||
})
|
||||
.collect();
|
||||
|
||||
report_unused_imports(imports_introduced, &output.references, &mut env, &mut scope);
|
||||
|
||||
if let GeneratedInfo::Hosted {
|
||||
effect_symbol,
|
||||
generated_functions,
|
||||
|
@ -544,7 +558,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
let annotation = crate::annotation::Annotation {
|
||||
typ: def_annotation.signature,
|
||||
introduced_variables: def_annotation.introduced_variables,
|
||||
references: Default::default(),
|
||||
references: References::new(),
|
||||
aliases: Default::default(),
|
||||
};
|
||||
|
||||
|
@ -602,7 +616,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
let annotation = crate::annotation::Annotation {
|
||||
typ: def_annotation.signature,
|
||||
introduced_variables: def_annotation.introduced_variables,
|
||||
references: Default::default(),
|
||||
references: References::new(),
|
||||
aliases: Default::default(),
|
||||
};
|
||||
|
||||
|
@ -699,14 +713,12 @@ pub fn canonicalize_module_defs<'a>(
|
|||
|
||||
// Incorporate any remaining output.lookups entries into references.
|
||||
referenced_values.extend(output.references.value_lookups().copied());
|
||||
referenced_types.extend(output.references.type_lookups().copied());
|
||||
|
||||
// Incorporate any remaining output.calls entries into references.
|
||||
referenced_values.extend(output.references.calls().copied());
|
||||
|
||||
// Gather up all the symbols that were referenced from other modules.
|
||||
referenced_values.extend(env.qualified_value_lookups.iter().copied());
|
||||
referenced_types.extend(env.qualified_type_lookups.iter().copied());
|
||||
|
||||
let mut fix_closures_no_capture_symbols = VecSet::default();
|
||||
let mut fix_closures_closure_captures = VecMap::default();
|
||||
|
@ -802,7 +814,6 @@ pub fn canonicalize_module_defs<'a>(
|
|||
rigid_variables,
|
||||
declarations,
|
||||
referenced_values,
|
||||
referenced_types,
|
||||
exposed_imports: can_exposed_imports,
|
||||
problems: env.problems,
|
||||
symbols_from_requires,
|
||||
|
|
|
@ -265,7 +265,7 @@ pub fn canonicalize_def_header_pattern<'a>(
|
|||
|
||||
match pattern {
|
||||
// Identifiers that shadow ability members may appear (and may only appear) at the header of a def.
|
||||
Identifier(name) => {
|
||||
Identifier { ident: name } => {
|
||||
match scope.introduce_or_shadow_ability_member(
|
||||
pending_abilities_in_scope,
|
||||
(*name).into(),
|
||||
|
@ -373,7 +373,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
use PatternType::*;
|
||||
|
||||
let can_pattern = match pattern {
|
||||
Identifier(name) => {
|
||||
Identifier { ident: name } => {
|
||||
match canonicalize_pattern_symbol(env, scope, output, region, permit_shadows, name) {
|
||||
Ok(symbol) => Pattern::Identifier(symbol),
|
||||
Err(pattern) => pattern,
|
||||
|
@ -446,7 +446,10 @@ pub fn canonicalize_pattern<'a>(
|
|||
let (type_arguments, lambda_set_variables, specialized_def_type) =
|
||||
freshen_opaque_def(var_store, opaque_def);
|
||||
|
||||
output.references.insert_type_lookup(opaque);
|
||||
output.references.insert_type_lookup(
|
||||
opaque,
|
||||
crate::procedure::QualifiedReference::Unqualified,
|
||||
);
|
||||
|
||||
Pattern::UnwrappedOpaque {
|
||||
whole_var: var_store.fresh(),
|
||||
|
@ -628,7 +631,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
|
||||
for loc_pattern in patterns.iter() {
|
||||
match loc_pattern.value {
|
||||
Identifier(label) => {
|
||||
Identifier { ident: label } => {
|
||||
match scope.introduce(label.into(), region) {
|
||||
Ok(symbol) => {
|
||||
output.references.insert_bound(symbol);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::expr::Expr;
|
||||
use crate::pattern::Pattern;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_module::symbol::{ModuleId, Symbol};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
|
@ -46,6 +46,23 @@ impl ReferencesBitflags {
|
|||
const TYPE_LOOKUP: Self = ReferencesBitflags(2);
|
||||
const CALL: Self = ReferencesBitflags(4);
|
||||
const BOUND: Self = ReferencesBitflags(8);
|
||||
const QUALIFIED: Self = ReferencesBitflags(16);
|
||||
const UNQUALIFIED: Self = ReferencesBitflags(32);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum QualifiedReference {
|
||||
Unqualified,
|
||||
Qualified,
|
||||
}
|
||||
|
||||
impl QualifiedReference {
|
||||
fn flags(&self, flags: ReferencesBitflags) -> ReferencesBitflags {
|
||||
match self {
|
||||
Self::Unqualified => ReferencesBitflags(flags.0 | ReferencesBitflags::UNQUALIFIED.0),
|
||||
Self::Qualified => ReferencesBitflags(flags.0 | ReferencesBitflags::QUALIFIED.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
@ -108,12 +125,12 @@ impl References {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn insert_value_lookup(&mut self, symbol: Symbol) {
|
||||
self.insert(symbol, ReferencesBitflags::VALUE_LOOKUP);
|
||||
pub fn insert_value_lookup(&mut self, symbol: Symbol, qualified: QualifiedReference) {
|
||||
self.insert(symbol, qualified.flags(ReferencesBitflags::VALUE_LOOKUP));
|
||||
}
|
||||
|
||||
pub fn insert_type_lookup(&mut self, symbol: Symbol) {
|
||||
self.insert(symbol, ReferencesBitflags::TYPE_LOOKUP);
|
||||
pub fn insert_type_lookup(&mut self, symbol: Symbol, qualified: QualifiedReference) {
|
||||
self.insert(symbol, qualified.flags(ReferencesBitflags::TYPE_LOOKUP));
|
||||
}
|
||||
|
||||
pub fn insert_bound(&mut self, symbol: Symbol) {
|
||||
|
@ -178,7 +195,24 @@ impl References {
|
|||
false
|
||||
}
|
||||
|
||||
pub fn has_unqualified_type_or_value_lookup(&self, symbol: Symbol) -> bool {
|
||||
let mask = ReferencesBitflags::VALUE_LOOKUP.0 | ReferencesBitflags::TYPE_LOOKUP.0;
|
||||
let it = self.symbols.iter().zip(self.bitflags.iter());
|
||||
|
||||
for (a, b) in it {
|
||||
if *a == symbol && b.0 & mask > 0 && b.0 & ReferencesBitflags::UNQUALIFIED.0 > 0 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn references_type_def(&self, symbol: Symbol) -> bool {
|
||||
self.has_type_lookup(symbol)
|
||||
}
|
||||
|
||||
pub fn has_module_lookup(&self, module_id: ModuleId) -> bool {
|
||||
self.symbols.iter().any(|sym| sym.module_id() == module_id)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use roc_collections::{VecMap, VecSet};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::symbol::{IdentId, IdentIds, ModuleId, Symbol};
|
||||
use roc_module::ident::{Ident, ModuleName};
|
||||
use roc_module::symbol::{IdentId, IdentIds, ModuleId, ScopeModules, Symbol};
|
||||
use roc_problem::can::RuntimeError;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::Variable;
|
||||
|
@ -29,8 +29,11 @@ pub struct Scope {
|
|||
/// The first `exposed_ident_count` identifiers are exposed
|
||||
exposed_ident_count: usize,
|
||||
|
||||
/// Identifiers that are imported (and introduced in the header)
|
||||
imports: Vec<(Ident, Symbol, Region)>,
|
||||
/// Modules that are imported
|
||||
pub modules: ScopeModules,
|
||||
|
||||
/// Identifiers that are imported
|
||||
imported_symbols: Vec<(Ident, Symbol, Region)>,
|
||||
|
||||
/// Shadows of an ability member, for example a local specialization of `eq` for the ability
|
||||
/// member `Eq implements eq : a, a -> Bool where a implements Eq` gets a shadow symbol it can use for its
|
||||
|
@ -50,16 +53,15 @@ pub struct Scope {
|
|||
impl Scope {
|
||||
pub fn new(
|
||||
home: ModuleId,
|
||||
module_name: ModuleName,
|
||||
initial_ident_ids: IdentIds,
|
||||
starting_abilities_store: PendingAbilitiesStore,
|
||||
) -> Scope {
|
||||
let default_imports =
|
||||
// Add all `Apply` types.
|
||||
(Symbol::apply_types_in_scope().into_iter())
|
||||
// Add all tag names we might want to suggest as hints in error messages.
|
||||
.chain(Symbol::symbols_in_scope_for_hints());
|
||||
|
||||
let default_imports = default_imports.map(|(a, (b, c))| (a, b, c)).collect();
|
||||
// Add all `Apply` types.
|
||||
let default_imports = Symbol::apply_types_in_scope()
|
||||
.into_iter()
|
||||
.map(|(a, (b, c))| (a, b, c))
|
||||
.collect();
|
||||
|
||||
Scope {
|
||||
home,
|
||||
|
@ -68,7 +70,8 @@ impl Scope {
|
|||
aliases: VecMap::default(),
|
||||
abilities_store: starting_abilities_store,
|
||||
shadows: VecMap::default(),
|
||||
imports: default_imports,
|
||||
modules: ScopeModules::new(home, module_name),
|
||||
imported_symbols: default_imports,
|
||||
ignored_locals: VecMap::default(),
|
||||
}
|
||||
}
|
||||
|
@ -82,9 +85,9 @@ impl Scope {
|
|||
}
|
||||
|
||||
pub fn add_docs_imports(&mut self) {
|
||||
self.imports
|
||||
self.imported_symbols
|
||||
.push(("Dict".into(), Symbol::DICT_DICT, Region::zero()));
|
||||
self.imports
|
||||
self.imported_symbols
|
||||
.push(("Set".into(), Symbol::SET_SET, Region::zero()));
|
||||
}
|
||||
|
||||
|
@ -113,7 +116,7 @@ impl Scope {
|
|||
|
||||
fn idents_in_scope(&self) -> impl Iterator<Item = Ident> + '_ {
|
||||
let it1 = self.locals.idents_in_scope();
|
||||
let it2 = self.imports.iter().map(|t| t.0.clone());
|
||||
let it2 = self.imported_symbols.iter().map(|t| t.0.clone());
|
||||
|
||||
it2.chain(it1)
|
||||
}
|
||||
|
@ -139,7 +142,7 @@ impl Scope {
|
|||
},
|
||||
None => {
|
||||
// opaque types can only be wrapped/unwrapped in the scope they are defined in (and below)
|
||||
let error = if let Some((_, decl_region)) = self.has_imported(opaque_str) {
|
||||
let error = if let Some((_, decl_region)) = self.has_imported_symbol(opaque_str) {
|
||||
// specific error for when the opaque is imported, which definitely does not work
|
||||
RuntimeError::OpaqueOutsideScope {
|
||||
opaque,
|
||||
|
@ -202,8 +205,8 @@ impl Scope {
|
|||
}
|
||||
}
|
||||
|
||||
fn has_imported(&self, ident: &str) -> Option<(Symbol, Region)> {
|
||||
for (import, shadow, original_region) in self.imports.iter() {
|
||||
fn has_imported_symbol(&self, ident: &str) -> Option<(Symbol, Region)> {
|
||||
for (import, shadow, original_region) in self.imported_symbols.iter() {
|
||||
if ident == import.as_str() {
|
||||
return Some((*shadow, *original_region));
|
||||
}
|
||||
|
@ -215,7 +218,7 @@ impl Scope {
|
|||
/// Is an identifier in scope, either in the locals or imports
|
||||
fn scope_contains_ident(&self, ident: &str) -> ContainsIdent {
|
||||
// exposed imports are likely to be small
|
||||
match self.has_imported(ident) {
|
||||
match self.has_imported_symbol(ident) {
|
||||
Some((symbol, region)) => ContainsIdent::InScope(symbol, region),
|
||||
None => self.locals.contains_ident(ident),
|
||||
}
|
||||
|
@ -379,19 +382,19 @@ impl Scope {
|
|||
///
|
||||
/// Returns Err if this would shadow an existing ident, including the
|
||||
/// Symbol and Region of the ident we already had in scope under that name.
|
||||
pub fn import(
|
||||
pub fn import_symbol(
|
||||
&mut self,
|
||||
ident: Ident,
|
||||
symbol: Symbol,
|
||||
region: Region,
|
||||
) -> Result<(), (Symbol, Region)> {
|
||||
if let Some((s, r)) = self.has_imported(ident.as_str()) {
|
||||
return Err((s, r));
|
||||
match self.scope_contains_ident(ident.as_str()) {
|
||||
ContainsIdent::InScope(symbol, region) => Err((symbol, region)),
|
||||
ContainsIdent::NotPresent | ContainsIdent::NotInScope(_) => {
|
||||
self.imported_symbols.push((ident, symbol, region));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
self.imports.push((ident, symbol, region));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_alias(
|
||||
|
@ -423,17 +426,22 @@ impl Scope {
|
|||
//
|
||||
// - abilities_store: ability definitions not allowed in inner scopes
|
||||
// - locals: everything introduced in the inner scope is marked as not in scope in the rollback
|
||||
// - imports: everything that was imported in the inner scope is removed in the rollback
|
||||
// - aliases: stored in a VecMap, we just discard anything added in an inner scope
|
||||
// - exposed_ident_count: unchanged
|
||||
// - home: unchanged
|
||||
let aliases_count = self.aliases.len();
|
||||
let ignored_locals_count = self.ignored_locals.len();
|
||||
let locals_snapshot = self.locals.in_scope.len();
|
||||
let imported_symbols_snapshot = self.imported_symbols.len();
|
||||
let imported_modules_snapshot = self.modules.len();
|
||||
|
||||
let result = f(self);
|
||||
|
||||
self.aliases.truncate(aliases_count);
|
||||
self.ignored_locals.truncate(ignored_locals_count);
|
||||
self.imported_symbols.truncate(imported_symbols_snapshot);
|
||||
self.modules.truncate(imported_modules_snapshot);
|
||||
|
||||
// anything added in the inner scope is no longer in scope now
|
||||
for i in locals_snapshot..self.locals.in_scope.len() {
|
||||
|
@ -651,6 +659,7 @@ mod test {
|
|||
let _register_module_debug_names = ModuleIds::default();
|
||||
let mut scope = Scope::new(
|
||||
ModuleId::ATTR,
|
||||
"#Attr".into(),
|
||||
IdentIds::default(),
|
||||
PendingAbilitiesStore::default(),
|
||||
);
|
||||
|
@ -670,6 +679,7 @@ mod test {
|
|||
let _register_module_debug_names = ModuleIds::default();
|
||||
let mut scope = Scope::new(
|
||||
ModuleId::ATTR,
|
||||
"#Attr".into(),
|
||||
IdentIds::default(),
|
||||
PendingAbilitiesStore::default(),
|
||||
);
|
||||
|
@ -699,6 +709,7 @@ mod test {
|
|||
let _register_module_debug_names = ModuleIds::default();
|
||||
let mut scope = Scope::new(
|
||||
ModuleId::ATTR,
|
||||
"#Attr".into(),
|
||||
IdentIds::default(),
|
||||
PendingAbilitiesStore::default(),
|
||||
);
|
||||
|
@ -720,6 +731,7 @@ mod test {
|
|||
let _register_module_debug_names = ModuleIds::default();
|
||||
let scope = Scope::new(
|
||||
ModuleId::ATTR,
|
||||
"#Attr".into(),
|
||||
IdentIds::default(),
|
||||
PendingAbilitiesStore::default(),
|
||||
);
|
||||
|
@ -728,13 +740,7 @@ mod test {
|
|||
|
||||
assert_eq!(
|
||||
&idents,
|
||||
&[
|
||||
Ident::from("Str"),
|
||||
Ident::from("List"),
|
||||
Ident::from("Box"),
|
||||
Ident::from("Ok"),
|
||||
Ident::from("Err"),
|
||||
]
|
||||
&[Ident::from("Str"), Ident::from("List"), Ident::from("Box"),]
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -743,6 +749,7 @@ mod test {
|
|||
let _register_module_debug_names = ModuleIds::default();
|
||||
let mut scope = Scope::new(
|
||||
ModuleId::ATTR,
|
||||
"#Attr".into(),
|
||||
IdentIds::default(),
|
||||
PendingAbilitiesStore::default(),
|
||||
);
|
||||
|
@ -751,13 +758,7 @@ mod test {
|
|||
|
||||
assert_eq!(
|
||||
&idents,
|
||||
&[
|
||||
Ident::from("Str"),
|
||||
Ident::from("List"),
|
||||
Ident::from("Box"),
|
||||
Ident::from("Ok"),
|
||||
Ident::from("Err"),
|
||||
]
|
||||
&[Ident::from("Str"), Ident::from("List"), Ident::from("Box"),]
|
||||
);
|
||||
|
||||
let builtin_count = idents.len();
|
||||
|
@ -810,6 +811,7 @@ mod test {
|
|||
let _register_module_debug_names = ModuleIds::default();
|
||||
let mut scope = Scope::new(
|
||||
ModuleId::ATTR,
|
||||
"#Attr".into(),
|
||||
IdentIds::default(),
|
||||
PendingAbilitiesStore::default(),
|
||||
);
|
||||
|
@ -820,7 +822,7 @@ mod test {
|
|||
|
||||
assert!(scope.lookup(&ident, region).is_err());
|
||||
|
||||
assert!(scope.import(ident.clone(), symbol, region).is_ok());
|
||||
assert!(scope.import_symbol(ident.clone(), symbol, region).is_ok());
|
||||
|
||||
assert!(scope.lookup(&ident, region).is_ok());
|
||||
|
||||
|
@ -832,6 +834,7 @@ mod test {
|
|||
let _register_module_debug_names = ModuleIds::default();
|
||||
let mut scope = Scope::new(
|
||||
ModuleId::ATTR,
|
||||
"#Attr".into(),
|
||||
IdentIds::default(),
|
||||
PendingAbilitiesStore::default(),
|
||||
);
|
||||
|
@ -842,7 +845,7 @@ mod test {
|
|||
let region1 = Region::from_pos(Position { offset: 10 });
|
||||
let region2 = Region::from_pos(Position { offset: 20 });
|
||||
|
||||
scope.import(ident.clone(), symbol, region1).unwrap();
|
||||
scope.import_symbol(ident.clone(), symbol, region1).unwrap();
|
||||
|
||||
let (original, _ident, shadow_symbol) =
|
||||
scope.introduce(ident.clone(), region2).unwrap_err();
|
||||
|
|
879
crates/compiler/can/src/suffixed.rs
Normal file
879
crates/compiler/can/src/suffixed.rs
Normal file
|
@ -0,0 +1,879 @@
|
|||
#![allow(clippy::manual_map)]
|
||||
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_parse::ast::Expr::{self, *};
|
||||
use roc_parse::ast::{is_expr_suffixed, Pattern, ValueDef, WhenBranch};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use std::cell::Cell;
|
||||
|
||||
thread_local! {
|
||||
// we use a thread_local here so that tests consistently give the same pattern
|
||||
static SUFFIXED_ANSWER_COUNTER: Cell<usize> = Cell::new(0);
|
||||
}
|
||||
|
||||
/// Provide an intermediate answer expression and pattern when unwrapping a
|
||||
/// (sub) expression
|
||||
///
|
||||
/// e.g. `x = foo (bar!)` unwraps to `x = Task.await (bar) \#!a0 -> foo #!a0`
|
||||
fn next_suffixed_answer_pattern(arena: &Bump) -> (Expr, Pattern) {
|
||||
// Use the thread-local counter
|
||||
SUFFIXED_ANSWER_COUNTER.with(|counter| {
|
||||
let count = counter.get();
|
||||
counter.set(count + 1);
|
||||
|
||||
let answer_ident = arena.alloc(format!("#!a{}", count));
|
||||
|
||||
(
|
||||
Expr::Var {
|
||||
module_name: "",
|
||||
ident: answer_ident,
|
||||
},
|
||||
Pattern::Identifier {
|
||||
ident: answer_ident.as_str(),
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EUnwrapped<'a> {
|
||||
UnwrappedDefExpr(&'a Loc<Expr<'a>>),
|
||||
|
||||
UnwrappedSubExpr {
|
||||
/// the unwrapped expression argument for Task.await
|
||||
sub_arg: &'a Loc<Expr<'a>>,
|
||||
|
||||
/// the pattern for the closure
|
||||
sub_pat: &'a Loc<Pattern<'a>>,
|
||||
|
||||
/// the expression to replace the unwrapped
|
||||
sub_new: &'a Loc<Expr<'a>>,
|
||||
},
|
||||
|
||||
Malformed,
|
||||
}
|
||||
|
||||
fn init_unwrapped_err<'a>(
|
||||
arena: &'a Bump,
|
||||
unwrapped_expr: &'a Loc<Expr<'a>>,
|
||||
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
|
||||
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
|
||||
match maybe_def_pat {
|
||||
Some(..) => {
|
||||
// we have a def pattern, so no need to generate a new pattern
|
||||
// as this should only be created in the first call from a def
|
||||
Err(EUnwrapped::UnwrappedDefExpr(unwrapped_expr))
|
||||
}
|
||||
None => {
|
||||
let (answer_var, answer_pat) = next_suffixed_answer_pattern(arena);
|
||||
let sub_new = arena.alloc(Loc::at(unwrapped_expr.region, answer_var));
|
||||
let sub_pat = arena.alloc(Loc::at(unwrapped_expr.region, answer_pat));
|
||||
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg: unwrapped_expr,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Descend through the AST and unwrap each suffixed expression
|
||||
/// when an expression is unwrapped, we apply a `Task.await` and
|
||||
/// then descend through the AST again until there are no more suffixed
|
||||
/// expressions, or we hit an error
|
||||
pub fn unwrap_suffixed_expression<'a>(
|
||||
arena: &'a Bump,
|
||||
loc_expr: &'a Loc<Expr<'a>>,
|
||||
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
|
||||
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
|
||||
let unwrapped_expression = {
|
||||
match loc_expr.value {
|
||||
Expr::TaskAwaitBang(sub_expr) => {
|
||||
let unwrapped_sub_expr = arena.alloc(Loc::at(loc_expr.region, *sub_expr));
|
||||
|
||||
init_unwrapped_err(arena, unwrapped_sub_expr, maybe_def_pat)
|
||||
}
|
||||
|
||||
Expr::Defs(..) => unwrap_suffixed_expression_defs_help(arena, loc_expr, maybe_def_pat),
|
||||
|
||||
Expr::Apply(..) => {
|
||||
unwrap_suffixed_expression_apply_help(arena, loc_expr, maybe_def_pat)
|
||||
}
|
||||
|
||||
Expr::When(..) => unwrap_suffixed_expression_when_help(arena, loc_expr, maybe_def_pat),
|
||||
|
||||
Expr::If(..) => {
|
||||
unwrap_suffixed_expression_if_then_else_help(arena, loc_expr, maybe_def_pat)
|
||||
}
|
||||
|
||||
Expr::Closure(..) => {
|
||||
unwrap_suffixed_expression_closure_help(arena, loc_expr, maybe_def_pat)
|
||||
}
|
||||
|
||||
Expr::ParensAround(..) => {
|
||||
unwrap_suffixed_expression_parens_help(arena, loc_expr, maybe_def_pat)
|
||||
}
|
||||
|
||||
Expr::SpaceBefore(..) | Expr::SpaceAfter(..) => {
|
||||
internal_error!(
|
||||
"SpaceBefore and SpaceAfter should have been removed in desugar_expr"
|
||||
);
|
||||
}
|
||||
|
||||
Expr::BinOps(..) => {
|
||||
internal_error!("BinOps should have been desugared in desugar_expr");
|
||||
}
|
||||
|
||||
Expr::LowLevelDbg(dbg_src, arg, rest) => {
|
||||
if is_expr_suffixed(&arg.value) {
|
||||
// we cannot unwrap a suffixed expression within dbg
|
||||
// e.g. dbg (foo! "bar")
|
||||
return Err(EUnwrapped::Malformed);
|
||||
}
|
||||
|
||||
match unwrap_suffixed_expression(arena, rest, maybe_def_pat) {
|
||||
Ok(unwrapped_expr) => {
|
||||
let new_dbg = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
LowLevelDbg(dbg_src, arg, unwrapped_expr),
|
||||
));
|
||||
return Ok(new_dbg);
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(unwrapped_expr)) => {
|
||||
let new_dbg = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
LowLevelDbg(dbg_src, arg, unwrapped_expr),
|
||||
));
|
||||
Err(EUnwrapped::UnwrappedDefExpr(new_dbg))
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg: unwrapped_expr,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
}) => {
|
||||
let new_dbg = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
LowLevelDbg(dbg_src, arg, unwrapped_expr),
|
||||
));
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg: new_dbg,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
})
|
||||
}
|
||||
Err(EUnwrapped::Malformed) => Err(EUnwrapped::Malformed),
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Expect(condition, continuation) => {
|
||||
if is_expr_suffixed(&condition.value) {
|
||||
// we cannot unwrap a suffixed expression within expect
|
||||
// e.g. expect (foo! "bar")
|
||||
return Err(EUnwrapped::Malformed);
|
||||
}
|
||||
|
||||
match unwrap_suffixed_expression(arena, continuation, maybe_def_pat) {
|
||||
Ok(unwrapped_expr) => {
|
||||
let new_expect = arena
|
||||
.alloc(Loc::at(loc_expr.region, Expect(condition, unwrapped_expr)));
|
||||
return Ok(new_expect);
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(unwrapped_expr)) => {
|
||||
let new_expect = arena
|
||||
.alloc(Loc::at(loc_expr.region, Expect(condition, unwrapped_expr)));
|
||||
Err(EUnwrapped::UnwrappedDefExpr(new_expect))
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg: unwrapped_expr,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
}) => {
|
||||
let new_expect = arena
|
||||
.alloc(Loc::at(loc_expr.region, Expect(condition, unwrapped_expr)));
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg: new_expect,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
})
|
||||
}
|
||||
Err(EUnwrapped::Malformed) => Err(EUnwrapped::Malformed),
|
||||
}
|
||||
}
|
||||
|
||||
// we only need to unwrap some expressions, leave the rest as is
|
||||
_ => Ok(loc_expr),
|
||||
}
|
||||
};
|
||||
|
||||
// KEEP THIS HERE FOR DEBUGGING
|
||||
// USEFUL TO SEE THE UNWRAPPING
|
||||
// OF AST NODES AS THEY DESCEND
|
||||
// if is_expr_suffixed(&loc_expr.value) {
|
||||
// dbg!(&maybe_def_pat, &loc_expr, &unwrapped_expression);
|
||||
// }
|
||||
|
||||
unwrapped_expression
|
||||
}
|
||||
|
||||
pub fn unwrap_suffixed_expression_parens_help<'a>(
|
||||
arena: &'a Bump,
|
||||
loc_expr: &'a Loc<Expr<'a>>,
|
||||
_maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
|
||||
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
|
||||
match loc_expr.value {
|
||||
Expr::ParensAround(sub_loc_expr) => {
|
||||
// note we use `None` here as we always want to generate a new pattern from child expressions
|
||||
match unwrap_suffixed_expression(arena, arena.alloc(Loc::at_zero(*sub_loc_expr)), None)
|
||||
{
|
||||
Ok(new_expr) => {
|
||||
let new_parens = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
ParensAround(arena.alloc(new_expr.value)),
|
||||
));
|
||||
Ok(new_parens)
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
|
||||
internal_error!("unreachable, child expressions from ParensAround should generate UnwrappedSubExpr instead");
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
}) => {
|
||||
let new_parens = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
ParensAround(arena.alloc(sub_new.value)),
|
||||
));
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg,
|
||||
sub_pat,
|
||||
sub_new: new_parens,
|
||||
})
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
_ => internal_error!("unreachable, expected a ParensAround node to be passed into unwrap_suffixed_expression_parens_help"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_suffixed_expression_closure_help<'a>(
|
||||
arena: &'a Bump,
|
||||
loc_expr: &'a Loc<Expr<'a>>,
|
||||
_maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
|
||||
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
|
||||
match loc_expr.value {
|
||||
Expr::Closure(closure_args, closure_loc_ret) => {
|
||||
// note we use `None` here as we don't want to pass a DefExpr up and
|
||||
// unwrap the definition pattern for the closure
|
||||
match unwrap_suffixed_expression(arena, closure_loc_ret, None) {
|
||||
Ok(unwrapped_expr) => {
|
||||
let new_closure = arena.alloc(Loc::at(loc_expr.region, Expr::Closure(closure_args, unwrapped_expr)));
|
||||
Ok(new_closure)
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
|
||||
let new_closure_loc_ret = apply_task_await(arena, loc_expr.region, sub_arg, sub_pat, sub_new);
|
||||
let new_closure = arena.alloc(Loc::at(loc_expr.region, Expr::Closure(closure_args, new_closure_loc_ret)));
|
||||
Ok(new_closure)
|
||||
}
|
||||
Err(err) => {
|
||||
debug_assert!(false,"the closure Defs was malformd, got {:#?}", err);
|
||||
Err(EUnwrapped::Malformed)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => internal_error!("unreachable, expected a Closure node to be passed into unwrap_suffixed_expression_closure_help"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_suffixed_expression_apply_help<'a>(
|
||||
arena: &'a Bump,
|
||||
loc_expr: &'a Loc<Expr<'a>>,
|
||||
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
|
||||
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
|
||||
match loc_expr.value {
|
||||
Expr::Apply(function, apply_args, called_via) => {
|
||||
|
||||
// Any suffixed arguments will be innermost, therefore we unwrap those first
|
||||
let local_args = arena.alloc_slice_copy(apply_args);
|
||||
for arg in local_args.iter_mut() {
|
||||
// Args are always expressions, don't pass `maybe_def_pat`
|
||||
match unwrap_suffixed_expression(arena, arg, None) {
|
||||
Ok(new_arg) => {
|
||||
*arg = new_arg;
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
|
||||
internal_error!("unreachable, unwrapped arg cannot be def expression as `None` was passed as pattern");
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new: new_arg }) => {
|
||||
|
||||
*arg = new_arg;
|
||||
|
||||
let new_apply = arena.alloc(Loc::at(loc_expr.region, Apply(function, local_args, called_via)));
|
||||
return Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new: new_apply});
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
// special case for when our Apply function is a suffixed Var (but not multiple suffixed)
|
||||
if let Expr::TaskAwaitBang(sub_expr) = function.value {
|
||||
let unwrapped_function = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
*sub_expr,
|
||||
));
|
||||
|
||||
let new_apply = arena.alloc(Loc::at(loc_expr.region, Expr::Apply(unwrapped_function, local_args, called_via)));
|
||||
|
||||
return init_unwrapped_err(arena, new_apply, maybe_def_pat);
|
||||
}
|
||||
|
||||
// function is another expression
|
||||
match unwrap_suffixed_expression(arena, function, maybe_def_pat) {
|
||||
Ok(new_function) => {
|
||||
let new_apply = arena.alloc(Loc::at(loc_expr.region, Expr::Apply(new_function, local_args, called_via)));
|
||||
Ok(new_apply)
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(unwrapped_function)) => {
|
||||
let new_apply = arena.alloc(Loc::at(loc_expr.region, Expr::Apply(unwrapped_function, local_args, called_via)));
|
||||
Err(EUnwrapped::UnwrappedDefExpr(new_apply))
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg: unwrapped_function, sub_pat, sub_new }) => {
|
||||
|
||||
let new_apply = arena.alloc(Loc::at(loc_expr.region, Expr::Apply(sub_new, local_args, called_via)));
|
||||
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg: unwrapped_function, sub_pat, sub_new:new_apply})
|
||||
}
|
||||
Err(err) => Err(err)
|
||||
}
|
||||
}
|
||||
_ => internal_error!("unreachable, expected an Apply node to be passed into unwrap_suffixed_expression_apply_help"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwrap if-then-else statements
|
||||
pub fn unwrap_suffixed_expression_if_then_else_help<'a>(
|
||||
arena: &'a Bump,
|
||||
loc_expr: &'a Loc<Expr<'a>>,
|
||||
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
|
||||
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
|
||||
match loc_expr.value {
|
||||
Expr::If(if_thens, final_else_branch) => {
|
||||
for (index, if_then) in if_thens.iter().enumerate() {
|
||||
let (current_if_then_statement, current_if_then_expression) = if_then;
|
||||
|
||||
// unwrap suffixed (innermost) expressions e.g. `if true then doThing! then ...`
|
||||
if is_expr_suffixed(¤t_if_then_expression.value) {
|
||||
// split if_thens around the current index
|
||||
let (before, after) = roc_parse::ast::split_around(if_thens, index);
|
||||
|
||||
match unwrap_suffixed_expression(arena, current_if_then_expression, None) {
|
||||
Ok(unwrapped_expression) => {
|
||||
let mut new_if_thens = Vec::new_in(arena);
|
||||
|
||||
new_if_thens.extend(before);
|
||||
new_if_thens.push((*current_if_then_statement, *unwrapped_expression));
|
||||
new_if_thens.extend(after);
|
||||
|
||||
let new_if = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Expr::If(
|
||||
arena.alloc_slice_copy(new_if_thens.as_slice()),
|
||||
final_else_branch,
|
||||
),
|
||||
));
|
||||
|
||||
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
|
||||
internal_error!("unexpected, unwrapped if-then-else Def expr should have intermediate answer as `None` was passed as pattern");
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
}) => {
|
||||
let unwrapped_expression =
|
||||
apply_task_await(arena, sub_arg.region, sub_arg, sub_pat, sub_new);
|
||||
|
||||
let mut new_if_thens = Vec::new_in(arena);
|
||||
|
||||
new_if_thens.extend(before);
|
||||
new_if_thens.push((*current_if_then_statement, *unwrapped_expression));
|
||||
new_if_thens.extend(after);
|
||||
|
||||
let new_if = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Expr::If(
|
||||
arena.alloc_slice_copy(new_if_thens.as_slice()),
|
||||
final_else_branch,
|
||||
),
|
||||
));
|
||||
|
||||
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);
|
||||
}
|
||||
Err(EUnwrapped::Malformed) => return Err(EUnwrapped::Malformed),
|
||||
}
|
||||
}
|
||||
|
||||
// unwrap suffixed statements e.g. `if isThing! then ...`
|
||||
// note we want to split and nest if-then's so we only run Task's
|
||||
// that are required
|
||||
if is_expr_suffixed(¤t_if_then_statement.value) {
|
||||
// split if_thens around the current index
|
||||
let (before, after) = roc_parse::ast::split_around(if_thens, index);
|
||||
|
||||
match unwrap_suffixed_expression(arena, current_if_then_statement, None) {
|
||||
Ok(unwrapped_statement) => {
|
||||
let mut new_if_thens = Vec::new_in(arena);
|
||||
|
||||
new_if_thens.push((*unwrapped_statement, *current_if_then_expression));
|
||||
new_if_thens.extend(after);
|
||||
|
||||
let new_if = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Expr::If(
|
||||
arena.alloc_slice_copy(new_if_thens.as_slice()),
|
||||
final_else_branch,
|
||||
),
|
||||
));
|
||||
|
||||
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
|
||||
internal_error!("unexpected, unwrapped if-then-else Def expr should have intermediate answer as `None` was passed as pattern");
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
}) => {
|
||||
if before.is_empty() {
|
||||
let mut new_if_thens = Vec::new_in(arena);
|
||||
|
||||
new_if_thens.extend(before);
|
||||
new_if_thens.push((*sub_new, *current_if_then_expression));
|
||||
new_if_thens.extend(after);
|
||||
|
||||
let new_if = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Expr::If(
|
||||
arena.alloc_slice_copy(new_if_thens.as_slice()),
|
||||
final_else_branch,
|
||||
),
|
||||
));
|
||||
|
||||
let unwrapped_if_then = apply_task_await(
|
||||
arena,
|
||||
sub_arg.region,
|
||||
sub_arg,
|
||||
sub_pat,
|
||||
new_if,
|
||||
);
|
||||
|
||||
return unwrap_suffixed_expression(
|
||||
arena,
|
||||
unwrapped_if_then,
|
||||
maybe_def_pat,
|
||||
);
|
||||
} else {
|
||||
let mut after_if_thens = Vec::new_in(arena);
|
||||
|
||||
after_if_thens.push((*sub_new, *current_if_then_expression));
|
||||
after_if_thens.extend(after);
|
||||
|
||||
let after_if = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Expr::If(
|
||||
arena.alloc_slice_copy(after_if_thens.as_slice()),
|
||||
final_else_branch,
|
||||
),
|
||||
));
|
||||
|
||||
let after_if_then = apply_task_await(
|
||||
arena,
|
||||
sub_arg.region,
|
||||
sub_arg,
|
||||
sub_pat,
|
||||
after_if,
|
||||
);
|
||||
|
||||
let before_if_then = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Expr::If(before, after_if_then),
|
||||
));
|
||||
|
||||
return unwrap_suffixed_expression(
|
||||
arena,
|
||||
before_if_then,
|
||||
maybe_def_pat,
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(EUnwrapped::Malformed) => return Err(EUnwrapped::Malformed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check the final_else_branch
|
||||
match unwrap_suffixed_expression(arena, final_else_branch, None) {
|
||||
Ok(unwrapped_final_else) => {
|
||||
return Ok(arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Expr::If(if_thens, unwrapped_final_else),
|
||||
)));
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
|
||||
internal_error!("unexpected, unwrapped if-then-else Def expr should have intermediate answer as `None` was passed as pattern");
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedSubExpr {
|
||||
sub_arg,
|
||||
sub_pat,
|
||||
sub_new,
|
||||
}) => {
|
||||
let unwrapped_final_else =
|
||||
apply_task_await(arena, sub_arg.region, sub_arg, sub_pat, sub_new);
|
||||
|
||||
let new_if = arena.alloc(Loc::at(
|
||||
loc_expr.region,
|
||||
Expr::If(if_thens, unwrapped_final_else),
|
||||
));
|
||||
|
||||
return unwrap_suffixed_expression(arena, new_if, maybe_def_pat);
|
||||
}
|
||||
Err(EUnwrapped::Malformed) => Err(EUnwrapped::Malformed),
|
||||
}
|
||||
}
|
||||
_ => internal_error!("unreachable, expected an If expression to desugar"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_suffixed_expression_when_help<'a>(
|
||||
arena: &'a Bump,
|
||||
loc_expr: &'a Loc<Expr<'a>>,
|
||||
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
|
||||
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
|
||||
match loc_expr.value {
|
||||
Expr::When(condition, branches) => {
|
||||
|
||||
// first unwrap any when branches values
|
||||
// e.g.
|
||||
// when foo is
|
||||
// [] -> line! "bar"
|
||||
// _ -> line! "baz"
|
||||
for (branch_index, WhenBranch{value: branch_loc_expr,patterns, guard}) in branches.iter().enumerate() {
|
||||
|
||||
// if the branch isn't suffixed we can leave it alone
|
||||
if is_expr_suffixed(&branch_loc_expr.value) {
|
||||
let unwrapped_branch_value = match unwrap_suffixed_expression(arena, branch_loc_expr, None) {
|
||||
Ok(unwrapped_branch_value) => unwrapped_branch_value,
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => apply_task_await(arena, branch_loc_expr.region, sub_arg, sub_pat, sub_new),
|
||||
Err(..) => return Err(EUnwrapped::Malformed),
|
||||
};
|
||||
|
||||
// TODO: unwrap guard
|
||||
|
||||
let new_branch = WhenBranch{value: *unwrapped_branch_value, patterns, guard: *guard};
|
||||
let mut new_branches = Vec::new_in(arena);
|
||||
let (before, rest) = branches.split_at(branch_index);
|
||||
let after = &rest[1..];
|
||||
|
||||
new_branches.extend_from_slice(before);
|
||||
new_branches.push(arena.alloc(new_branch));
|
||||
new_branches.extend_from_slice(after);
|
||||
|
||||
let new_when = arena.alloc(Loc::at(loc_expr.region, Expr::When(condition, arena.alloc_slice_copy(new_branches.as_slice()))));
|
||||
|
||||
return unwrap_suffixed_expression(arena, new_when, maybe_def_pat);
|
||||
}
|
||||
}
|
||||
|
||||
// then unwrap the when condition
|
||||
match unwrap_suffixed_expression(arena, condition, None) {
|
||||
Ok(unwrapped_condition) => {
|
||||
let new_when = arena.alloc(Loc::at(loc_expr.region, Expr::When(unwrapped_condition, branches)));
|
||||
Ok(new_when)
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
|
||||
let new_when = arena.alloc(Loc::at(loc_expr.region, Expr::When(sub_new, branches)));
|
||||
let applied_task_await = apply_task_await(arena,loc_expr.region,sub_arg,sub_pat,new_when);
|
||||
Ok(applied_task_await)
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(..))
|
||||
| Err(EUnwrapped::Malformed) => Err(EUnwrapped::Malformed)
|
||||
}
|
||||
|
||||
}
|
||||
_ => internal_error!("unreachable, expected a When node to be passed into unwrap_suffixed_expression_defs_help"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_suffixed_expression_defs_help<'a>(
|
||||
arena: &'a Bump,
|
||||
loc_expr: &'a Loc<Expr<'a>>,
|
||||
maybe_def_pat: Option<&'a Loc<Pattern<'a>>>,
|
||||
) -> Result<&'a Loc<Expr<'a>>, EUnwrapped<'a>> {
|
||||
match loc_expr.value {
|
||||
Expr::Defs(defs, loc_ret) => {
|
||||
|
||||
let mut local_defs = defs.clone();
|
||||
let tags = local_defs.tags.clone();
|
||||
|
||||
// try an unwrap each def, if none can be unwrapped, then try to unwrap the loc_ret
|
||||
for (tag_index, type_or_value_def_index) in tags.iter().enumerate() {
|
||||
use ValueDef::*;
|
||||
|
||||
let mut current_value_def = match type_or_value_def_index.split() {
|
||||
Ok(..) => {
|
||||
// ignore type definitions
|
||||
continue;
|
||||
},
|
||||
Err(value_index) => *local_defs.value_defs.get(value_index.index()).unwrap(),
|
||||
};
|
||||
|
||||
let maybe_suffixed_value_def = match current_value_def {
|
||||
Annotation(..) | Dbg{..} | Expect{..} | ExpectFx{..} | Stmt(..) | ModuleImport{..} | IngestedFileImport(_) => None,
|
||||
AnnotatedBody { body_pattern, body_expr, .. } => Some((body_pattern, body_expr)),
|
||||
Body (def_pattern, def_expr, .. ) => Some((def_pattern, def_expr)),
|
||||
};
|
||||
|
||||
match maybe_suffixed_value_def {
|
||||
None => {
|
||||
// We can't unwrap this def type, continue
|
||||
},
|
||||
Some((def_pattern, def_expr)) => {
|
||||
match unwrap_suffixed_expression(arena, def_expr, Some(def_pattern)) {
|
||||
Ok(unwrapped_def) => {
|
||||
current_value_def.replace_expr(unwrapped_def);
|
||||
local_defs.replace_with_value_def(tag_index, current_value_def, def_expr.region);
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(unwrapped_expr)) => {
|
||||
let split_defs = local_defs.split_defs_around(tag_index);
|
||||
let before_empty = split_defs.before.is_empty();
|
||||
let after_empty = split_defs.after.is_empty();
|
||||
if before_empty && after_empty {
|
||||
// NIL before, NIL after -> SINGLE DEF
|
||||
let next_expr = match unwrap_suffixed_expression(arena,loc_ret,maybe_def_pat) {
|
||||
Ok(next_expr) => next_expr,
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
|
||||
// We need to apply Task.ok here as the defs final expression was unwrapped
|
||||
apply_task_await(arena,def_expr.region,sub_arg,sub_pat,sub_new)
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(..)) | Err(EUnwrapped::Malformed) => {
|
||||
// TODO handle case when we have maybe_def_pat so can return an unwrapped up
|
||||
return Err(EUnwrapped::Malformed);
|
||||
},
|
||||
};
|
||||
return unwrap_suffixed_expression(arena, apply_task_await(arena,def_expr.region,unwrapped_expr,def_pattern,next_expr), maybe_def_pat);
|
||||
} else if before_empty {
|
||||
// NIL before, SOME after -> FIRST DEF
|
||||
let new_defs = arena.alloc(Loc::at(def_expr.region, Defs(arena.alloc(split_defs.after), loc_ret)));
|
||||
|
||||
let next_expr = match unwrap_suffixed_expression(arena,new_defs,maybe_def_pat){
|
||||
Ok(next_expr) => next_expr,
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
|
||||
apply_task_await(arena, def_expr.region, sub_arg, sub_pat, sub_new)
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(..)) | Err(EUnwrapped::Malformed) => {
|
||||
// TODO handle case when we have maybe_def_pat so can return an unwrapped up
|
||||
return Err(EUnwrapped::Malformed);
|
||||
},
|
||||
};
|
||||
|
||||
return unwrap_suffixed_expression(arena, apply_task_await(arena,def_expr.region,unwrapped_expr,def_pattern,next_expr), maybe_def_pat);
|
||||
} else if after_empty {
|
||||
// SOME before, NIL after -> LAST DEF
|
||||
match unwrap_suffixed_expression(arena,loc_ret,maybe_def_pat){
|
||||
Ok(new_loc_ret) => {
|
||||
let applied_task_await = apply_task_await(arena, loc_expr.region, unwrapped_expr, def_pattern, new_loc_ret);
|
||||
let new_defs = arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(split_defs.before), applied_task_await)));
|
||||
return unwrap_suffixed_expression(arena, new_defs, maybe_def_pat);
|
||||
},
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
|
||||
let new_loc_ret = apply_task_await(arena,def_expr.region,sub_arg,sub_pat,sub_new);
|
||||
let applied_task_await = apply_task_await(arena, loc_expr.region, unwrapped_expr, def_pattern, new_loc_ret);
|
||||
let new_defs = arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(split_defs.before), applied_task_await)));
|
||||
return unwrap_suffixed_expression(arena, new_defs, maybe_def_pat);
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
|
||||
// TODO confirm this is correct with test case
|
||||
return Err(EUnwrapped::Malformed);
|
||||
}
|
||||
Err(EUnwrapped::Malformed) => {
|
||||
return Err(EUnwrapped::Malformed);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// SOME before, SOME after -> MIDDLE DEF
|
||||
let after_defs = arena.alloc(Loc::at(def_expr.region, Defs(arena.alloc(split_defs.after), loc_ret)));
|
||||
|
||||
match unwrap_suffixed_expression(arena,after_defs,maybe_def_pat){
|
||||
Ok(new_loc_ret) => {
|
||||
let applied_await = apply_task_await(arena, loc_expr.region, unwrapped_expr, def_pattern, new_loc_ret);
|
||||
let new_defs = arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(split_defs.before), applied_await)));
|
||||
return unwrap_suffixed_expression(arena, new_defs, maybe_def_pat);
|
||||
},
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
|
||||
let new_loc_ret = apply_task_await(arena, def_expr.region, sub_arg, sub_pat, sub_new);
|
||||
let applied_await = apply_task_await(arena, loc_expr.region, unwrapped_expr, def_pattern, new_loc_ret);
|
||||
let new_defs = arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(split_defs.before), applied_await)));
|
||||
return unwrap_suffixed_expression(arena, new_defs, maybe_def_pat);
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(..)) | Err(EUnwrapped::Malformed) => {
|
||||
// TODO handle case when we have maybe_def_pat so can return an unwrapped up
|
||||
return Err(EUnwrapped::Malformed);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
|
||||
let new_body_def = ValueDef::Body(def_pattern, sub_new);
|
||||
local_defs.replace_with_value_def(tag_index,new_body_def, sub_new.region);
|
||||
let new_defs_expr = arena.alloc(Loc::at(def_expr.region,Defs(arena.alloc(local_defs), loc_ret)));
|
||||
let replaced_def = apply_task_await(arena,def_expr.region,sub_arg,sub_pat,new_defs_expr);
|
||||
return unwrap_suffixed_expression(arena,replaced_def,maybe_def_pat);
|
||||
}
|
||||
Err(err) => return Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to unwrap the loc_ret
|
||||
match unwrap_suffixed_expression(arena,loc_ret,maybe_def_pat){
|
||||
Ok(new_loc_ret) => {
|
||||
Ok(arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(local_defs), new_loc_ret))))
|
||||
},
|
||||
Err(EUnwrapped::UnwrappedSubExpr { sub_arg, sub_pat, sub_new }) => {
|
||||
let new_loc_ret = apply_task_await(arena, loc_expr.region,sub_arg,sub_pat,sub_new);
|
||||
let new_defs = arena.alloc(Loc::at(loc_expr.region,Defs(arena.alloc(local_defs), new_loc_ret)));
|
||||
unwrap_suffixed_expression(arena, new_defs, maybe_def_pat)
|
||||
}
|
||||
Err(EUnwrapped::UnwrappedDefExpr(..)) => {
|
||||
// TODO confirm this is correct with test case
|
||||
Err(EUnwrapped::Malformed)
|
||||
}
|
||||
Err(EUnwrapped::Malformed) => {
|
||||
Err(EUnwrapped::Malformed)
|
||||
},
|
||||
}
|
||||
},
|
||||
_ => internal_error!("unreachable, expected a Defs node to be passed into unwrap_suffixed_expression_defs_help"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for `Task.await (loc_arg) \loc_pat -> loc_new`
|
||||
pub fn apply_task_await<'a>(
|
||||
arena: &'a Bump,
|
||||
region: Region,
|
||||
loc_arg: &'a Loc<Expr<'a>>,
|
||||
loc_pat: &'a Loc<Pattern<'a>>,
|
||||
loc_new: &'a Loc<Expr<'a>>,
|
||||
) -> &'a Loc<Expr<'a>> {
|
||||
// If the pattern and the new are the same then we don't need to unwrap anything
|
||||
// e.g. `Task.await foo \{} -> Task.ok {}` is the same as `foo`
|
||||
if is_matching_empty_record(loc_pat, loc_new) {
|
||||
return loc_arg;
|
||||
}
|
||||
|
||||
// If the pattern and the new are matching answers then we don't need to unwrap anything
|
||||
// e.g. `Task.await foo \#!a1 -> Task.ok #!a1` is the same as `foo`
|
||||
if is_matching_intermediate_answer(loc_pat, loc_new) {
|
||||
return loc_arg;
|
||||
}
|
||||
|
||||
let mut task_await_apply_args: Vec<&'a Loc<Expr<'a>>> = Vec::new_in(arena);
|
||||
|
||||
// apply the unwrapped suffixed expression
|
||||
task_await_apply_args.push(loc_arg);
|
||||
|
||||
// apply the closure
|
||||
let mut closure_pattern = Vec::new_in(arena);
|
||||
closure_pattern.push(*loc_pat);
|
||||
task_await_apply_args.push(arena.alloc(Loc::at(
|
||||
region,
|
||||
Closure(arena.alloc_slice_copy(closure_pattern.as_slice()), loc_new),
|
||||
)));
|
||||
|
||||
arena.alloc(Loc::at(
|
||||
region,
|
||||
Apply(
|
||||
arena.alloc(Loc {
|
||||
region,
|
||||
value: Var {
|
||||
module_name: ModuleName::TASK,
|
||||
ident: "await",
|
||||
},
|
||||
}),
|
||||
arena.alloc(task_await_apply_args),
|
||||
CalledVia::BangSuffix,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn extract_wrapped_task_ok_value<'a>(loc_expr: &'a Loc<Expr<'a>>) -> Option<&'a Loc<Expr<'a>>> {
|
||||
match loc_expr.value {
|
||||
Expr::Apply(function, arguments, _) => match function.value {
|
||||
Var {
|
||||
module_name, ident, ..
|
||||
} if module_name == ModuleName::TASK && ident == "ok" => arguments.first().copied(),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_matching_empty_record<'a>(
|
||||
loc_pat: &'a Loc<Pattern<'a>>,
|
||||
loc_expr: &'a Loc<Expr<'a>>,
|
||||
) -> bool {
|
||||
let is_empty_record = match extract_wrapped_task_ok_value(loc_expr) {
|
||||
Some(task_expr) => match task_expr.value {
|
||||
Expr::Record(collection) => collection.is_empty(),
|
||||
_ => false,
|
||||
},
|
||||
None => false,
|
||||
};
|
||||
|
||||
let is_pattern_empty_record = match loc_pat.value {
|
||||
Pattern::RecordDestructure(collection) => collection.is_empty(),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
is_empty_record && is_pattern_empty_record
|
||||
}
|
||||
|
||||
pub fn is_matching_intermediate_answer<'a>(
|
||||
loc_pat: &'a Loc<Pattern<'a>>,
|
||||
loc_new: &'a Loc<Expr<'a>>,
|
||||
) -> bool {
|
||||
let pat_ident = match loc_pat.value {
|
||||
Pattern::Identifier { ident, .. } => Some(ident),
|
||||
_ => None,
|
||||
};
|
||||
let exp_ident = match loc_new.value {
|
||||
Expr::Var {
|
||||
module_name, ident, ..
|
||||
} if module_name.is_empty() && ident.starts_with('#') => Some(ident),
|
||||
_ => None,
|
||||
};
|
||||
let exp_ident_in_task = match extract_wrapped_task_ok_value(loc_new) {
|
||||
Some(task_expr) => match task_expr.value {
|
||||
Expr::Var {
|
||||
module_name, ident, ..
|
||||
} if module_name.is_empty() && ident.starts_with('#') => Some(ident),
|
||||
_ => None,
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
match (pat_ident, exp_ident, exp_ident_in_task) {
|
||||
(Some(a), Some(b), None) => a == b,
|
||||
(Some(a), None, Some(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
|
@ -7,12 +7,13 @@ use roc_can::expr::Output;
|
|||
use roc_can::expr::{canonicalize_expr, Expr};
|
||||
use roc_can::scope::Scope;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, PackageModuleIds, Symbol};
|
||||
use roc_problem::can::Problem;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{AliasVar, Type};
|
||||
use std::hash::Hash;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn test_home() -> ModuleId {
|
||||
ModuleIds::default().get_or_insert(&"Test".into())
|
||||
|
@ -43,7 +44,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
|
|||
|
||||
let mut var_store = VarStore::default();
|
||||
let var = var_store.fresh();
|
||||
let module_ids = ModuleIds::default();
|
||||
let qualified_module_ids = PackageModuleIds::default();
|
||||
|
||||
// Desugar operators (convert them to Apply calls, taking into account
|
||||
// operator precedence and associativity rules), before doing other canonicalization.
|
||||
|
@ -60,7 +61,12 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
|
|||
arena.alloc("TestPath"),
|
||||
);
|
||||
|
||||
let mut scope = Scope::new(home, IdentIds::default(), Default::default());
|
||||
let mut scope = Scope::new(
|
||||
home,
|
||||
"TestPath".into(),
|
||||
IdentIds::default(),
|
||||
Default::default(),
|
||||
);
|
||||
scope.add_alias(
|
||||
Symbol::NUM_INT,
|
||||
Region::zero(),
|
||||
|
@ -74,7 +80,14 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
|
|||
);
|
||||
|
||||
let dep_idents = IdentIds::exposed_builtins(0);
|
||||
let mut env = Env::new(arena, home, &dep_idents, &module_ids);
|
||||
let mut env = Env::new(
|
||||
arena,
|
||||
home,
|
||||
Path::new("Test.roc"),
|
||||
&dep_idents,
|
||||
&qualified_module_ids,
|
||||
None,
|
||||
);
|
||||
let (loc_expr, output) = canonicalize_expr(
|
||||
&mut env,
|
||||
&mut var_store,
|
||||
|
@ -87,7 +100,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
|
|||
all_ident_ids.insert(home, scope.locals.ident_ids);
|
||||
|
||||
let interns = Interns {
|
||||
module_ids: env.module_ids.clone(),
|
||||
module_ids: env.qualified_module_ids.clone().into_module_ids(),
|
||||
all_ident_ids,
|
||||
};
|
||||
|
||||
|
|
922
crates/compiler/can/tests/test_suffixed.rs
Normal file
922
crates/compiler/can/tests/test_suffixed.rs
Normal file
|
@ -0,0 +1,922 @@
|
|||
#[cfg(test)]
|
||||
mod suffixed_tests {
|
||||
|
||||
use bumpalo::Bump;
|
||||
use roc_can::desugar::desugar_defs_node_values;
|
||||
use roc_parse::test_helpers::parse_defs_with;
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
|
||||
fn run_test(src: &str, expected: &str) {
|
||||
let arena = &Bump::new();
|
||||
let mut defs = parse_defs_with(arena, src).unwrap();
|
||||
desugar_defs_node_values(arena, &mut defs, src, &mut None, "test.roc", true);
|
||||
print!("{:#?}", &defs);
|
||||
assert_multiline_str_eq!(format!("{:?}", &defs).as_str(), expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* This example tests a suffixed statement, followed
|
||||
* by a Body with an empty record pattern.
|
||||
*
|
||||
* The def final expression is explicitly provided.
|
||||
*
|
||||
```roc
|
||||
main =
|
||||
line! "Ahoy"
|
||||
{} = "There" |> Stdout.line!
|
||||
|
||||
Task.ok {}
|
||||
|
||||
main =
|
||||
Task.await [line "Ahoy"] \{} ->
|
||||
Task.await [Stdout.line "there"] \{} ->
|
||||
Task.ok {}
|
||||
|
||||
main =
|
||||
Task.await [line "Ahoy"] \{} -> Stdout.line "there"
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn multi_defs_stmts() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
line! "Ahoy"
|
||||
{} = "There" |> Stdout.line!
|
||||
|
||||
Task.ok {}
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-125], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @29-36 Apply(@29-36 Var { module_name: "Task", ident: "await" }, [@29-36 Apply(@29-36 Var { module_name: "", ident: "line" }, [@30-36 Str(PlainLine("Ahoy"))], Space), @29-36 Closure([@29-36 RecordDestructure([])], @58-80 Apply(@58-80 Var { module_name: "Stdout", ident: "line" }, [@58-65 Str(PlainLine("There"))], BinOp(Pizza)))], BangSuffix))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The most simple suffixed example. A single statement
|
||||
* without arguments and a final expression.
|
||||
```roc
|
||||
main =
|
||||
foo!
|
||||
|
||||
ok {}
|
||||
|
||||
main =
|
||||
Task.await [foo] \{} ->
|
||||
ok {}
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
|
||||
fn basic() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
foo!
|
||||
|
||||
ok {}
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-47], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @24-24 Apply(@24-24 Var { module_name: "Task", ident: "await" }, [@24-24 Var { module_name: "", ident: "foo" }, @24-24 Closure([@24-24 RecordDestructure([])], @42-47 Apply(@42-44 Var { module_name: "", ident: "ok" }, [@45-47 Record([])], Space))], BangSuffix))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A single suffixed statement with arguments applied.
|
||||
* Note there is no final expression.
|
||||
```roc
|
||||
main = foo! "bar" {} "baz"
|
||||
|
||||
main = foo "bar" {} "baz"
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn last_suffixed_single() {
|
||||
run_test(
|
||||
r#"
|
||||
main = foo! "bar" {} "baz"
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-26], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @0-26 Apply(@0-26 Var { module_name: "", ident: "foo" }, [@12-17 Str(PlainLine("bar")), @18-20 Record([]), @21-26 Str(PlainLine("baz"))], Space))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiple suffixed statements with no
|
||||
* arguments, and no final expression.
|
||||
```roc
|
||||
main =
|
||||
foo!
|
||||
bar!
|
||||
baz!
|
||||
|
||||
main =
|
||||
Task.await foo \{} ->
|
||||
Task.await bar \{} ->
|
||||
Task.await baz \{} ->
|
||||
Task.ok {}
|
||||
|
||||
main =
|
||||
Task.await foo \{} ->
|
||||
Task.await bar \{} ->
|
||||
baz
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn last_suffixed_multiple() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
foo!
|
||||
bar!
|
||||
baz!
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-70], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @28-28 Apply(@28-28 Var { module_name: "Task", ident: "await" }, [@28-28 Var { module_name: "", ident: "foo" }, @28-28 Closure([@28-28 RecordDestructure([])], @45-49 Apply(@45-49 Var { module_name: "Task", ident: "await" }, [@45-49 Var { module_name: "", ident: "bar" }, @45-49 Closure([@45-49 RecordDestructure([])], @66-70 Var { module_name: "", ident: "baz" })], BangSuffix))], BangSuffix))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A definition with a closure that contains a Defs node, which also
|
||||
* contains a suffixed binops statement.
|
||||
```roc
|
||||
main =
|
||||
x = \msg ->
|
||||
msg |> line!
|
||||
ok {}
|
||||
|
||||
x "hi"
|
||||
|
||||
main =
|
||||
|
||||
x = \msg ->
|
||||
Task.await [line msg] \{} -> ok {}
|
||||
|
||||
x "hi"
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn closure_simple() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
x = \msg ->
|
||||
msg |> line!
|
||||
ok {}
|
||||
|
||||
x "hi"
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-118], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @23-118 Defs(Defs { tags: [Index(2147483649)], regions: [@27-94], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@23-24 Identifier { ident: "x" }, @27-94 Closure([@28-31 Identifier { ident: "msg" }], @55-94 Defs(Defs { tags: [Index(2147483648)], regions: [@55-66], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@55-66 RecordDestructure([]), @55-66 Apply(@62-66 TaskAwaitBang(Var { module_name: "", ident: "line" }), [@55-58 Var { module_name: "", ident: "msg" }], BinOp(Pizza)))] }, @89-94 Apply(@89-91 Var { module_name: "", ident: "ok" }, [@92-94 Record([])], Space)))), Body(@23-24 Identifier { ident: "x" }, @27-94 Closure([@28-31 Identifier { ident: "msg" }], @55-66 Apply(@55-66 Var { module_name: "Task", ident: "await" }, [@55-66 Apply(@55-66 Var { module_name: "", ident: "line" }, [@55-58 Var { module_name: "", ident: "msg" }], BinOp(Pizza)), @55-66 Closure([@55-66 RecordDestructure([])], @89-94 Apply(@89-91 Var { module_name: "", ident: "ok" }, [@92-94 Record([])], Space))], BangSuffix)))] }, @112-118 Apply(@112-113 Var { module_name: "", ident: "x" }, [@114-118 Str(PlainLine("hi"))], Space)))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of unwrapping a pipline statement
|
||||
*
|
||||
* Note pipelines are desugared into Apply functions,
|
||||
* however this also tests the parser.
|
||||
*
|
||||
```roc
|
||||
main =
|
||||
"hello"
|
||||
|> Str.concat "world"
|
||||
|> line!
|
||||
|
||||
Task.ok {}
|
||||
|
||||
main =
|
||||
Task.await [line [Str.concat "hello" "world"]] \{} ->
|
||||
Task.ok {}
|
||||
|
||||
main =
|
||||
line (Str.concat "hello" "world")
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn simple_pizza() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
"hello"
|
||||
|> Str.concat "world"
|
||||
|> line!
|
||||
|
||||
Task.ok {}
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-130], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @24-93 Apply(@24-93 Var { module_name: "", ident: "line" }, [@24-69 Apply(@51-61 Var { module_name: "Str", ident: "concat" }, [@24-31 Str(PlainLine("hello")), @62-69 Str(PlainLine("world"))], BinOp(Pizza))], BinOp(Pizza)))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example with a parens suffixed sub-expression
|
||||
* in the function part of an Apply.
|
||||
*
|
||||
* Note how the parens unwraps into an intermediate answer #!a0 instead of
|
||||
* unwrapping the def `do`.
|
||||
*
|
||||
```roc
|
||||
main =
|
||||
do = (sayMultiple!) "hi"
|
||||
do
|
||||
|
||||
main =
|
||||
Task.await [sayMultiple] \#!a0 ->
|
||||
do = (#!a0) "hi"
|
||||
do
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn body_parens_apply() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
do = (sayMultiple!) "hi"
|
||||
do
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-66], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @28-47 Apply(@28-47 Var { module_name: "Task", ident: "await" }, [Var { module_name: "", ident: "sayMultiple" }, @28-47 Closure([Identifier { ident: "#!a0" }], @28-47 Defs(Defs { tags: [Index(2147483650)], regions: [@28-47], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@23-25 Identifier { ident: "do" }, @28-47 Apply(@29-41 ParensAround(TaskAwaitBang(Var { module_name: "", ident: "sayMultiple" })), [@43-47 Str(PlainLine("hi"))], Space)), Body(@23-25 Identifier { ident: "do" }, @28-47 Apply(@29-41 ParensAround(Var { module_name: "", ident: "#!a0" }), [@43-47 Str(PlainLine("hi"))], Space)), Body(@23-25 Identifier { ident: "do" }, @28-47 Apply(@29-41 ParensAround(Var { module_name: "", ident: "#!a0" }), [@43-47 Str(PlainLine("hi"))], Space))] }, @64-66 Var { module_name: "", ident: "do" }))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example of unwrapping mixed Body defs with
|
||||
* Var's of both single and multiple suffixes
|
||||
```roc
|
||||
main =
|
||||
a = foo!
|
||||
b = bar!!
|
||||
baz a b
|
||||
|
||||
main =
|
||||
Task.await [foo] \a ->
|
||||
b = bar!!
|
||||
baz a b
|
||||
|
||||
main =
|
||||
Task.await [foo] \a ->
|
||||
Tas.await [bar] \#!a0 ->
|
||||
b = #!a0!
|
||||
baz a b
|
||||
|
||||
main =
|
||||
Task.await [foo] \a ->
|
||||
Task.await [bar] \#!a0 ->
|
||||
Task.await #!a0 \b -> baz a b
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn var_suffixes() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
a = foo!
|
||||
b = bar!!
|
||||
baz a b
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-81], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @27-31 Apply(@27-31 Var { module_name: "Task", ident: "await" }, [@27-31 Var { module_name: "", ident: "foo" }, @27-31 Closure([@23-24 Identifier { ident: "a" }], @27-31 Apply(@27-31 Var { module_name: "Task", ident: "await" }, [@48-57 Var { module_name: "", ident: "bar" }, @27-31 Closure([@48-57 Identifier { ident: "#!a0" }], @48-57 Apply(@48-57 Var { module_name: "Task", ident: "await" }, [@48-57 Var { module_name: "", ident: "#!a0" }, @48-57 Closure([@48-49 Identifier { ident: "b" }], @74-81 Apply(@74-77 Var { module_name: "", ident: "baz" }, [@78-79 Var { module_name: "", ident: "a" }, @80-81 Var { module_name: "", ident: "b" }], Space))], BangSuffix))], BangSuffix))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example with a multiple suffixed Var
|
||||
*
|
||||
* Note it unwraps into an intermediate answer `#!a0`
|
||||
*
|
||||
```roc
|
||||
main =
|
||||
foo!!
|
||||
bar
|
||||
|
||||
main =
|
||||
Task.await [foo] \#!a0 ->
|
||||
#!a0!
|
||||
bar
|
||||
|
||||
main =
|
||||
Task.await [foo] \#!a0 ->
|
||||
Task.await [#!a0] \{} -> bar
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn multiple_suffix() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
foo!!
|
||||
bar
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-49], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @24-49 Apply(@24-49 Var { module_name: "Task", ident: "await" }, [@29-29 Var { module_name: "", ident: "foo" }, @24-49 Closure([@29-29 Identifier { ident: "#!a0" }], @29-29 Apply(@29-29 Var { module_name: "Task", ident: "await" }, [@29-29 Var { module_name: "", ident: "#!a0" }, @29-29 Closure([@29-29 RecordDestructure([])], @46-49 Var { module_name: "", ident: "bar" })], BangSuffix))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A suffixed expression in the function part of the Apply
|
||||
```roc
|
||||
main =
|
||||
x = (foo! "bar") "hello"
|
||||
baz x
|
||||
|
||||
main =
|
||||
Task.await [foo "bar"] \#!a0 ->
|
||||
x = (#!a0) "hello"
|
||||
baz x
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn apply_function_suffixed() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
x = (foo! "bar") "hello"
|
||||
baz x
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-70], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @28-48 Apply(@28-48 Var { module_name: "Task", ident: "await" }, [Apply(Var { module_name: "", ident: "foo" }, [@34-39 Str(PlainLine("bar"))], Space), @28-48 Closure([Identifier { ident: "#!a0" }], @28-48 Defs(Defs { tags: [Index(2147483650)], regions: [@28-48], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@24-25 Identifier { ident: "x" }, @28-48 Apply(@29-39 ParensAround(Apply(@29-32 TaskAwaitBang(Var { module_name: "", ident: "foo" }), [@34-39 Str(PlainLine("bar"))], Space)), [@41-48 Str(PlainLine("hello"))], Space)), Body(@24-25 Identifier { ident: "x" }, @28-48 Apply(@29-39 ParensAround(Var { module_name: "", ident: "#!a0" }), [@41-48 Str(PlainLine("hello"))], Space)), Body(@24-25 Identifier { ident: "x" }, @28-48 Apply(@29-39 ParensAround(Var { module_name: "", ident: "#!a0" }), [@41-48 Str(PlainLine("hello"))], Space))] }, @65-70 Apply(@65-68 Var { module_name: "", ident: "baz" }, [@69-70 Var { module_name: "", ident: "x" }], Space)))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A suffixed expression in an Apply argument position.
|
||||
```roc
|
||||
main =
|
||||
x = bar (foo! "hello")
|
||||
baz x
|
||||
|
||||
main =
|
||||
Task.await [foo "hello"] \#!a0 ->
|
||||
x = bar (#!a0)
|
||||
baz x
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn apply_argument_suffixed() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
x = bar (foo! "hello")
|
||||
baz x
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-68], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @28-46 Apply(@28-46 Var { module_name: "Task", ident: "await" }, [Apply(Var { module_name: "", ident: "foo" }, [@38-45 Str(PlainLine("hello"))], Space), @28-46 Closure([Identifier { ident: "#!a0" }], @28-46 Defs(Defs { tags: [Index(2147483650)], regions: [@28-46], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@24-25 Identifier { ident: "x" }, @28-46 Apply(@28-31 Var { module_name: "", ident: "bar" }, [@33-45 ParensAround(Apply(@33-36 TaskAwaitBang(Var { module_name: "", ident: "foo" }), [@38-45 Str(PlainLine("hello"))], Space))], Space)), Body(@24-25 Identifier { ident: "x" }, @28-46 Apply(@28-31 Var { module_name: "", ident: "bar" }, [@33-45 ParensAround(Var { module_name: "", ident: "#!a0" })], Space)), Body(@24-25 Identifier { ident: "x" }, @28-46 Apply(@28-31 Var { module_name: "", ident: "bar" }, [@33-45 ParensAround(Var { module_name: "", ident: "#!a0" })], Space))] }, @63-68 Apply(@63-66 Var { module_name: "", ident: "baz" }, [@67-68 Var { module_name: "", ident: "x" }], Space)))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example where the suffixed def is not the first def
|
||||
```roc
|
||||
main =
|
||||
msg = "hello"
|
||||
x = foo! msg
|
||||
bar x
|
||||
|
||||
main =
|
||||
msg = "hello"
|
||||
Task.await [foo msg] \x -> bar x
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn multiple_def_first_suffixed() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
msg = "hello"
|
||||
x = foo! msg
|
||||
bar x
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-88], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @0-88 Defs(Defs { tags: [Index(2147483649)], regions: [@30-37], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@24-27 Identifier { ident: "msg" }, @30-37 Str(PlainLine("hello"))), Body(@24-27 Identifier { ident: "msg" }, @30-37 Str(PlainLine("hello")))] }, @0-88 Apply(@0-88 Var { module_name: "Task", ident: "await" }, [@54-66 Apply(@54-66 Var { module_name: "", ident: "foo" }, [@63-66 Var { module_name: "", ident: "msg" }], Space), @0-88 Closure([@54-55 Identifier { ident: "x" }], @83-88 Apply(@83-86 Var { module_name: "", ident: "bar" }, [@87-88 Var { module_name: "", ident: "x" }], Space))], BangSuffix)))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotated defs and a suffixed expression
|
||||
* with annotations inside a closure
|
||||
```roc
|
||||
main =
|
||||
x : Str -> Task _ _
|
||||
x = \msg ->
|
||||
|
||||
y : Task {} _
|
||||
y = line! msg
|
||||
y
|
||||
|
||||
x "foo"
|
||||
|
||||
main =
|
||||
x : Str -> Task _ _
|
||||
x = \msg ->
|
||||
Task.await [line msg] \y -> y
|
||||
|
||||
x "foo"
|
||||
```
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn closure_with_annotations() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
x : Str -> Task _ _
|
||||
x = \msg ->
|
||||
|
||||
y : Task {} _
|
||||
y = line! msg
|
||||
y
|
||||
|
||||
x "foo"
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-187], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @0-187 Defs(Defs { tags: [Index(2147483650)], regions: [@60-162], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Annotation(@24-25 Identifier { ident: "x" }, @28-43 Function([@28-31 Apply("", "Str", [])], @35-43 Apply("", "Task", [@40-41 Inferred, @42-43 Inferred]))), AnnotatedBody { ann_pattern: @24-25 Identifier { ident: "x" }, ann_type: @28-43 Function([@28-31 Apply("", "Str", [])], @35-43 Apply("", "Task", [@40-41 Inferred, @42-43 Inferred])), comment: None, body_pattern: @60-61 Identifier { ident: "x" }, body_expr: @60-162 Closure([@65-68 Identifier { ident: "msg" }], @93-162 Defs(Defs { tags: [Index(2147483649)], regions: [@93-140], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Annotation(@93-94 Identifier { ident: "y" }, @97-106 Apply("", "Task", [@102-104 Record { fields: [], ext: None }, @105-106 Inferred])), AnnotatedBody { ann_pattern: @93-94 Identifier { ident: "y" }, ann_type: @97-106 Apply("", "Task", [@102-104 Record { fields: [], ext: None }, @105-106 Inferred]), comment: None, body_pattern: @127-128 Identifier { ident: "y" }, body_expr: @127-140 Apply(@131-135 TaskAwaitBang(Var { module_name: "", ident: "line" }), [@137-140 Var { module_name: "", ident: "msg" }], Space) }] }, @161-162 Var { module_name: "", ident: "y" })) }, AnnotatedBody { ann_pattern: @24-25 Identifier { ident: "x" }, ann_type: @28-43 Function([@28-31 Apply("", "Str", [])], @35-43 Apply("", "Task", [@40-41 Inferred, @42-43 Inferred])), comment: None, body_pattern: @60-61 Identifier { ident: "x" }, body_expr: @60-162 Closure([@65-68 Identifier { ident: "msg" }], @127-140 Apply(@127-140 Var { module_name: "Task", ident: "await" }, [@127-140 Apply(@127-140 Var { module_name: "", ident: "line" }, [@137-140 Var { module_name: "", ident: "msg" }], Space), @127-140 Closure([@127-128 Identifier { ident: "y" }], @161-162 Var { module_name: "", ident: "y" })], BangSuffix)) }] }, @180-187 Apply(@180-181 Var { module_name: "", ident: "x" }, [@182-187 Str(PlainLine("foo"))], Space)))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested suffixed expressions
|
||||
```roc
|
||||
run = line! (nextMsg!)
|
||||
|
||||
run = Task.await nextMsg \#!a0 -> line! (#!a0)
|
||||
|
||||
run = Task.await nextMsg \#!a0 -> line (#!a0)
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn nested_simple() {
|
||||
run_test(
|
||||
r#"
|
||||
run = line! (nextMsg!)
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-22], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-3 Identifier { ident: "run" }, @0-22 Apply(@0-22 Var { module_name: "Task", ident: "await" }, [Var { module_name: "", ident: "nextMsg" }, @0-22 Closure([Identifier { ident: "#!a0" }], @0-22 Apply(@0-22 Var { module_name: "", ident: "line" }, [@13-21 ParensAround(Var { module_name: "", ident: "#!a0" })], Space))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested suffixed expressions
|
||||
```roc
|
||||
main =
|
||||
z = foo! (bar! baz) (blah stuff)
|
||||
doSomething z
|
||||
|
||||
main =
|
||||
Task.await [bar baz] \#!a0 ->
|
||||
z = foo! (#!a0) (blah stuff)
|
||||
doSomething z
|
||||
|
||||
main =
|
||||
Task.await [bar baz] \#!a0 ->
|
||||
Task.await [foo (#!a0) (blah stuff)] \z -> doSomething z
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn nested_complex() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
z = foo! (bar! baz) (blah stuff)
|
||||
doSomething z
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-86], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @28-56 Apply(@28-56 Var { module_name: "Task", ident: "await" }, [Apply(Var { module_name: "", ident: "bar" }, [@39-42 Var { module_name: "", ident: "baz" }], Space), @28-56 Closure([Identifier { ident: "#!a0" }], @28-56 Apply(@28-56 Var { module_name: "Task", ident: "await" }, [@28-56 Apply(@28-56 Var { module_name: "", ident: "foo" }, [@34-42 ParensAround(Var { module_name: "", ident: "#!a0" }), @45-55 ParensAround(Apply(@45-49 Var { module_name: "", ident: "blah" }, [@50-55 Var { module_name: "", ident: "stuff" }], Space))], Space), @28-56 Closure([@24-25 Identifier { ident: "z" }], @73-86 Apply(@73-84 Var { module_name: "", ident: "doSomething" }, [@85-86 Var { module_name: "", ident: "z" }], Space))], BangSuffix))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A closure that contains a Defs node
|
||||
```roc
|
||||
main = foo "bar" {} "baz"
|
||||
|
||||
foo : Str, {}, Str -> Task {} I32
|
||||
foo = \a, _, b ->
|
||||
line! a
|
||||
line! b
|
||||
|
||||
Task.ok {}
|
||||
|
||||
foo : Str, {}, Str -> Task {} I32
|
||||
foo = \a, _, b ->
|
||||
Task.await line a \{} ->
|
||||
line! b
|
||||
|
||||
Task.ok {}
|
||||
|
||||
foo : Str, {}, Str -> Task {} I32
|
||||
foo = \a, _, b ->
|
||||
Task.await [line a] \{} ->
|
||||
Task.await [line b] \{} ->
|
||||
Task.ok {}
|
||||
|
||||
foo : Str, {}, Str -> Task {} I32
|
||||
foo = \a, _, b ->
|
||||
Task.await [line a] \{} -> line b
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn closure_with_defs() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
|
||||
foo : Str, {}, Str -> Task {} I32
|
||||
foo = \a, _, b ->
|
||||
line! a
|
||||
line! b
|
||||
|
||||
Task.ok {}
|
||||
|
||||
foo "bar" {} "baz"
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-249], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @25-249 Defs(Defs { tags: [Index(2147483650)], regions: [@81-193], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Annotation(@25-28 Identifier { ident: "foo" }, @31-58 Function([@31-34 Apply("", "Str", []), @36-38 Record { fields: [], ext: None }, @40-43 Apply("", "Str", [])], @47-58 Apply("", "Task", [@52-54 Record { fields: [], ext: None }, @55-58 Apply("", "I32", [])]))), AnnotatedBody { ann_pattern: @25-28 Identifier { ident: "foo" }, ann_type: @31-58 Function([@31-34 Apply("", "Str", []), @36-38 Record { fields: [], ext: None }, @40-43 Apply("", "Str", [])], @47-58 Apply("", "Task", [@52-54 Record { fields: [], ext: None }, @55-58 Apply("", "I32", [])])), comment: None, body_pattern: @75-78 Identifier { ident: "foo" }, body_expr: @81-193 Closure([@82-83 Identifier { ident: "a" }, @85-86 Underscore(""), @88-89 Identifier { ident: "b" }], @114-193 Defs(Defs { tags: [Index(2147483648), Index(2147483649)], regions: [@119-121, @142-149], space_before: [Slice(start = 0, length = 0), Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0), Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@119-121 RecordDestructure([]), @119-121 Apply(@114-118 TaskAwaitBang(Var { module_name: "", ident: "line" }), [@120-121 Var { module_name: "", ident: "a" }], Space)), Body(@142-149 RecordDestructure([]), @142-149 Apply(@142-146 TaskAwaitBang(Var { module_name: "", ident: "line" }), [@148-149 Var { module_name: "", ident: "b" }], Space))] }, @183-193 Apply(@183-190 Var { module_name: "Task", ident: "ok" }, [@191-193 Record([])], Space))) }, AnnotatedBody { ann_pattern: @25-28 Identifier { ident: "foo" }, ann_type: @31-58 Function([@31-34 Apply("", "Str", []), @36-38 Record { fields: [], ext: None }, @40-43 Apply("", "Str", [])], @47-58 Apply("", "Task", [@52-54 Record { fields: [], ext: None }, @55-58 Apply("", "I32", [])])), comment: None, body_pattern: @75-78 Identifier { ident: "foo" }, body_expr: @81-193 Closure([@82-83 Identifier { ident: "a" }, @85-86 Underscore(""), @88-89 Identifier { ident: "b" }], @119-121 Apply(@119-121 Var { module_name: "Task", ident: "await" }, [@119-121 Apply(@119-121 Var { module_name: "", ident: "line" }, [@120-121 Var { module_name: "", ident: "a" }], Space), @119-121 Closure([@119-121 RecordDestructure([])], @142-149 Apply(@142-149 Var { module_name: "", ident: "line" }, [@148-149 Var { module_name: "", ident: "b" }], Space))], BangSuffix)) }] }, @231-249 Apply(@231-234 Var { module_name: "", ident: "foo" }, [@235-240 Str(PlainLine("bar")), @241-243 Record([]), @244-249 Str(PlainLine("baz"))], Space)))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test when the suffixed def being unwrapped is not the first or last
|
||||
```roc
|
||||
main =
|
||||
a = "Foo"
|
||||
Stdout.line! a
|
||||
|
||||
printBar!
|
||||
|
||||
printBar =
|
||||
b = "Bar"
|
||||
Stdout.line b
|
||||
|
||||
main =
|
||||
a = "Foo"
|
||||
Task.await [Stdout.line a] \{} ->
|
||||
printBar!
|
||||
|
||||
main =
|
||||
a = "Foo"
|
||||
Task.await [Stdout.line a] \{} ->
|
||||
Task.await [printBar] \{} ->
|
||||
Task.ok {}
|
||||
|
||||
main =
|
||||
a = "Foo"
|
||||
Task.await [Stdout.line a] \{} ->
|
||||
printBar
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn defs_suffixed_middle() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
a = "Foo"
|
||||
Stdout.line! a
|
||||
|
||||
printBar!
|
||||
|
||||
printBar =
|
||||
b = "Bar"
|
||||
Stdout.line b
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648), Index(2147483649)], regions: [@0-90, @120-186], space_before: [Slice(start = 0, length = 0), Slice(start = 0, length = 2)], space_after: [Slice(start = 0, length = 0), Slice(start = 2, length = 0)], spaces: [Newline, Newline], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @23-90 Defs(Defs { tags: [Index(2147483649)], regions: [@27-32], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@23-24 Identifier { ident: "a" }, @27-32 Str(PlainLine("Foo"))), Body(@23-24 Identifier { ident: "a" }, @27-32 Str(PlainLine("Foo")))] }, @23-90 Apply(@23-90 Var { module_name: "Task", ident: "await" }, [@49-63 Apply(@49-63 Var { module_name: "Stdout", ident: "line" }, [@62-63 Var { module_name: "", ident: "a" }], Space), @23-90 Closure([@49-63 RecordDestructure([])], @81-90 Var { module_name: "", ident: "printBar" })], BangSuffix))), Body(@120-128 Identifier { ident: "printBar" }, @147-186 Defs(Defs { tags: [Index(2147483649)], regions: [@151-156], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@147-148 Identifier { ident: "b" }, @151-156 Str(PlainLine("Bar"))), Body(@147-148 Identifier { ident: "b" }, @151-156 Str(PlainLine("Bar")))] }, @173-186 Apply(@173-184 Var { module_name: "Stdout", ident: "line" }, [@185-186 Var { module_name: "", ident: "b" }], Space)))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple if-then-else statement which is split
|
||||
```roc
|
||||
|
||||
main =
|
||||
isTrue = Task.ok Bool.true
|
||||
isFalse = Task.ok Bool.false
|
||||
|
||||
if isFalse! then
|
||||
line "fail"
|
||||
else if isTrue! then
|
||||
line "success"
|
||||
else
|
||||
line "fail"
|
||||
|
||||
main =
|
||||
isTrue = Task.ok Bool.true
|
||||
|
||||
Task.await isFalse \#!a0 ->
|
||||
if #!a0 then
|
||||
line "fail"
|
||||
else
|
||||
Task.await isTrue \#!a1 ->
|
||||
if #!a0 then
|
||||
line "success"
|
||||
else
|
||||
line "fail"
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn if_simple() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
isTrue = Task.ok Bool.true
|
||||
isFalse = Task.ok Bool.false
|
||||
|
||||
if isFalse! then
|
||||
line "fail"
|
||||
else if isTrue! then
|
||||
line "success"
|
||||
else
|
||||
line "fail"
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-286], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @23-286 Defs(Defs { tags: [Index(2147483650), Index(2147483651)], regions: [@32-49, @76-94], space_before: [Slice(start = 0, length = 0), Slice(start = 0, length = 1)], space_after: [Slice(start = 0, length = 0), Slice(start = 1, length = 0)], spaces: [Newline], type_defs: [], value_defs: [Body(@23-29 Identifier { ident: "isTrue" }, @32-49 Apply(@32-39 Var { module_name: "Task", ident: "ok" }, [@40-49 Var { module_name: "Bool", ident: "true" }], Space)), Body(@66-73 Identifier { ident: "isFalse" }, @76-94 Apply(@76-83 Var { module_name: "Task", ident: "ok" }, [@84-94 Var { module_name: "Bool", ident: "false" }], Space)), Body(@23-29 Identifier { ident: "isTrue" }, @32-49 Apply(@32-39 Var { module_name: "Task", ident: "ok" }, [@40-49 Var { module_name: "Bool", ident: "true" }], Space)), Body(@66-73 Identifier { ident: "isFalse" }, @76-94 Apply(@76-83 Var { module_name: "Task", ident: "ok" }, [@84-94 Var { module_name: "Bool", ident: "false" }], Space))] }, @115-123 Apply(@115-123 Var { module_name: "Task", ident: "await" }, [@115-123 Var { module_name: "", ident: "isFalse" }, @115-123 Closure([@115-123 Identifier { ident: "#!a0" }], @112-286 If([(@115-123 Var { module_name: "", ident: "#!a0" }, @149-160 Apply(@149-153 Var { module_name: "", ident: "line" }, [@154-160 Str(PlainLine("fail"))], Space))], @185-192 Apply(@185-192 Var { module_name: "Task", ident: "await" }, [@185-192 Var { module_name: "", ident: "isTrue" }, @185-192 Closure([@185-192 Identifier { ident: "#!a1" }], @112-286 If([(@185-192 Var { module_name: "", ident: "#!a1" }, @219-233 Apply(@219-223 Var { module_name: "", ident: "line" }, [@224-233 Str(PlainLine("success"))], Space))], @275-286 Apply(@275-279 Var { module_name: "", ident: "line" }, [@280-286 Str(PlainLine("fail"))], Space)))], BangSuffix)))], BangSuffix)))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A more complex example including the use of nested Defs nodes
|
||||
```roc
|
||||
# OTHER DEFS AND INTERMEDIATE STEPS NOT SHOWN
|
||||
msg =
|
||||
Task.await isTrue \#!a0 ->
|
||||
if !(#!a0) then
|
||||
Task.await line "fail" \{} -> err 1
|
||||
else
|
||||
Task.await isFalsey Bool.false \#!a1 ->
|
||||
if (#!a1) then
|
||||
Task.await line "nope" \{} -> ok {}
|
||||
else
|
||||
line "success"
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn if_complex() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
isTrue = Task.ok Bool.true
|
||||
isFalsey = \x -> Task.ok x
|
||||
msg : Task {} I32
|
||||
msg =
|
||||
if !(isTrue!) then
|
||||
line! "fail"
|
||||
err 1
|
||||
else if (isFalsey! Bool.false) then
|
||||
line! "nope"
|
||||
ok {}
|
||||
else
|
||||
line! "success"
|
||||
|
||||
msg
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-466], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @0-466 Defs(Defs { tags: [Index(2147483652), Index(2147483653), Index(2147483654)], regions: [@32-49, @77-92, @143-445], space_before: [Slice(start = 0, length = 0), Slice(start = 0, length = 1), Slice(start = 1, length = 1)], space_after: [Slice(start = 0, length = 0), Slice(start = 1, length = 0), Slice(start = 2, length = 0)], spaces: [Newline, Newline], type_defs: [], value_defs: [Body(@23-29 Identifier { ident: "isTrue" }, @32-49 Apply(@32-39 Var { module_name: "Task", ident: "ok" }, [@40-49 Var { module_name: "Bool", ident: "true" }], Space)), Body(@66-74 Identifier { ident: "isFalsey" }, @77-92 Closure([@78-79 Identifier { ident: "x" }], @83-92 Apply(@83-90 Var { module_name: "Task", ident: "ok" }, [@91-92 Var { module_name: "", ident: "x" }], Space))), Annotation(@109-112 Identifier { ident: "msg" }, @115-126 Apply("", "Task", [@120-122 Record { fields: [], ext: None }, @123-126 Apply("", "I32", [])])), AnnotatedBody { ann_pattern: @109-112 Identifier { ident: "msg" }, ann_type: @115-126 Apply("", "Task", [@120-122 Record { fields: [], ext: None }, @123-126 Apply("", "I32", [])]), comment: None, body_pattern: @143-146 Identifier { ident: "msg" }, body_expr: @143-445 If([(@173-183 Apply(@173-174 Var { module_name: "Bool", ident: "not" }, [@175-182 ParensAround(TaskAwaitBang(Var { module_name: "", ident: "isTrue" }))], UnaryOp(Not)), @213-256 Defs(Defs { tags: [Index(2147483648)], regions: [@218-225], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@218-225 RecordDestructure([]), @218-225 Apply(@213-217 TaskAwaitBang(Var { module_name: "", ident: "line" }), [@219-225 Str(PlainLine("fail"))], Space))] }, @251-256 Apply(@251-254 Var { module_name: "", ident: "err" }, [@255-256 Num("1")], Space))), (@285-307 ParensAround(Apply(@286-294 TaskAwaitBang(Var { module_name: "", ident: "isFalsey" }), [@296-306 Var { module_name: "Bool", ident: "false" }], Space)), @338-380 Defs(Defs { tags: [Index(2147483648)], regions: [@343-350], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@343-350 RecordDestructure([]), @343-350 Apply(@338-342 TaskAwaitBang(Var { module_name: "", ident: "line" }), [@344-350 Str(PlainLine("nope"))], Space))] }, @375-380 Apply(@375-377 Var { module_name: "", ident: "ok" }, [@378-380 Record([])], Space)))], @430-445 Apply(@430-434 TaskAwaitBang(Var { module_name: "", ident: "line" }), [@436-445 Str(PlainLine("success"))], Space)) }, Body(@23-29 Identifier { ident: "isTrue" }, @32-49 Apply(@32-39 Var { module_name: "Task", ident: "ok" }, [@40-49 Var { module_name: "Bool", ident: "true" }], Space)), Body(@66-74 Identifier { ident: "isFalsey" }, @77-92 Closure([@78-79 Identifier { ident: "x" }], @83-92 Apply(@83-90 Var { module_name: "Task", ident: "ok" }, [@91-92 Var { module_name: "", ident: "x" }], Space))), AnnotatedBody { ann_pattern: @109-112 Identifier { ident: "msg" }, ann_type: @115-126 Apply("", "Task", [@120-122 Record { fields: [], ext: None }, @123-126 Apply("", "I32", [])]), comment: None, body_pattern: @143-146 Identifier { ident: "msg" }, body_expr: Apply(Var { module_name: "Task", ident: "await" }, [Var { module_name: "", ident: "isTrue" }, Closure([Identifier { ident: "#!a0" }], @143-445 If([(@173-183 Apply(@173-174 Var { module_name: "Bool", ident: "not" }, [@175-182 ParensAround(Var { module_name: "", ident: "#!a0" })], UnaryOp(Not)), @218-225 Apply(@218-225 Var { module_name: "Task", ident: "await" }, [@218-225 Apply(@218-225 Var { module_name: "", ident: "line" }, [@219-225 Str(PlainLine("fail"))], Space), @218-225 Closure([@218-225 RecordDestructure([])], @251-256 Apply(@251-254 Var { module_name: "", ident: "err" }, [@255-256 Num("1")], Space))], BangSuffix))], Apply(Var { module_name: "Task", ident: "await" }, [Apply(Var { module_name: "", ident: "isFalsey" }, [@296-306 Var { module_name: "Bool", ident: "false" }], Space), Closure([Identifier { ident: "#!a1" }], @143-445 If([(@285-307 ParensAround(Var { module_name: "", ident: "#!a1" }), @343-350 Apply(@343-350 Var { module_name: "Task", ident: "await" }, [@343-350 Apply(@343-350 Var { module_name: "", ident: "line" }, [@344-350 Str(PlainLine("nope"))], Space), @343-350 Closure([@343-350 RecordDestructure([])], @375-380 Apply(@375-377 Var { module_name: "", ident: "ok" }, [@378-380 Record([])], Space))], BangSuffix))], @430-445 Apply(@430-445 Var { module_name: "", ident: "line" }, [@436-445 Str(PlainLine("success"))], Space)))], BangSuffix)))], BangSuffix) }] }, @463-466 Var { module_name: "", ident: "msg" }))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap a trailing binops
|
||||
```roc
|
||||
copy = \a,b ->
|
||||
Task.await line "FOO" \{} ->
|
||||
CMD.new "cp"
|
||||
|> mapErr! ERR
|
||||
|
||||
copy = \a,b ->
|
||||
Task.await line "FOO" \{} ->
|
||||
Task.await (CMD.new "cp" |> mapErr ERR) \#!a0 -> #!a0
|
||||
|
||||
copy = \a,b ->
|
||||
Task.await line "FOO" \{} ->
|
||||
CMD.new "cp" |> mapErr ERR
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn trailing_binops() {
|
||||
run_test(
|
||||
r#"
|
||||
copy = \a,b ->
|
||||
line! "FOO"
|
||||
|
||||
CMD.new "cp"
|
||||
|> mapErr! ERR
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-103], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "copy" }, @7-103 Closure([@8-9 Identifier { ident: "a" }, @10-11 Identifier { ident: "b" }], @36-42 Apply(@36-42 Var { module_name: "Task", ident: "await" }, [@36-42 Apply(@36-42 Var { module_name: "", ident: "line" }, [@37-42 Str(PlainLine("FOO"))], Space), @36-42 Closure([@36-42 RecordDestructure([])], @60-103 Apply(@60-103 Var { module_name: "", ident: "mapErr" }, [@60-72 Apply(@60-67 Var { module_name: "CMD", ident: "new" }, [@68-72 Str(PlainLine("cp"))], Space), @100-103 Tag("ERR")], BinOp(Pizza)))], BangSuffix)))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap a when expression
|
||||
```roc
|
||||
list =
|
||||
when getList! is
|
||||
[] -> "empty"
|
||||
_ -> "non-empty"
|
||||
|
||||
list =
|
||||
Task.await getList \#!a0 ->
|
||||
when #!a0 is
|
||||
[] -> "empty"
|
||||
_ -> "non-empty"
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn when_simple() {
|
||||
run_test(
|
||||
r#"
|
||||
list =
|
||||
when getList! is
|
||||
[] -> "empty"
|
||||
_ -> "non-empty"
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-111], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "list" }, @0-111 Apply(@0-111 Var { module_name: "Task", ident: "await" }, [@29-37 Var { module_name: "", ident: "getList" }, @0-111 Closure([@29-37 Identifier { ident: "#!a0" }], @0-111 When(@29-37 Var { module_name: "", ident: "#!a0" }, [WhenBranch { patterns: [@61-63 List([])], value: @67-74 Str(PlainLine("empty")), guard: None }, WhenBranch { patterns: [@95-96 Underscore("")], value: @100-111 Str(PlainLine("non-empty")), guard: None }]))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap a when expression
|
||||
```roc
|
||||
list =
|
||||
when getList! is
|
||||
[] ->
|
||||
line! "foo"
|
||||
line! "bar"
|
||||
_ ->
|
||||
ok {}
|
||||
|
||||
list =
|
||||
Task.await getList \#!a0 ->
|
||||
when getList is
|
||||
[] ->
|
||||
line! "foo"
|
||||
line! "bar"
|
||||
_ ->
|
||||
ok {}
|
||||
|
||||
list =
|
||||
Task.await getList \#!a0 ->
|
||||
when getList is
|
||||
[] ->
|
||||
Task.await line "foo" \{} -> line! "bar"
|
||||
_ ->
|
||||
ok {}
|
||||
```
|
||||
*/
|
||||
#[test]
|
||||
fn when_branches() {
|
||||
run_test(
|
||||
r#"
|
||||
list =
|
||||
when getList! is
|
||||
[] ->
|
||||
line! "foo"
|
||||
line! "bar"
|
||||
_ ->
|
||||
ok {}
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-195], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "list" }, @0-195 Apply(@0-195 Var { module_name: "Task", ident: "await" }, [@29-37 Var { module_name: "", ident: "getList" }, @0-195 Closure([@29-37 Identifier { ident: "#!a0" }], @0-195 When(@29-37 Var { module_name: "", ident: "#!a0" }, [WhenBranch { patterns: [@61-63 List([])], value: @97-103 Apply(@97-103 Var { module_name: "Task", ident: "await" }, [@97-103 Apply(@97-103 Var { module_name: "", ident: "line" }, [@98-103 Str(PlainLine("foo"))], Space), @97-103 Closure([@97-103 RecordDestructure([])], @128-139 Apply(@128-139 Var { module_name: "", ident: "line" }, [@134-139 Str(PlainLine("bar"))], Space))], BangSuffix), guard: None }, WhenBranch { patterns: [@160-161 Underscore("")], value: @190-195 Apply(@190-192 Var { module_name: "", ident: "ok" }, [@193-195 Record([])], Space), guard: None }]))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trailing_suffix_inside_when() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
result = Stdin.line!
|
||||
|
||||
when result is
|
||||
End ->
|
||||
Task.ok {}
|
||||
|
||||
Input name ->
|
||||
Stdout.line! "Hello, $(name)"
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-226], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @32-43 Apply(@32-43 Var { module_name: "Task", ident: "await" }, [@32-43 Var { module_name: "Stdin", ident: "line" }, @32-43 Closure([@23-29 Identifier { ident: "result" }], @61-226 When(@66-72 Var { module_name: "", ident: "result" }, [WhenBranch { patterns: [@96-99 Tag("End")], value: @127-137 Apply(@127-134 Var { module_name: "Task", ident: "ok" }, [@135-137 Record([])], Space), guard: None }, WhenBranch { patterns: [@159-169 Apply(@159-164 Tag("Input"), [@165-169 Identifier { ident: "name" }])], value: @197-226 Apply(@197-226 Var { module_name: "Stdout", ident: "line" }, [@210-226 Str(Line([Plaintext("Hello, "), Interpolated(@220-224 Var { module_name: "", ident: "name" })]))], Space), guard: None }]))], BangSuffix))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
main =
|
||||
foo = getFoo!
|
||||
dbg foo
|
||||
bar foo
|
||||
|
||||
main =
|
||||
Task.await getFoo \foo ->
|
||||
dbg foo
|
||||
bar! foo
|
||||
|
||||
main =
|
||||
Task.await getFoo \foo ->
|
||||
dbg foo
|
||||
bar foo
|
||||
*/
|
||||
#[test]
|
||||
fn dbg_simple() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
foo = getFoo!
|
||||
dbg foo
|
||||
bar! foo
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-85], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @29-36 Apply(@29-36 Var { module_name: "Task", ident: "await" }, [@29-36 Var { module_name: "", ident: "getFoo" }, @29-36 Closure([@23-26 Identifier { ident: "foo" }], @53-85 LowLevelDbg(("test.roc:4", " "), @57-60 Apply(@57-60 Var { module_name: "Inspect", ident: "toStr" }, [@57-60 Var { module_name: "", ident: "foo" }], Space), @77-85 Apply(@77-85 Var { module_name: "", ident: "bar" }, [@82-85 Var { module_name: "", ident: "foo" }], Space)))], BangSuffix))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
// main =
|
||||
// Task.await a \#!a0 ->
|
||||
// c = b #!a0
|
||||
// Task.ok c
|
||||
#[test]
|
||||
fn apply_argument_single() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
c = b a!
|
||||
c
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-49], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @27-31 Apply(@27-31 Var { module_name: "Task", ident: "await" }, [@29-30 Var { module_name: "", ident: "a" }, @27-31 Closure([@29-30 Identifier { ident: "#!a0" }], @27-31 Defs(Defs { tags: [Index(2147483650)], regions: [@27-31], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@23-24 Identifier { ident: "c" }, @27-31 Apply(@27-28 Var { module_name: "", ident: "b" }, [@29-30 TaskAwaitBang(Var { module_name: "", ident: "a" })], Space)), Body(@23-24 Identifier { ident: "c" }, @27-31 Apply(@27-28 Var { module_name: "", ident: "b" }, [@29-30 Var { module_name: "", ident: "#!a0" }], Space)), Body(@23-24 Identifier { ident: "c" }, @27-31 Apply(@27-28 Var { module_name: "", ident: "b" }, [@29-30 Var { module_name: "", ident: "#!a0" }], Space))] }, @48-49 Var { module_name: "", ident: "c" }))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
// main =
|
||||
// Task.await a \#!a0 ->
|
||||
// Task.await x \#!a1 ->
|
||||
// c = b #!a0 #!a1
|
||||
// Task.ok c
|
||||
#[test]
|
||||
fn apply_argument_multiple() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
c = b a! x!
|
||||
c
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-52], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @27-34 Apply(@27-34 Var { module_name: "Task", ident: "await" }, [@29-30 Var { module_name: "", ident: "a" }, @27-34 Closure([@29-30 Identifier { ident: "#!a0" }], @27-34 Apply(@27-34 Var { module_name: "Task", ident: "await" }, [@32-33 Var { module_name: "", ident: "x" }, @27-34 Closure([@32-33 Identifier { ident: "#!a1" }], @27-34 Defs(Defs { tags: [Index(2147483651)], regions: [@27-34], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@23-24 Identifier { ident: "c" }, @27-34 Apply(@27-28 Var { module_name: "", ident: "b" }, [@29-30 TaskAwaitBang(Var { module_name: "", ident: "a" }), @32-33 TaskAwaitBang(Var { module_name: "", ident: "x" })], Space)), Body(@23-24 Identifier { ident: "c" }, @27-34 Apply(@27-28 Var { module_name: "", ident: "b" }, [@29-30 Var { module_name: "", ident: "#!a0" }, @32-33 TaskAwaitBang(Var { module_name: "", ident: "x" })], Space)), Body(@23-24 Identifier { ident: "c" }, @27-34 Apply(@27-28 Var { module_name: "", ident: "b" }, [@29-30 Var { module_name: "", ident: "#!a0" }, @32-33 Var { module_name: "", ident: "#!a1" }], Space)), Body(@23-24 Identifier { ident: "c" }, @27-34 Apply(@27-28 Var { module_name: "", ident: "b" }, [@29-30 Var { module_name: "", ident: "#!a0" }, @32-33 Var { module_name: "", ident: "#!a1" }], Space))] }, @51-52 Var { module_name: "", ident: "c" }))], BangSuffix))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
// main =
|
||||
// Task.await a \#!a0 ->
|
||||
// c = b #!a0
|
||||
// Task.ok c
|
||||
#[test]
|
||||
fn bang_in_pipe_root() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
c = a! |> b
|
||||
c
|
||||
"#,
|
||||
r##"Defs { tags: [Index(2147483648)], regions: [@0-52], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @27-34 Apply(@27-34 Var { module_name: "Task", ident: "await" }, [@27-28 Var { module_name: "", ident: "a" }, @27-34 Closure([@27-28 Identifier { ident: "#!a0" }], @27-34 Defs(Defs { tags: [Index(2147483650)], regions: [@27-34], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@23-24 Identifier { ident: "c" }, @27-34 Apply(@33-34 Var { module_name: "", ident: "b" }, [@27-28 TaskAwaitBang(Var { module_name: "", ident: "a" })], BinOp(Pizza))), Body(@23-24 Identifier { ident: "c" }, @27-34 Apply(@33-34 Var { module_name: "", ident: "b" }, [@27-28 Var { module_name: "", ident: "#!a0" }], BinOp(Pizza))), Body(@23-24 Identifier { ident: "c" }, @27-34 Apply(@33-34 Var { module_name: "", ident: "b" }, [@27-28 Var { module_name: "", ident: "#!a0" }], BinOp(Pizza)))] }, @51-52 Var { module_name: "", ident: "c" }))], BangSuffix))] }"##,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expect_then_bang() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
expect 1 == 2
|
||||
x!
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-55], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @0-55 Expect(@30-36 Apply(@32-34 Var { module_name: "Bool", ident: "isEq" }, [@30-31 Num("1"), @35-36 Num("2")], BinOp(Equals)), @53-55 Var { module_name: "", ident: "x" }))] }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deep_when() {
|
||||
run_test(
|
||||
r#"
|
||||
main =
|
||||
when a is
|
||||
0 ->
|
||||
when b is
|
||||
1 ->
|
||||
c!
|
||||
"#,
|
||||
r#"Defs { tags: [Index(2147483648)], regions: [@0-159], space_before: [Slice(start = 0, length = 0)], space_after: [Slice(start = 0, length = 0)], spaces: [], type_defs: [], value_defs: [Body(@0-4 Identifier { ident: "main" }, @0-159 When(@28-29 Var { module_name: "", ident: "a" }, [WhenBranch { patterns: [@53-54 NumLiteral("0")], value: @82-159 When(@87-88 Var { module_name: "", ident: "b" }, [WhenBranch { patterns: [@120-121 NumLiteral("1")], value: @157-159 Var { module_name: "", ident: "c" }, guard: None }]), guard: None }]))] }"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_suffixed_helpers {
|
||||
|
||||
use roc_can::suffixed::is_matching_intermediate_answer;
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_parse::ast::Expr;
|
||||
use roc_parse::ast::Pattern;
|
||||
use roc_region::all::Loc;
|
||||
|
||||
#[test]
|
||||
fn test_matching_answer() {
|
||||
let loc_pat = Loc::at_zero(Pattern::Identifier { ident: "#!a0" });
|
||||
let loc_new = Loc::at_zero(Expr::Var {
|
||||
module_name: "",
|
||||
ident: "#!a0",
|
||||
});
|
||||
|
||||
std::assert!(is_matching_intermediate_answer(&loc_pat, &loc_new));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matching_answer_task_ok() {
|
||||
let loc_pat = Loc::at_zero(Pattern::Identifier { ident: "#!a0" });
|
||||
let intermetiate = &[&Loc::at_zero(Expr::Var {
|
||||
module_name: "",
|
||||
ident: "#!a0",
|
||||
})];
|
||||
let task_ok = Loc::at_zero(Expr::Var {
|
||||
module_name: ModuleName::TASK,
|
||||
ident: "ok",
|
||||
});
|
||||
|
||||
let loc_new = Loc::at_zero(Expr::Apply(&task_ok, intermetiate, CalledVia::BangSuffix));
|
||||
|
||||
std::assert!(is_matching_intermediate_answer(&loc_pat, &loc_new));
|
||||
}
|
||||
}
|
6
crates/compiler/checkmate/www/package-lock.json
generated
6
crates/compiler/checkmate/www/package-lock.json
generated
|
@ -7560,9 +7560,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
|
|
|
@ -3682,7 +3682,7 @@ fn constraint_recursive_function(
|
|||
signature_closure_type,
|
||||
ret_type,
|
||||
),
|
||||
_ => todo!("TODO {:?}", (loc_symbol, &signature)),
|
||||
_ => todo!("TODO {:?}", (loc_symbol, types[signature])),
|
||||
};
|
||||
|
||||
let region = loc_function_def.region;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,16 @@
|
|||
use roc_can::{abilities::SpecializationLambdaSets, module::ExposedByModule};
|
||||
use roc_can::{
|
||||
abilities::SpecializationLambdaSets,
|
||||
expr::{Expr, WhenBranch, WhenBranchPattern},
|
||||
module::ExposedByModule,
|
||||
pattern::Pattern,
|
||||
};
|
||||
use roc_checkmate::with_checkmate;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::{IdentIds, Symbol};
|
||||
use roc_region::all::Loc;
|
||||
use roc_solve_schema::UnificationMode;
|
||||
use roc_types::{
|
||||
subs::{instantiate_rigids, Subs, Variable},
|
||||
subs::{instantiate_rigids, RedundantMark, Subs, Variable},
|
||||
types::Polarity,
|
||||
};
|
||||
|
||||
|
@ -202,3 +208,42 @@ pub(crate) enum ExtensionKind {
|
|||
Record,
|
||||
TagUnion,
|
||||
}
|
||||
|
||||
/// Ok a -> Ok a
|
||||
/// A `when ... is` branch that matches `Ok a` and returns `Ok a`
|
||||
pub(crate) fn ok_to_ok_branch(
|
||||
pattern_var: Variable,
|
||||
result_var: Variable,
|
||||
field_var: Variable,
|
||||
symbol: &Symbol,
|
||||
env: &mut Env<'_>,
|
||||
) -> WhenBranch {
|
||||
WhenBranch {
|
||||
patterns: vec![WhenBranchPattern {
|
||||
pattern: Loc::at_zero(Pattern::AppliedTag {
|
||||
whole_var: pattern_var,
|
||||
ext_var: Variable::EMPTY_TAG_UNION,
|
||||
tag_name: "Ok".into(),
|
||||
arguments: vec![(field_var, Loc::at_zero(Pattern::Identifier(*symbol)))],
|
||||
}),
|
||||
degenerate: false,
|
||||
}],
|
||||
value: Loc::at_zero(Expr::Tag {
|
||||
tag_union_var: result_var,
|
||||
ext_var: env.new_ext_var(ExtensionKind::TagUnion),
|
||||
name: "Ok".into(),
|
||||
arguments: vec![(field_var, Loc::at_zero(Expr::Var(*symbol, field_var)))],
|
||||
}),
|
||||
guard: None,
|
||||
redundant: RedundantMark::known_non_redundant(),
|
||||
}
|
||||
}
|
||||
|
||||
/// `[]`
|
||||
/// Creates an empty list of the type provided.
|
||||
pub(crate) fn empty_list(var: Variable) -> Expr {
|
||||
Expr::List {
|
||||
elem_var: var,
|
||||
loc_elems: vec![],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,5 +12,6 @@ roc_collections = { path = "../collections" }
|
|||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
|
||||
bumpalo.workspace = true
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
use crate::annotation::{Formattable, Newlines, Parens};
|
||||
use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens};
|
||||
use crate::collection::{fmt_collection, Braces};
|
||||
use crate::expr::fmt_str_literal;
|
||||
use crate::pattern::fmt_pattern;
|
||||
use crate::spaces::{fmt_default_newline, fmt_spaces, INDENT};
|
||||
use crate::spaces::{fmt_default_newline, fmt_default_spaces, fmt_spaces, INDENT};
|
||||
use crate::Buf;
|
||||
use roc_parse::ast::{
|
||||
AbilityMember, Defs, Expr, ExtractSpaces, Pattern, Spaces, StrLiteral, TypeAnnotation, TypeDef,
|
||||
TypeHeader, ValueDef,
|
||||
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
|
||||
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
|
||||
ModuleImportParams, Pattern, Spaces, StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
};
|
||||
use roc_parse::header::Keyword;
|
||||
use roc_region::all::Loc;
|
||||
|
||||
/// A Located formattable value is also formattable
|
||||
|
@ -183,6 +187,226 @@ impl<'a> Formattable for TypeHeader<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for ModuleImport<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
let Self {
|
||||
before_name,
|
||||
name,
|
||||
params,
|
||||
alias,
|
||||
exposed,
|
||||
} = self;
|
||||
|
||||
!before_name.is_empty()
|
||||
|| name.is_multiline()
|
||||
|| params.is_multiline()
|
||||
|| alias.is_multiline()
|
||||
|| match exposed {
|
||||
Some(a) => a.keyword.is_multiline() || is_collection_multiline(&a.item),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_options(
|
||||
&self,
|
||||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
let Self {
|
||||
before_name,
|
||||
name,
|
||||
params,
|
||||
alias,
|
||||
exposed,
|
||||
} = self;
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push_str("import");
|
||||
|
||||
let indent = if !before_name.is_empty()
|
||||
|| (params.is_multiline() && exposed.is_some())
|
||||
|| alias.is_multiline()
|
||||
|| exposed.map_or(false, |e| e.keyword.is_multiline())
|
||||
{
|
||||
indent + INDENT
|
||||
} else {
|
||||
indent
|
||||
};
|
||||
|
||||
fmt_default_spaces(buf, before_name, indent);
|
||||
|
||||
name.format(buf, indent);
|
||||
params.format(buf, indent);
|
||||
alias.format(buf, indent);
|
||||
|
||||
if let Some(exposed) = exposed {
|
||||
exposed.keyword.format(buf, indent);
|
||||
fmt_collection(buf, indent, Braces::Square, exposed.item, Newlines::No);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for ModuleImportParams<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
let ModuleImportParams { before, params } = self;
|
||||
|
||||
!before.is_empty() || is_collection_multiline(params)
|
||||
}
|
||||
|
||||
fn format_with_options(&self, buf: &mut Buf, _parens: Parens, newlines: Newlines, indent: u16) {
|
||||
let ModuleImportParams { before, params } = self;
|
||||
|
||||
fmt_default_spaces(buf, before, indent);
|
||||
fmt_collection(buf, indent, Braces::Curly, *params, newlines);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for IngestedFileImport<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
let Self {
|
||||
before_path,
|
||||
path: _,
|
||||
name,
|
||||
annotation,
|
||||
} = self;
|
||||
!before_path.is_empty() || name.keyword.is_multiline() || annotation.is_multiline()
|
||||
}
|
||||
|
||||
fn format_with_options(
|
||||
&self,
|
||||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
let Self {
|
||||
before_path,
|
||||
path,
|
||||
name,
|
||||
annotation,
|
||||
} = self;
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push_str("import");
|
||||
|
||||
let indent = indent + INDENT;
|
||||
|
||||
fmt_default_spaces(buf, before_path, indent);
|
||||
fmt_str_literal(buf, path.value, indent);
|
||||
|
||||
name.keyword.format(buf, indent);
|
||||
buf.push_str(name.item.value);
|
||||
|
||||
annotation.format(buf, indent);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for ImportedModuleName<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
// No newlines in module name itself.
|
||||
false
|
||||
}
|
||||
|
||||
fn format_with_options(
|
||||
&self,
|
||||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.indent(indent);
|
||||
|
||||
if let Some(package_shorthand) = self.package {
|
||||
buf.push_str(package_shorthand);
|
||||
buf.push_str(".");
|
||||
}
|
||||
|
||||
self.name.format(buf, indent);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for ImportAlias<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
// No newlines in alias itself.
|
||||
false
|
||||
}
|
||||
|
||||
fn format_with_options(
|
||||
&self,
|
||||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.indent(indent);
|
||||
buf.push_str(self.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
impl Formattable for ImportAsKeyword {
|
||||
fn is_multiline(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn format_with_options(
|
||||
&self,
|
||||
buf: &mut Buf<'_>,
|
||||
_parens: crate::annotation::Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.indent(indent);
|
||||
buf.push_str(ImportAsKeyword::KEYWORD);
|
||||
}
|
||||
}
|
||||
|
||||
impl Formattable for ImportExposingKeyword {
|
||||
fn is_multiline(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn format_with_options(
|
||||
&self,
|
||||
buf: &mut Buf<'_>,
|
||||
_parens: crate::annotation::Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.indent(indent);
|
||||
buf.push_str(ImportExposingKeyword::KEYWORD);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for IngestedFileAnnotation<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
let Self {
|
||||
before_colon,
|
||||
annotation,
|
||||
} = self;
|
||||
!before_colon.is_empty() || annotation.is_multiline()
|
||||
}
|
||||
|
||||
fn format_with_options(
|
||||
&self,
|
||||
buf: &mut Buf,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
let Self {
|
||||
before_colon,
|
||||
annotation,
|
||||
} = self;
|
||||
|
||||
fmt_default_spaces(buf, before_colon, indent);
|
||||
buf.push_str(":");
|
||||
buf.spaces(1);
|
||||
annotation.format(buf, indent);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for ValueDef<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
use roc_parse::ast::ValueDef::*;
|
||||
|
@ -196,10 +420,13 @@ impl<'a> Formattable for ValueDef<'a> {
|
|||
Expect { condition, .. } => condition.is_multiline(),
|
||||
ExpectFx { condition, .. } => condition.is_multiline(),
|
||||
Dbg { condition, .. } => condition.is_multiline(),
|
||||
ModuleImport(module_import) => module_import.is_multiline(),
|
||||
IngestedFileImport(ingested_file_import) => ingested_file_import.is_multiline(),
|
||||
Stmt(loc_expr) => loc_expr.is_multiline(),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_options(&self, buf: &mut Buf, _parens: Parens, newlines: Newlines, indent: u16) {
|
||||
fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) {
|
||||
use roc_parse::ast::ValueDef::*;
|
||||
match self {
|
||||
Annotation(loc_pattern, loc_annotation) => {
|
||||
|
@ -238,6 +465,9 @@ impl<'a> Formattable for ValueDef<'a> {
|
|||
buf.newline();
|
||||
fmt_body(buf, &body_pattern.value, &body_expr.value, indent);
|
||||
}
|
||||
ModuleImport(module_import) => module_import.format(buf, indent),
|
||||
IngestedFileImport(ingested_file_import) => ingested_file_import.format(buf, indent),
|
||||
Stmt(loc_expr) => loc_expr.format_with_options(buf, parens, newlines, indent),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -357,9 +587,19 @@ pub fn fmt_defs(buf: &mut Buf, defs: &Defs, indent: u16) {
|
|||
}
|
||||
|
||||
pub fn fmt_body<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, body: &'a Expr<'a>, indent: u16) {
|
||||
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str(" =");
|
||||
// Check if this is an assignment into the unit value
|
||||
let is_unit_assignment = if let Pattern::RecordDestructure(collection) = pattern {
|
||||
collection.is_empty()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Don't format the `{} =` for defs with this pattern
|
||||
if !is_unit_assignment {
|
||||
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str(" =");
|
||||
}
|
||||
|
||||
if body.is_multiline() {
|
||||
match body {
|
||||
|
|
|
@ -9,8 +9,8 @@ use crate::spaces::{
|
|||
use crate::Buf;
|
||||
use roc_module::called_via::{self, BinOp};
|
||||
use roc_parse::ast::{
|
||||
AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern,
|
||||
RecordBuilderField, WhenBranch,
|
||||
is_expr_suffixed, AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces,
|
||||
Pattern, RecordBuilderField, WhenBranch,
|
||||
};
|
||||
use roc_parse::ast::{StrLiteral, StrSegment};
|
||||
use roc_parse::ident::Accessor;
|
||||
|
@ -31,23 +31,27 @@ impl<'a> Formattable for Expr<'a> {
|
|||
true
|
||||
}
|
||||
|
||||
MalformedSuffixed(loc_expr) => loc_expr.is_multiline(),
|
||||
|
||||
// These expressions never have newlines
|
||||
Float(..)
|
||||
| Num(..)
|
||||
| NonBase10Int { .. }
|
||||
| SingleQuote(_)
|
||||
| RecordAccess(_, _)
|
||||
| AccessorFunction(_)
|
||||
| TupleAccess(_, _)
|
||||
| Var { .. }
|
||||
| Underscore { .. }
|
||||
| MalformedIdent(_, _)
|
||||
| MalformedClosure
|
||||
| Tag(_)
|
||||
| OpaqueRef(_)
|
||||
| IngestedFile(_, _)
|
||||
| EmptyDefsFinal
|
||||
| Crash => false,
|
||||
|
||||
RecordAccess(inner, _) | TupleAccess(inner, _) | TaskAwaitBang(inner) => {
|
||||
inner.is_multiline()
|
||||
}
|
||||
|
||||
// These expressions always have newlines
|
||||
Defs(_, _) | When(_, _) => true,
|
||||
|
||||
|
@ -107,7 +111,6 @@ impl<'a> Formattable for Expr<'a> {
|
|||
Tuple(fields) => is_collection_multiline(fields),
|
||||
RecordUpdate { fields, .. } => is_collection_multiline(fields),
|
||||
RecordBuilder(fields) => is_collection_multiline(fields),
|
||||
Suffixed(subexpr) => subexpr.is_multiline(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,6 +420,9 @@ impl<'a> Formattable for Expr<'a> {
|
|||
indent,
|
||||
);
|
||||
}
|
||||
EmptyDefsFinal => {
|
||||
// no need to print anything
|
||||
}
|
||||
_ => {
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(indent);
|
||||
|
@ -433,6 +439,9 @@ impl<'a> Formattable for Expr<'a> {
|
|||
buf.push(')');
|
||||
}
|
||||
}
|
||||
EmptyDefsFinal => {
|
||||
// no need to print anything
|
||||
}
|
||||
Expect(condition, continuation) => {
|
||||
fmt_expect(buf, condition, continuation, self.is_multiline(), indent);
|
||||
}
|
||||
|
@ -504,19 +513,22 @@ impl<'a> Formattable for Expr<'a> {
|
|||
buf.push('.');
|
||||
buf.push_str(key);
|
||||
}
|
||||
TaskAwaitBang(expr) => {
|
||||
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
|
||||
buf.push('!');
|
||||
}
|
||||
MalformedIdent(str, _) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(str)
|
||||
}
|
||||
MalformedSuffixed(loc_expr) => {
|
||||
buf.indent(indent);
|
||||
loc_expr.format_with_options(buf, parens, newlines, indent);
|
||||
}
|
||||
MalformedClosure => {}
|
||||
PrecedenceConflict { .. } => {}
|
||||
MultipleRecordBuilders { .. } => {}
|
||||
UnappliedRecordBuilder { .. } => {}
|
||||
IngestedFile(_, _) => {}
|
||||
Suffixed(sub_expr) => {
|
||||
sub_expr.format_with_options(buf, parens, newlines, indent);
|
||||
buf.push('!');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -726,14 +738,32 @@ fn fmt_binops<'a>(
|
|||
|| loc_right_side.value.is_multiline()
|
||||
|| lefts.iter().any(|(expr, _)| expr.value.is_multiline());
|
||||
|
||||
let is_any_lefts_suffixed = lefts.iter().any(|(left, _)| is_expr_suffixed(&left.value));
|
||||
let is_right_suffixed = is_expr_suffixed(&loc_right_side.value);
|
||||
let is_any_suffixed = is_any_lefts_suffixed || is_right_suffixed;
|
||||
|
||||
let mut is_first = false;
|
||||
let mut adjusted_indent = indent;
|
||||
|
||||
if is_any_suffixed {
|
||||
// we only want to indent the remaining lines if this is a suffixed expression.
|
||||
is_first = true;
|
||||
}
|
||||
|
||||
for (loc_left_side, loc_binop) in lefts {
|
||||
let binop = loc_binop.value;
|
||||
|
||||
loc_left_side.format_with_options(buf, Parens::InOperator, Newlines::No, indent);
|
||||
loc_left_side.format_with_options(buf, Parens::InOperator, Newlines::No, adjusted_indent);
|
||||
|
||||
if is_first {
|
||||
// indent the remaining lines, but only if the expression is suffixed.
|
||||
is_first = false;
|
||||
adjusted_indent = indent + 4;
|
||||
}
|
||||
|
||||
if is_multiline {
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(indent);
|
||||
buf.indent(adjusted_indent);
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
@ -743,7 +773,7 @@ fn fmt_binops<'a>(
|
|||
buf.spaces(1);
|
||||
}
|
||||
|
||||
loc_right_side.format_with_options(buf, Parens::InOperator, Newlines::Yes, indent);
|
||||
loc_right_side.format_with_options(buf, Parens::InOperator, Newlines::Yes, adjusted_indent);
|
||||
}
|
||||
|
||||
fn format_spaces(buf: &mut Buf, spaces: &[CommentOrNewline], newlines: Newlines, indent: u16) {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::cmp::max;
|
||||
|
||||
use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens};
|
||||
use crate::collection::{fmt_collection, Braces};
|
||||
use crate::expr::fmt_str_literal;
|
||||
|
@ -5,12 +7,13 @@ use crate::spaces::RemoveSpaces;
|
|||
use crate::spaces::{fmt_comments_only, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT};
|
||||
use crate::Buf;
|
||||
use bumpalo::Bump;
|
||||
use roc_parse::ast::{Collection, Header, Module, Spaced, Spaces};
|
||||
use roc_parse::ast::{Collection, CommentOrNewline, Header, Module, Spaced, Spaces};
|
||||
use roc_parse::header::{
|
||||
AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, HostedHeader, ImportsEntry,
|
||||
ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName, PackageEntry, PackageHeader,
|
||||
PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, PlatformRequires,
|
||||
ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
|
||||
ImportsKeyword, Keyword, KeywordItem, ModuleHeader, ModuleName, PackageEntry, PackageHeader,
|
||||
PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, PlatformKeyword,
|
||||
PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent,
|
||||
WithKeyword,
|
||||
};
|
||||
use roc_parse::ident::UppercaseIdent;
|
||||
use roc_region::all::Loc;
|
||||
|
@ -18,8 +21,8 @@ use roc_region::all::Loc;
|
|||
pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) {
|
||||
fmt_comments_only(buf, module.comments.iter(), NewlineAt::Bottom, 0);
|
||||
match &module.header {
|
||||
Header::Interface(header) => {
|
||||
fmt_interface_header(buf, header);
|
||||
Header::Module(header) => {
|
||||
fmt_module_header(buf, header);
|
||||
}
|
||||
Header::App(header) => {
|
||||
fmt_app_header(buf, header);
|
||||
|
@ -75,6 +78,7 @@ keywords! {
|
|||
RequiresKeyword,
|
||||
ProvidesKeyword,
|
||||
ToKeyword,
|
||||
PlatformKeyword,
|
||||
}
|
||||
|
||||
impl<V: Formattable> Formattable for Option<V> {
|
||||
|
@ -171,20 +175,25 @@ impl<'a, K: Formattable, V: Formattable> Formattable for KeywordItem<'a, K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn fmt_interface_header<'a>(buf: &mut Buf, header: &'a InterfaceHeader<'a>) {
|
||||
pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) {
|
||||
buf.indent(0);
|
||||
buf.push_str("interface");
|
||||
let indent = INDENT;
|
||||
fmt_default_spaces(buf, header.before_name, indent);
|
||||
buf.push_str("module");
|
||||
|
||||
// module name
|
||||
buf.indent(indent);
|
||||
buf.push_str(header.name.value.as_str());
|
||||
let mut indent = fmt_spaces_with_outdent(buf, header.after_keyword, 0);
|
||||
|
||||
header.exposes.keyword.format(buf, indent);
|
||||
fmt_exposes(buf, header.exposes.item, indent);
|
||||
header.imports.keyword.format(buf, indent);
|
||||
fmt_imports(buf, header.imports.item, indent);
|
||||
if let Some(params) = &header.params {
|
||||
if is_collection_multiline(¶ms.params) {
|
||||
indent = INDENT;
|
||||
}
|
||||
|
||||
fmt_collection(buf, indent, Braces::Curly, params.params, Newlines::Yes);
|
||||
|
||||
indent = fmt_spaces_with_outdent(buf, params.before_arrow, indent);
|
||||
buf.push_str("->");
|
||||
indent = fmt_spaces_with_outdent(buf, params.after_arrow, indent);
|
||||
}
|
||||
|
||||
fmt_exposes(buf, header.exposes, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) {
|
||||
|
@ -207,34 +216,34 @@ pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) {
|
|||
pub fn fmt_app_header<'a>(buf: &mut Buf, header: &'a AppHeader<'a>) {
|
||||
buf.indent(0);
|
||||
buf.push_str("app");
|
||||
let indent = INDENT;
|
||||
fmt_default_spaces(buf, header.before_name, indent);
|
||||
|
||||
fmt_str_literal(buf, header.name.value, indent);
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_provides, 0);
|
||||
fmt_exposes(buf, header.provides, indent);
|
||||
|
||||
if let Some(packages) = &header.packages {
|
||||
packages.keyword.format(buf, indent);
|
||||
fmt_packages(buf, packages.item, indent);
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_packages, indent);
|
||||
fmt_packages(buf, header.packages.value, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_spaces_with_outdent(buf: &mut Buf, spaces: &[CommentOrNewline], indent: u16) -> u16 {
|
||||
if spaces.iter().all(|c| c.is_newline()) {
|
||||
buf.spaces(1);
|
||||
indent
|
||||
} else {
|
||||
let indent = max(INDENT, indent + INDENT);
|
||||
fmt_default_spaces(buf, spaces, indent);
|
||||
indent
|
||||
}
|
||||
if let Some(imports) = &header.imports {
|
||||
imports.keyword.format(buf, indent);
|
||||
fmt_imports(buf, imports.item, indent);
|
||||
}
|
||||
header.provides.format(buf, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_package_header<'a>(buf: &mut Buf, header: &'a PackageHeader<'a>) {
|
||||
buf.indent(0);
|
||||
buf.push_str("package");
|
||||
let indent = INDENT;
|
||||
fmt_default_spaces(buf, header.before_name, indent);
|
||||
|
||||
fmt_package_name(buf, header.name.value, indent);
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, 0);
|
||||
fmt_exposes(buf, header.exposes, indent);
|
||||
|
||||
header.exposes.keyword.format(buf, indent);
|
||||
fmt_exposes(buf, header.exposes.item, indent);
|
||||
header.packages.keyword.format(buf, indent);
|
||||
fmt_packages(buf, header.packages.item, indent);
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_packages, indent);
|
||||
fmt_packages(buf, header.packages.value, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_platform_header<'a>(buf: &mut Buf, header: &'a PlatformHeader<'a>) {
|
||||
|
@ -465,6 +474,15 @@ fn fmt_packages_entry(buf: &mut Buf, entry: &PackageEntry, indent: u16) {
|
|||
buf.push_str(entry.shorthand);
|
||||
buf.push(':');
|
||||
fmt_default_spaces(buf, entry.spaces_after_shorthand, indent);
|
||||
|
||||
let indent = indent + INDENT;
|
||||
|
||||
if let Some(spaces_after) = entry.platform_marker {
|
||||
buf.indent(indent);
|
||||
buf.push_str(roc_parse::keyword::PLATFORM);
|
||||
fmt_default_spaces(buf, spaces_after, indent);
|
||||
}
|
||||
|
||||
fmt_package_name(buf, entry.package_name.value, indent);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ impl<'a> Formattable for Pattern<'a> {
|
|||
}
|
||||
},
|
||||
|
||||
Pattern::Identifier(_)
|
||||
Pattern::Identifier { .. }
|
||||
| Pattern::Tag(_)
|
||||
| Pattern::OpaqueRef(_)
|
||||
| Pattern::Apply(_, _)
|
||||
|
@ -88,9 +88,9 @@ impl<'a> Formattable for Pattern<'a> {
|
|||
use self::Pattern::*;
|
||||
|
||||
match self {
|
||||
Identifier(string) => {
|
||||
Identifier { ident: string } => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string)
|
||||
buf.push_str(string);
|
||||
}
|
||||
Tag(name) | OpaqueRef(name) => {
|
||||
buf.indent(indent);
|
||||
|
|
|
@ -4,13 +4,15 @@ use roc_module::called_via::{BinOp, UnaryOp};
|
|||
use roc_parse::{
|
||||
ast::{
|
||||
AbilityImpls, AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr,
|
||||
Header, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, Module,
|
||||
Pattern, PatternAs, RecordBuilderField, Spaced, Spaces, StrLiteral, StrSegment, Tag,
|
||||
TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
|
||||
Header, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias,
|
||||
ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation,
|
||||
IngestedFileImport, Module, ModuleImport, ModuleImportParams, Pattern, PatternAs,
|
||||
RecordBuilderField, Spaced, Spaces, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
|
||||
TypeHeader, ValueDef, WhenBranch,
|
||||
},
|
||||
header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, KeywordItem,
|
||||
ModuleName, PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires,
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, KeywordItem, ModuleHeader, ModuleName,
|
||||
ModuleParams, PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires,
|
||||
ProvidesTo, To, TypedIdent,
|
||||
},
|
||||
ident::{BadIdent, UppercaseIdent},
|
||||
|
@ -282,23 +284,26 @@ impl<'a> RemoveSpaces<'a> for ProvidesTo<'a> {
|
|||
impl<'a> RemoveSpaces<'a> for Module<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let header = match &self.header {
|
||||
Header::Interface(header) => Header::Interface(InterfaceHeader {
|
||||
before_name: &[],
|
||||
name: header.name.remove_spaces(arena),
|
||||
Header::Module(header) => Header::Module(ModuleHeader {
|
||||
after_keyword: &[],
|
||||
params: header.params.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
interface_imports: header.interface_imports.remove_spaces(arena),
|
||||
}),
|
||||
Header::App(header) => Header::App(AppHeader {
|
||||
before_name: &[],
|
||||
name: header.name.remove_spaces(arena),
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
before_provides: &[],
|
||||
provides: header.provides.remove_spaces(arena),
|
||||
before_packages: &[],
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
old_imports: header.old_imports.remove_spaces(arena),
|
||||
old_provides_to_new_package: header
|
||||
.old_provides_to_new_package
|
||||
.remove_spaces(arena),
|
||||
}),
|
||||
Header::Package(header) => Header::Package(PackageHeader {
|
||||
before_name: &[],
|
||||
name: header.name.remove_spaces(arena),
|
||||
before_exposes: &[],
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
before_packages: &[],
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
}),
|
||||
Header::Platform(header) => Header::Platform(PlatformHeader {
|
||||
|
@ -326,6 +331,16 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ModuleParams<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
ModuleParams {
|
||||
params: self.params.remove_spaces(arena),
|
||||
before_arrow: &[],
|
||||
after_arrow: &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Region {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
Region::zero()
|
||||
|
@ -405,6 +420,10 @@ impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
|
|||
PackageEntry {
|
||||
shorthand: self.shorthand,
|
||||
spaces_after_shorthand: &[],
|
||||
platform_marker: match self.platform_marker {
|
||||
Some(_) => Some(&[]),
|
||||
None => None,
|
||||
},
|
||||
package_name: self.package_name.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
|
@ -567,6 +586,79 @@ impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
|
|||
condition: arena.alloc(condition.remove_spaces(arena)),
|
||||
preceding_comment: Region::zero(),
|
||||
},
|
||||
ModuleImport(module_import) => ModuleImport(module_import.remove_spaces(arena)),
|
||||
IngestedFileImport(ingested_file_import) => {
|
||||
IngestedFileImport(ingested_file_import.remove_spaces(arena))
|
||||
}
|
||||
Stmt(loc_expr) => Stmt(arena.alloc(loc_expr.remove_spaces(arena))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ModuleImport<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
ModuleImport {
|
||||
before_name: &[],
|
||||
name: self.name.remove_spaces(arena),
|
||||
params: self.params.remove_spaces(arena),
|
||||
alias: self.alias.remove_spaces(arena),
|
||||
exposed: self.exposed.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ModuleImportParams<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
ModuleImportParams {
|
||||
before: &[],
|
||||
params: self.params.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for IngestedFileImport<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
IngestedFileImport {
|
||||
before_path: &[],
|
||||
path: self.path.remove_spaces(arena),
|
||||
name: self.name.remove_spaces(arena),
|
||||
annotation: self.annotation.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ImportedModuleName<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
ImportedModuleName {
|
||||
package: self.package.remove_spaces(arena),
|
||||
name: self.name.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ImportAlias<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ImportAsKeyword {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ImportExposingKeyword {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for IngestedFileAnnotation<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
IngestedFileAnnotation {
|
||||
before_colon: &[],
|
||||
annotation: self.annotation.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -668,6 +760,7 @@ impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
|
|||
impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Expr::EmptyDefsFinal => Expr::EmptyDefsFinal,
|
||||
Expr::Float(a) => Expr::Float(a),
|
||||
Expr::Num(a) => Expr::Num(a),
|
||||
Expr::NonBase10Int {
|
||||
|
@ -680,10 +773,10 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
|||
is_negative,
|
||||
},
|
||||
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
|
||||
Expr::IngestedFile(a, b) => Expr::IngestedFile(a, b),
|
||||
Expr::RecordAccess(a, b) => Expr::RecordAccess(arena.alloc(a.remove_spaces(arena)), b),
|
||||
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
|
||||
Expr::TupleAccess(a, b) => Expr::TupleAccess(arena.alloc(a.remove_spaces(arena)), b),
|
||||
Expr::TaskAwaitBang(a) => Expr::TaskAwaitBang(arena.alloc(a.remove_spaces(arena))),
|
||||
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
|
||||
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
|
||||
update: arena.alloc(update.remove_spaces(arena)),
|
||||
|
@ -755,13 +848,13 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
|||
}
|
||||
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, remove_spaces_bad_ident(b)),
|
||||
Expr::MalformedClosure => Expr::MalformedClosure,
|
||||
Expr::MalformedSuffixed(a) => Expr::MalformedSuffixed(a),
|
||||
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
|
||||
Expr::MultipleRecordBuilders(a) => Expr::MultipleRecordBuilders(a),
|
||||
Expr::UnappliedRecordBuilder(a) => Expr::UnappliedRecordBuilder(a),
|
||||
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Expr::SingleQuote(a) => Expr::Num(a),
|
||||
Expr::Suffixed(a) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -792,7 +885,7 @@ fn remove_spaces_bad_ident(ident: BadIdent) -> BadIdent {
|
|||
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Pattern::Identifier(a) => Pattern::Identifier(a),
|
||||
Pattern::Identifier { ident } => Pattern::Identifier { ident },
|
||||
Pattern::Tag(a) => Pattern::Tag(a),
|
||||
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
|
||||
Pattern::Apply(a, b) => Pattern::Apply(
|
||||
|
|
|
@ -1704,6 +1704,13 @@ trait Backend<'a> {
|
|||
|
||||
self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
|
||||
}
|
||||
LowLevel::ListConcatUtf8 => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::LIST_CONCAT_UTF8.to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
LowLevel::PtrCast => {
|
||||
debug_assert_eq!(
|
||||
1,
|
||||
|
|
|
@ -1124,7 +1124,11 @@ pub fn construct_optimization_passes<'a>(
|
|||
}
|
||||
OptLevel::Size => {
|
||||
pmb.set_optimization_level(OptimizationLevel::Default);
|
||||
// 2 is equivalent to `-Oz`.
|
||||
pmb.set_size_level(2);
|
||||
|
||||
// TODO: For some usecase, like embedded, it is useful to expose this and tune it.
|
||||
// This really depends on if inlining causes enough simplifications to reduce code size.
|
||||
pmb.set_inliner_with_threshold(50);
|
||||
}
|
||||
OptLevel::Optimize => {
|
||||
|
@ -1134,9 +1138,10 @@ pub fn construct_optimization_passes<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// Add optimization passes for Size and Optimize.
|
||||
if matches!(opt_level, OptLevel::Size | OptLevel::Optimize) {
|
||||
// TODO figure out which of these actually help
|
||||
// Add extra optimization passes for Optimize.
|
||||
if matches!(opt_level, OptLevel::Optimize) {
|
||||
// TODO: figure out which of these actually help.
|
||||
// Note, llvm probably already runs all of these as part of Aggressive.
|
||||
|
||||
// function passes
|
||||
|
||||
|
|
|
@ -845,6 +845,27 @@ pub(crate) fn run_low_level<'a, 'ctx>(
|
|||
}
|
||||
}
|
||||
}
|
||||
ListConcatUtf8 => {
|
||||
// List.concatUtf8: List U8, Str -> List U8
|
||||
arguments!(list, string);
|
||||
|
||||
match env.target.ptr_width() {
|
||||
PtrWidth::Bytes4 => call_str_bitcode_fn(
|
||||
env,
|
||||
&[list, string],
|
||||
&[],
|
||||
BitcodeReturns::List,
|
||||
bitcode::LIST_CONCAT_UTF8,
|
||||
),
|
||||
PtrWidth::Bytes8 => call_list_bitcode_fn(
|
||||
env,
|
||||
&[list.into_struct_value()],
|
||||
&[string],
|
||||
BitcodeReturns::List,
|
||||
bitcode::LIST_CONCAT_UTF8,
|
||||
),
|
||||
}
|
||||
}
|
||||
NumToStr => {
|
||||
// Num.toStr : Num a -> Str
|
||||
arguments_with_layouts!((num, num_layout));
|
||||
|
|
|
@ -481,6 +481,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
|
||||
backend.call_host_fn_after_loading_args(bitcode::LIST_CONCAT);
|
||||
}
|
||||
ListConcatUtf8 => self.load_args_and_call_zig(backend, bitcode::LIST_CONCAT_UTF8),
|
||||
|
||||
ListReserve => {
|
||||
// List.reserve : List elem, U64 -> List elem
|
||||
|
|
|
@ -40,7 +40,7 @@ roc_problem = { path = "../problem" }
|
|||
roc_region = { path = "../region" }
|
||||
roc_solve_problem = { path = "../solve_problem" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
roc_test_utils = { path = "../../test_utils" }
|
||||
roc_test_utils_dir = { path = "../../test_utils_dir" }
|
||||
|
||||
indoc.workspace = true
|
||||
insta.workspace = true
|
||||
|
|
|
@ -76,6 +76,7 @@ fn write_types_for_module_real(module_id: ModuleId, filename: &str, output_path:
|
|||
PathBuf::from(filename),
|
||||
source,
|
||||
cwd,
|
||||
None,
|
||||
Default::default(),
|
||||
target,
|
||||
function_kind,
|
||||
|
|
|
@ -104,12 +104,14 @@ pub fn load_and_monomorphize_from_str<'a>(
|
|||
filename: PathBuf,
|
||||
src: &'a str,
|
||||
src_dir: PathBuf,
|
||||
opt_main_path: Option<PathBuf>,
|
||||
roc_cache_dir: RocCacheDir<'_>,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<MonomorphizedModule<'a>, LoadMonomorphizedError<'a>> {
|
||||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_str(arena, filename, src, roc_cache_dir, src_dir)?;
|
||||
let load_start =
|
||||
LoadStart::from_str(arena, filename, opt_main_path, src, roc_cache_dir, src_dir)?;
|
||||
let exposed_types = ExposedByModule::default();
|
||||
|
||||
match load(arena, load_start, exposed_types, roc_cache_dir, load_config)? {
|
||||
|
@ -121,6 +123,7 @@ pub fn load_and_monomorphize_from_str<'a>(
|
|||
pub fn load_and_monomorphize<'a>(
|
||||
arena: &'a Bump,
|
||||
filename: PathBuf,
|
||||
opt_main_path: Option<PathBuf>,
|
||||
roc_cache_dir: RocCacheDir<'_>,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<MonomorphizedModule<'a>, LoadMonomorphizedError<'a>> {
|
||||
|
@ -129,6 +132,7 @@ pub fn load_and_monomorphize<'a>(
|
|||
let load_start = LoadStart::from_path(
|
||||
arena,
|
||||
filename,
|
||||
opt_main_path,
|
||||
load_config.render,
|
||||
roc_cache_dir,
|
||||
load_config.palette,
|
||||
|
@ -145,6 +149,7 @@ pub fn load_and_monomorphize<'a>(
|
|||
pub fn load_and_typecheck<'a>(
|
||||
arena: &'a Bump,
|
||||
filename: PathBuf,
|
||||
opt_main_path: Option<PathBuf>,
|
||||
roc_cache_dir: RocCacheDir<'_>,
|
||||
load_config: LoadConfig,
|
||||
) -> Result<LoadedModule, LoadingProblem<'a>> {
|
||||
|
@ -153,6 +158,7 @@ pub fn load_and_typecheck<'a>(
|
|||
let load_start = LoadStart::from_path(
|
||||
arena,
|
||||
filename,
|
||||
opt_main_path,
|
||||
load_config.render,
|
||||
roc_cache_dir,
|
||||
load_config.palette,
|
||||
|
@ -172,6 +178,7 @@ pub fn load_and_typecheck_str<'a>(
|
|||
filename: PathBuf,
|
||||
source: &'a str,
|
||||
src_dir: PathBuf,
|
||||
opt_main_path: Option<PathBuf>,
|
||||
target: Target,
|
||||
function_kind: FunctionKind,
|
||||
render: RenderTarget,
|
||||
|
@ -180,7 +187,14 @@ pub fn load_and_typecheck_str<'a>(
|
|||
) -> Result<LoadedModule, LoadingProblem<'a>> {
|
||||
use LoadResult::*;
|
||||
|
||||
let load_start = LoadStart::from_str(arena, filename, source, roc_cache_dir, src_dir)?;
|
||||
let load_start = LoadStart::from_str(
|
||||
arena,
|
||||
filename,
|
||||
opt_main_path,
|
||||
source,
|
||||
roc_cache_dir,
|
||||
src_dir,
|
||||
)?;
|
||||
|
||||
// NOTE: this function is meant for tests, and so we use single-threaded
|
||||
// solving so we don't use too many threads per-test. That gives higher
|
||||
|
|
|
@ -11,7 +11,7 @@ use roc_can::scope::Scope;
|
|||
use roc_collections::all::{ImMap, MutMap, SendSet};
|
||||
use roc_constrain::expr::constrain_expr;
|
||||
use roc_derive::SharedDerivedModule;
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds};
|
||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds};
|
||||
use roc_parse::parser::{SourceError, SyntaxError};
|
||||
use roc_problem::can::Problem;
|
||||
use roc_region::all::Loc;
|
||||
|
@ -154,10 +154,10 @@ pub fn can_expr_with<'a>(
|
|||
let var = var_store.fresh();
|
||||
let var_index = constraints.push_variable(var);
|
||||
let expected = constraints.push_expected_type(Expected::NoExpectation(var_index));
|
||||
let mut module_ids = ModuleIds::default();
|
||||
let mut module_ids = PackageModuleIds::default();
|
||||
|
||||
// ensure the Test module is accessible in our tests
|
||||
module_ids.get_or_insert(&"Test".into());
|
||||
module_ids.get_or_insert(&PQModuleName::Unqualified("Test".into()));
|
||||
|
||||
// Desugar operators (convert them to Apply calls, taking into account
|
||||
// operator precedence and associativity rules), before doing other canonicalization.
|
||||
|
@ -174,10 +174,22 @@ pub fn can_expr_with<'a>(
|
|||
arena.alloc("TestPath"),
|
||||
);
|
||||
|
||||
let mut scope = Scope::new(home, IdentIds::default(), Default::default());
|
||||
let mut scope = Scope::new(
|
||||
home,
|
||||
"TestPath".into(),
|
||||
IdentIds::default(),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
let dep_idents = IdentIds::exposed_builtins(0);
|
||||
let mut env = Env::new(arena, home, &dep_idents, &module_ids);
|
||||
let mut env = Env::new(
|
||||
arena,
|
||||
home,
|
||||
Path::new("Test.roc"),
|
||||
&dep_idents,
|
||||
&module_ids,
|
||||
None,
|
||||
);
|
||||
let (loc_expr, output) = canonicalize_expr(
|
||||
&mut env,
|
||||
&mut var_store,
|
||||
|
@ -203,7 +215,7 @@ pub fn can_expr_with<'a>(
|
|||
all_ident_ids.insert(home, scope.locals.ident_ids);
|
||||
|
||||
let interns = Interns {
|
||||
module_ids: env.module_ids.clone(),
|
||||
module_ids: env.qualified_module_ids.clone().into_module_ids(),
|
||||
all_ident_ids,
|
||||
};
|
||||
|
||||
|
|
9
crates/compiler/load/tests/platform.roc
Normal file
9
crates/compiler/load/tests/platform.roc
Normal file
|
@ -0,0 +1,9 @@
|
|||
platform "test-platform"
|
||||
requires {} { main : * }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [mainForHost]
|
||||
|
||||
mainForHost : {} -> {}
|
||||
mainForHost = \{} -> {}
|
|
@ -28,6 +28,7 @@ mod test_reporting {
|
|||
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
||||
use roc_solve::FunctionKind;
|
||||
use roc_solve_problem::TypeError;
|
||||
use roc_test_utils_dir::TmpDir;
|
||||
use roc_types::subs::Subs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
@ -115,7 +116,7 @@ mod test_reporting {
|
|||
// We can't have all tests use "tmp" because tests run in parallel,
|
||||
// so append the test name to the tmp path.
|
||||
let tmp = format!("tmp/{subdir}");
|
||||
let dir = roc_test_utils::TmpDir::new(&tmp);
|
||||
let dir = TmpDir::new(&tmp);
|
||||
|
||||
let filename = PathBuf::from("Test.roc");
|
||||
let file_path = dir.path().join(filename);
|
||||
|
@ -133,6 +134,7 @@ mod test_reporting {
|
|||
let result = roc_load::load_and_typecheck(
|
||||
arena,
|
||||
full_file_path,
|
||||
None,
|
||||
RocCacheDir::Disallowed,
|
||||
load_config,
|
||||
);
|
||||
|
@ -646,7 +648,7 @@ mod test_reporting {
|
|||
if true then 1 else 2
|
||||
"
|
||||
),
|
||||
@r"
|
||||
@r###"
|
||||
── UNRECOGNIZED NAME in /code/proj/Main.roc ────────────────────────────────────
|
||||
|
||||
Nothing is named `true` in this scope.
|
||||
|
@ -656,11 +658,11 @@ mod test_reporting {
|
|||
|
||||
Did you mean one of these?
|
||||
|
||||
Str
|
||||
Frac
|
||||
Num
|
||||
Str
|
||||
Err
|
||||
"
|
||||
U8
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -811,10 +813,10 @@ mod test_reporting {
|
|||
|
||||
Did you mean one of these?
|
||||
|
||||
Ok
|
||||
List
|
||||
Err
|
||||
Box
|
||||
Str
|
||||
isDisabled
|
||||
"
|
||||
),
|
||||
);
|
||||
|
@ -2211,10 +2213,10 @@ mod test_reporting {
|
|||
|
||||
Did you mean one of these?
|
||||
|
||||
Ok
|
||||
U8
|
||||
Box
|
||||
Eq
|
||||
f
|
||||
"
|
||||
);
|
||||
|
||||
|
@ -4544,13 +4546,13 @@ mod test_reporting {
|
|||
|
||||
test_report!(
|
||||
comment_with_tab,
|
||||
"# comment with a \t\n4",
|
||||
"# comment with a \t char\n4",
|
||||
@r###"
|
||||
── TAB CHARACTER in tmp/comment_with_tab/Test.roc ──────────────────────────────
|
||||
|
||||
I encountered a tab character:
|
||||
|
||||
4│ # comment with a
|
||||
4│ # comment with a char
|
||||
^
|
||||
|
||||
Tab characters are not allowed in Roc code. Please use spaces instead!
|
||||
|
@ -4559,17 +4561,17 @@ mod test_reporting {
|
|||
|
||||
test_report!(
|
||||
comment_with_control_character,
|
||||
"# comment with a \x07\n",
|
||||
@r"
|
||||
"# comment with a \x07 char\n",
|
||||
@r###"
|
||||
── ASCII CONTROL CHARACTER in tmp/comment_with_control_character/Test.roc ──────
|
||||
|
||||
I encountered an ASCII control character:
|
||||
|
||||
4│ # comment with a
|
||||
4│ # comment with a char
|
||||
^
|
||||
|
||||
ASCII control characters are not allowed.
|
||||
"
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -4767,33 +4769,38 @@ mod test_reporting {
|
|||
"
|
||||
);
|
||||
|
||||
test_report!(
|
||||
def_missing_final_expression,
|
||||
indoc!(
|
||||
r"
|
||||
f : Foo.foo
|
||||
"
|
||||
),
|
||||
@r#"
|
||||
── MISSING FINAL EXPRESSION in tmp/def_missing_final_expression/Test.roc ───────
|
||||
// TODO investigate this test. It was disabled in https://github.com/roc-lang/roc/pull/6634
|
||||
// as the way Defs without final expressions are handled. The changes probably shouldn't have
|
||||
// changed this error report. The exact same test_syntax test for this has not changed, so
|
||||
// we know the parser is parsing the same thing. Therefore the way the AST is desugared must be
|
||||
// the cause of the change in error report.
|
||||
// test_report!(
|
||||
// def_missing_final_expression,
|
||||
// indoc!(
|
||||
// r"
|
||||
// f : Foo.foo
|
||||
// "
|
||||
// ),
|
||||
// @r#"
|
||||
// ── MISSING FINAL EXPRESSION in tmp/def_missing_final_expression/Test.roc ───────
|
||||
|
||||
I am partway through parsing a definition, but I got stuck here:
|
||||
// I am partway through parsing a definition, but I got stuck here:
|
||||
|
||||
1│ app "test" provides [main] to "./platform"
|
||||
2│
|
||||
3│ main =
|
||||
4│ f : Foo.foo
|
||||
^
|
||||
// 1│ app "test" provides [main] to "./platform"
|
||||
// 2│
|
||||
// 3│ main =
|
||||
// 4│ f : Foo.foo
|
||||
// ^
|
||||
|
||||
This definition is missing a final expression. A nested definition
|
||||
must be followed by either another definition, or an expression
|
||||
// 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 = 4
|
||||
// y = 2
|
||||
|
||||
x + y
|
||||
"#
|
||||
);
|
||||
// x + y
|
||||
// "#
|
||||
// );
|
||||
|
||||
test_report!(
|
||||
expression_indentation_end,
|
||||
|
@ -4908,25 +4915,260 @@ mod test_reporting {
|
|||
"
|
||||
);
|
||||
|
||||
test_report!(
|
||||
unfinished_import,
|
||||
indoc!(
|
||||
r"
|
||||
import [
|
||||
"
|
||||
),
|
||||
@r###"
|
||||
── UNFINISHED IMPORT in tmp/unfinished_import/Test.roc ─────────────────────────
|
||||
|
||||
I was partway through parsing an `import`, but I got stuck here:
|
||||
|
||||
4│ import [
|
||||
^
|
||||
|
||||
I was expecting to see a module name, like:
|
||||
|
||||
import BigNum
|
||||
|
||||
Or a package module name, like:
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
Or a file path to ingest, like:
|
||||
|
||||
import "users.json" as users : Str
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
weird_import_params_record,
|
||||
indoc!(
|
||||
r"
|
||||
import Menu { x = 4 }
|
||||
"
|
||||
),@r###"
|
||||
── RECORD PARSE PROBLEM in tmp/weird_import_params_record/Test.roc ─────────────
|
||||
|
||||
I am partway through parsing a record, but I got stuck here:
|
||||
|
||||
4│ import Menu { x = 4 }
|
||||
^
|
||||
|
||||
TODO provide more context.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
record_builder_in_module_params,
|
||||
indoc!(
|
||||
r"
|
||||
import Menu {
|
||||
echo,
|
||||
name: <- applyName
|
||||
}
|
||||
"
|
||||
),@r###"
|
||||
── RECORD BUILDER IN MODULE PARAMS in ...ord_builder_in_module_params/Test.roc ─
|
||||
|
||||
I was partway through parsing module params, but I got stuck here:
|
||||
|
||||
4│ import Menu {
|
||||
5│ echo,
|
||||
6│ name: <- applyName
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This looks like a record builder field, but those are not allowed in
|
||||
module params.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
record_update_in_module_params,
|
||||
indoc!(
|
||||
r"
|
||||
import Menu { myParams & echo: echoFn }
|
||||
"
|
||||
),@r###"
|
||||
── RECORD UPDATE IN MODULE PARAMS in ...ecord_update_in_module_params/Test.roc ─
|
||||
|
||||
I was partway through parsing module params, but I got stuck here:
|
||||
|
||||
4│ import Menu { myParams & echo: echoFn }
|
||||
^^^^^^^^
|
||||
|
||||
It looks like you're trying to update a record, but module params
|
||||
require a standalone record literal.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
unfinished_import_as_or_exposing,
|
||||
indoc!(
|
||||
r"
|
||||
import svg.Path a
|
||||
"
|
||||
),
|
||||
@r###"
|
||||
── UNFINISHED IMPORT in tmp/unfinished_import_as_or_exposing/Test.roc ──────────
|
||||
|
||||
I was partway through parsing an `import`, but I got stuck here:
|
||||
|
||||
4│ import svg.Path a
|
||||
^
|
||||
|
||||
I was expecting to see the `as` keyword, like:
|
||||
|
||||
import svg.Path as SvgPath
|
||||
|
||||
Or the `exposing` keyword, like:
|
||||
|
||||
import svg.Path exposing [arc, rx]
|
||||
|
||||
Or module params, like:
|
||||
|
||||
import Menu { echo, read }
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
unfinished_import_alias,
|
||||
indoc!(
|
||||
r"
|
||||
import svg.Path as
|
||||
"
|
||||
),
|
||||
@r###"
|
||||
── UNFINISHED IMPORT in tmp/unfinished_import_alias/Test.roc ───────────────────
|
||||
|
||||
I was partway through parsing an `import`, but I got stuck here:
|
||||
|
||||
4│ import svg.Path as
|
||||
^
|
||||
|
||||
I just saw the `as` keyword, so I was expecting to see an alias next.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
lowercase_import_alias,
|
||||
indoc!(
|
||||
r"
|
||||
import svg.Path as path
|
||||
"
|
||||
),
|
||||
@r###"
|
||||
── LOWERCASE ALIAS in tmp/lowercase_import_alias/Test.roc ──────────────────────
|
||||
|
||||
This import is using a lowercase alias:
|
||||
|
||||
4│ import svg.Path as path
|
||||
^^^^
|
||||
|
||||
Module names and aliases must start with an uppercase letter.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
unfinished_import_exposing,
|
||||
indoc!(
|
||||
r"
|
||||
import svg.Path exposing
|
||||
"
|
||||
),
|
||||
@r###"
|
||||
── UNFINISHED IMPORT in tmp/unfinished_import_exposing/Test.roc ────────────────
|
||||
|
||||
I was partway through parsing an `import`, but I got stuck here:
|
||||
|
||||
4│ import svg.Path exposing
|
||||
^
|
||||
|
||||
I just saw the `exposing` keyword, so I was expecting to see `[` next.
|
||||
"###);
|
||||
|
||||
test_report!(
|
||||
unfinished_import_exposing_name,
|
||||
indoc!(
|
||||
r"
|
||||
import svg.Path exposing [3
|
||||
"
|
||||
),
|
||||
@r###"
|
||||
── WEIRD EXPOSING in tmp/unfinished_import_exposing_name/Test.roc ──────────────
|
||||
|
||||
I'm partway through parsing an exposing list, but I got stuck here:
|
||||
|
||||
4│ import svg.Path exposing [3
|
||||
^
|
||||
|
||||
I was expecting a type, value, or function name next, like:
|
||||
|
||||
import Svg exposing [Path, arc, rx]
|
||||
"###);
|
||||
|
||||
test_report!(
|
||||
unfinished_ingested_file_name,
|
||||
indoc!(
|
||||
r#"
|
||||
import "example.json" as
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── UNFINISHED IMPORT in tmp/unfinished_ingested_file_name/Test.roc ─────────────
|
||||
|
||||
I was partway through parsing an `import`, but I got stuck here:
|
||||
|
||||
4│ import "example.json" as
|
||||
^
|
||||
|
||||
I was expecting to see a name next, like:
|
||||
|
||||
import "users.json" as users : Str
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
ingested_file_import_ann_syntax_err,
|
||||
indoc!(
|
||||
r#"
|
||||
import "example.json" as example : List U8, U32
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── UNFINISHED TYPE in tmp/ingested_file_import_ann_syntax_err/Test.roc ─────────
|
||||
|
||||
I am partway through parsing a type, but I got stuck here:
|
||||
|
||||
4│ import "example.json" as example : List U8, U32
|
||||
^
|
||||
|
||||
Note: I may be confused by indentation
|
||||
"###
|
||||
);
|
||||
|
||||
// TODO could do better by pointing out we're parsing a function type
|
||||
test_report!(
|
||||
dict_type_formatting,
|
||||
indoc!(
|
||||
r#"
|
||||
app "dict" imports [ Dict ] provides [main] to "./platform"
|
||||
app "dict" imports [] provides [main] to "./platform"
|
||||
|
||||
myDict : Dict.Dict Num.I64 Str
|
||||
myDict : Dict Num.I64 Str
|
||||
myDict = Dict.insert (Dict.empty {}) "foo" 42
|
||||
|
||||
main = myDict
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
@r###"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
Something is off with the body of the `myDict` definition:
|
||||
|
||||
3│ myDict : Dict.Dict Num.I64 Str
|
||||
3│ myDict : Dict Num.I64 Str
|
||||
4│ myDict = Dict.insert (Dict.empty {}) "foo" 42
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -4937,14 +5179,14 @@ mod test_reporting {
|
|||
But the type annotation on `myDict` says it should be:
|
||||
|
||||
Dict I64 Str
|
||||
"#
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
alias_type_diff,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Set.{ Set }] provides [main] to "./platform"
|
||||
app "test" imports [] provides [main] to "./platform"
|
||||
|
||||
HSet a : Set a
|
||||
|
||||
|
@ -5799,9 +6041,9 @@ All branches in an `if` must have the same type!
|
|||
Did you mean one of these?
|
||||
|
||||
Str
|
||||
Err
|
||||
U8
|
||||
F64
|
||||
Box
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -6034,6 +6276,31 @@ In roc, functions are always written as a lambda, like{}
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_params_with_missing_arrow() {
|
||||
report_header_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
module {echo, read} [menu]
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── WEIRD MODULE PARAMS in /code/proj/Main.roc ──────────────────────────────────
|
||||
|
||||
I am partway through parsing a module header, but I got stuck here:
|
||||
|
||||
1│ module {echo, read} [menu]
|
||||
^
|
||||
|
||||
I am expecting `->` next, like:
|
||||
|
||||
module { echo, read } -> [menu]
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn platform_requires_rigids() {
|
||||
report_header_problem_as(
|
||||
|
@ -6103,9 +6370,7 @@ In roc, functions are always written as a lambda, like{}
|
|||
report_header_problem_as(
|
||||
indoc!(
|
||||
r"
|
||||
interface Foobar
|
||||
exposes [main, @Foo]
|
||||
imports [pf.Task, Base64]
|
||||
module [main, @Foo]
|
||||
"
|
||||
),
|
||||
indoc!(
|
||||
|
@ -6114,39 +6379,12 @@ In roc, functions are always written as a lambda, like{}
|
|||
|
||||
I am partway through parsing an `exposes` list, but I got stuck here:
|
||||
|
||||
1│ interface Foobar
|
||||
2│ exposes [main, @Foo]
|
||||
^
|
||||
1│ module [main, @Foo]
|
||||
^
|
||||
|
||||
I was expecting a type name, value name or function name next, like
|
||||
|
||||
exposes [Animal, default, tame]
|
||||
"
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_module_name() {
|
||||
report_header_problem_as(
|
||||
indoc!(
|
||||
r"
|
||||
interface foobar
|
||||
exposes [main, @Foo]
|
||||
imports [pf.Task, Base64]
|
||||
"
|
||||
),
|
||||
indoc!(
|
||||
r"
|
||||
── WEIRD MODULE NAME in /code/proj/Main.roc ────────────────────────────────────
|
||||
|
||||
I am partway through parsing a header, but got stuck here:
|
||||
|
||||
1│ interface foobar
|
||||
^
|
||||
|
||||
I am expecting a module name next, like BigNum or Main. Module names
|
||||
must start with an uppercase letter.
|
||||
[Animal, default, tame]
|
||||
"
|
||||
),
|
||||
)
|
||||
|
@ -7925,7 +8163,7 @@ In roc, functions are always written as a lambda, like{}
|
|||
"#
|
||||
),
|
||||
// TODO(opaques): error could be improved by saying that the opaque definition demands
|
||||
// that the argument be a U8, and linking to the definitin!
|
||||
// that the argument be a U8, and linking to the definition!
|
||||
@r#"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
|
@ -8401,17 +8639,38 @@ In roc, functions are always written as a lambda, like{}
|
|||
a
|
||||
"
|
||||
),
|
||||
@r"
|
||||
── UNBOUND TYPE VARIABLE in /code/proj/Main.roc ────────────────────────────────
|
||||
@r###"
|
||||
── WILDCARD NOT ALLOWED HERE in /code/proj/Main.roc ────────────────────────────
|
||||
|
||||
The definition of `I` has an unbound type variable:
|
||||
The definition of `I` includes a wildcard (`*`) type variable:
|
||||
|
||||
4│ I : Num.Int *
|
||||
^
|
||||
|
||||
Tip: Type variables must be bound before the `:`. Perhaps you intended
|
||||
to add a type parameter to this type?
|
||||
"
|
||||
Type alias definitions may not use wildcard (`*`) type variables. Only
|
||||
named type variables are allowed.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
underscore_in_alias,
|
||||
indoc!(
|
||||
r"
|
||||
I : Num.Int _
|
||||
a : I
|
||||
a
|
||||
"
|
||||
),
|
||||
@r###"
|
||||
── UNDERSCORE NOT ALLOWED HERE in /code/proj/Main.roc ──────────────────────────
|
||||
|
||||
The definition of `I` includes an inferred (`_`) type:
|
||||
|
||||
4│ I : Num.Int _
|
||||
^
|
||||
|
||||
Type alias definitions may not use inferred types (`_`).
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -8423,17 +8682,17 @@ In roc, functions are always written as a lambda, like{}
|
|||
a
|
||||
"
|
||||
),
|
||||
@r"
|
||||
── UNBOUND TYPE VARIABLE in /code/proj/Main.roc ────────────────────────────────
|
||||
@r###"
|
||||
── WILDCARD NOT ALLOWED HERE in /code/proj/Main.roc ────────────────────────────
|
||||
|
||||
The definition of `I` has an unbound type variable:
|
||||
The definition of `I` includes a wildcard (`*`) type variable:
|
||||
|
||||
4│ I := Num.Int *
|
||||
^
|
||||
|
||||
Tip: Type variables must be bound before the `:=`. Perhaps you intended
|
||||
to add a type parameter to this type?
|
||||
"
|
||||
Opaque type definitions may not use wildcard (`*`) type variables. Only
|
||||
named type variables are allowed.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -8445,19 +8704,18 @@ In roc, functions are always written as a lambda, like{}
|
|||
a
|
||||
"
|
||||
),
|
||||
@r"
|
||||
── UNBOUND TYPE VARIABLE in /code/proj/Main.roc ────────────────────────────────
|
||||
@r###"
|
||||
── WILDCARD NOT ALLOWED HERE in /code/proj/Main.roc ────────────────────────────
|
||||
|
||||
The definition of `I` has 2 unbound type variables.
|
||||
|
||||
Here is one occurrence:
|
||||
The definition of `I` includes 2 wildcard (`*`) type variables. Here is
|
||||
one of them:
|
||||
|
||||
4│ I : [A (Num.Int *), B (Num.Int *)]
|
||||
^
|
||||
|
||||
Tip: Type variables must be bound before the `:`. Perhaps you intended
|
||||
to add a type parameter to this type?
|
||||
"
|
||||
Type alias definitions may not use wildcard (`*`) type variables. Only
|
||||
named type variables are allowed.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -8469,17 +8727,16 @@ In roc, functions are always written as a lambda, like{}
|
|||
a
|
||||
"
|
||||
),
|
||||
@r"
|
||||
── UNBOUND TYPE VARIABLE in /code/proj/Main.roc ────────────────────────────────
|
||||
@r###"
|
||||
── UNDERSCORE NOT ALLOWED HERE in /code/proj/Main.roc ──────────────────────────
|
||||
|
||||
The definition of `I` has an unbound type variable:
|
||||
The definition of `I` includes an inferred (`_`) type:
|
||||
|
||||
4│ I : Num.Int _
|
||||
^
|
||||
|
||||
Tip: Type variables must be bound before the `:`. Perhaps you intended
|
||||
to add a type parameter to this type?
|
||||
"
|
||||
Type alias definitions may not use inferred types (`_`).
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -8491,17 +8748,19 @@ In roc, functions are always written as a lambda, like{}
|
|||
a
|
||||
"
|
||||
),
|
||||
@r"
|
||||
── UNBOUND TYPE VARIABLE in /code/proj/Main.roc ────────────────────────────────
|
||||
@r###"
|
||||
── UNDECLARED TYPE VARIABLE in /code/proj/Main.roc ─────────────────────────────
|
||||
|
||||
The definition of `I` has an unbound type variable:
|
||||
The definition of `I` includes an undeclared type variable:
|
||||
|
||||
4│ I : Num.Int a
|
||||
^
|
||||
|
||||
Tip: Type variables must be bound before the `:`. Perhaps you intended
|
||||
to add a type parameter to this type?
|
||||
"
|
||||
All type variables in type alias definitions must be declared.
|
||||
|
||||
Tip: You can declare type variables by putting them right before the `:`
|
||||
symbol, separated by spaces.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -9304,7 +9563,7 @@ In roc, functions are always written as a lambda, like{}
|
|||
type_error_in_apply_is_circular,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Set] provides [go] to "./platform"
|
||||
app "test" imports [] provides [go] to "./platform"
|
||||
|
||||
S a : { set : Set.Set a }
|
||||
|
||||
|
@ -10892,7 +11151,9 @@ In roc, functions are always written as a lambda, like{}
|
|||
function_cannot_derive_encoding,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode.{decoder}] provides [main] to "./platform"
|
||||
app "test" imports [] provides [main] to "./platform"
|
||||
|
||||
import Decode exposing [decoder]
|
||||
|
||||
main =
|
||||
myDecoder : Decoder (a -> a) fmt where fmt implements DecoderFormatting
|
||||
|
@ -10901,12 +11162,12 @@ In roc, functions are always written as a lambda, like{}
|
|||
myDecoder
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
@r###"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This expression has a type that does not implement the abilities it's expected to:
|
||||
|
||||
5│ myDecoder = decoder
|
||||
7│ myDecoder = decoder
|
||||
^^^^^^^
|
||||
|
||||
I can't generate an implementation of the `Decoding` ability for
|
||||
|
@ -10914,14 +11175,16 @@ In roc, functions are always written as a lambda, like{}
|
|||
a -> a
|
||||
|
||||
Note: `Decoding` cannot be generated for functions.
|
||||
"
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
nested_opaque_cannot_derive_encoding,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode.{decoder}] provides [main] to "./platform"
|
||||
app "test" imports [] provides [main] to "./platform"
|
||||
|
||||
import Decode exposing [decoder]
|
||||
|
||||
A := {}
|
||||
|
||||
|
@ -10932,12 +11195,12 @@ In roc, functions are always written as a lambda, like{}
|
|||
myDecoder
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
@r###"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This expression has a type that does not implement the abilities it's expected to:
|
||||
|
||||
7│ myDecoder = decoder
|
||||
9│ myDecoder = decoder
|
||||
^^^^^^^
|
||||
|
||||
I can't generate an implementation of the `Decoding` ability for
|
||||
|
@ -10952,7 +11215,7 @@ In roc, functions are always written as a lambda, like{}
|
|||
|
||||
Tip: `A` does not implement `Decoding`. Consider adding a custom
|
||||
implementation or `implements Decode.Decoding` to the definition of `A`.
|
||||
"
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -11113,7 +11376,9 @@ In roc, functions are always written as a lambda, like{}
|
|||
infer_decoded_record_error_with_function_field,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [TotallyNotJson] provides [main] to "./platform"
|
||||
app "test" imports [] provides [main] to "./platform"
|
||||
|
||||
import TotallyNotJson
|
||||
|
||||
main =
|
||||
decoded = Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes TotallyNotJson.json
|
||||
|
@ -11122,12 +11387,12 @@ In roc, functions are always written as a lambda, like{}
|
|||
_ -> "something went wrong"
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
@r###"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This expression has a type that does not implement the abilities it's expected to:
|
||||
|
||||
6│ Ok rcd -> rcd.first rcd.second
|
||||
8│ Ok rcd -> rcd.first rcd.second
|
||||
^^^^^^^^^
|
||||
|
||||
I can't generate an implementation of the `Decoding` ability for
|
||||
|
@ -11135,14 +11400,16 @@ In roc, functions are always written as a lambda, like{}
|
|||
* -> *
|
||||
|
||||
Note: `Decoding` cannot be generated for functions.
|
||||
"
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
record_with_optional_field_types_cannot_derive_decoding,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode.{decoder}] provides [main] to "./platform"
|
||||
app "test" imports [] provides [main] to "./platform"
|
||||
|
||||
import Decode exposing [decoder]
|
||||
|
||||
main =
|
||||
myDecoder : Decoder {x : Str, y ? Str} fmt where fmt implements DecoderFormatting
|
||||
|
@ -11151,12 +11418,12 @@ In roc, functions are always written as a lambda, like{}
|
|||
myDecoder
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
@r###"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This expression has a type that does not implement the abilities it's expected to:
|
||||
|
||||
5│ myDecoder = decoder
|
||||
7│ myDecoder = decoder
|
||||
^^^^^^^
|
||||
|
||||
I can't generate an implementation of the `Decoding` ability for
|
||||
|
@ -11171,7 +11438,7 @@ In roc, functions are always written as a lambda, like{}
|
|||
over records that may or may not contain them at compile time, but are
|
||||
not a concept that extends to runtime!
|
||||
Maybe you wanted to use a `Result`?
|
||||
"
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -11353,21 +11620,23 @@ In roc, functions are always written as a lambda, like{}
|
|||
unused_value_import,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [List.{ concat }] provides [main] to "./platform"
|
||||
app "test" imports [] provides [main] to "./platform"
|
||||
|
||||
import List exposing [concat]
|
||||
|
||||
main = ""
|
||||
"#
|
||||
),
|
||||
@r#"
|
||||
@r###"
|
||||
── UNUSED IMPORT in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
`List.concat` is not used in this module.
|
||||
List is imported but not used.
|
||||
|
||||
1│ app "test" imports [List.{ concat }] provides [main] to "./platform"
|
||||
^^^^^^
|
||||
3│ import List exposing [concat]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Since `List.concat` isn't used, you don't need to import it.
|
||||
"#
|
||||
Since List isn't used, you don't need to import it.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
|
@ -11389,7 +11658,9 @@ In roc, functions are always written as a lambda, like{}
|
|||
unnecessary_builtin_type_import,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode.{ DecodeError }] provides [main, E] to "./platform"
|
||||
app "test" imports [] provides [main, E] to "./platform"
|
||||
|
||||
import Decode exposing [DecodeError]
|
||||
|
||||
E : DecodeError
|
||||
|
||||
|
@ -11399,6 +11670,54 @@ In roc, functions are always written as a lambda, like{}
|
|||
@r"
|
||||
"
|
||||
);
|
||||
test_report!(
|
||||
unknown_shorthand_no_deps,
|
||||
indoc!(
|
||||
r#"
|
||||
import foo.Foo
|
||||
|
||||
Foo.foo
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── UNRECOGNIZED PACKAGE in tmp/unknown_shorthand_no_deps/Test.roc ──────────────
|
||||
|
||||
This module is trying to import from `foo`:
|
||||
|
||||
4│ import foo.Foo
|
||||
^^^^^^^
|
||||
|
||||
A lowercase name indicates a package shorthand, but no packages have
|
||||
been specified.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
unknown_shorthand_in_app,
|
||||
indoc!(
|
||||
r#"
|
||||
app [main] { pf: platform "../../tests/platform.roc" }
|
||||
|
||||
import foo.Foo
|
||||
|
||||
main =
|
||||
Foo.foo
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
── UNRECOGNIZED PACKAGE in tmp/unknown_shorthand_in_app/Test.roc ───────────────
|
||||
|
||||
This module is trying to import from `foo`:
|
||||
|
||||
3│ import foo.Foo
|
||||
^^^^^^^
|
||||
|
||||
A lowercase name indicates a package shorthand, but I don't recognize
|
||||
this one. Did you mean one of these?
|
||||
|
||||
pf
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
invalid_toplevel_cycle,
|
||||
|
@ -13260,7 +13579,7 @@ In roc, functions are always written as a lambda, like{}
|
|||
4│ crash "" ""
|
||||
^^^^^
|
||||
|
||||
`crash` must be given exacly one message to crash with.
|
||||
`crash` must be given exactly one message to crash with.
|
||||
"#
|
||||
);
|
||||
|
||||
|
@ -13658,7 +13977,9 @@ In roc, functions are always written as a lambda, like{}
|
|||
derive_decoding_for_tuple,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode.{decoder}] provides [main] to "./platform"
|
||||
app "test" imports [] provides [main] to "./platform"
|
||||
|
||||
import Decode exposing [decoder]
|
||||
|
||||
main =
|
||||
myDecoder : Decoder (U32, Str) fmt where fmt implements DecoderFormatting
|
||||
|
@ -13673,7 +13994,9 @@ In roc, functions are always written as a lambda, like{}
|
|||
cannot_decode_tuple_with_non_decode_element,
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" imports [Decode.{decoder}] provides [main] to "./platform"
|
||||
app "test" imports [] provides [main] to "./platform"
|
||||
|
||||
import Decode exposing [decoder]
|
||||
|
||||
main =
|
||||
myDecoder : Decoder (U32, {} -> {}) fmt where fmt implements DecoderFormatting
|
||||
|
@ -13682,12 +14005,12 @@ In roc, functions are always written as a lambda, like{}
|
|||
myDecoder
|
||||
"#
|
||||
),
|
||||
@r"
|
||||
@r###"
|
||||
── TYPE MISMATCH in /code/proj/Main.roc ────────────────────────────────────────
|
||||
|
||||
This expression has a type that does not implement the abilities it's expected to:
|
||||
|
||||
5│ myDecoder = decoder
|
||||
7│ myDecoder = decoder
|
||||
^^^^^^^
|
||||
|
||||
I can't generate an implementation of the `Decoding` ability for
|
||||
|
@ -13695,7 +14018,7 @@ In roc, functions are always written as a lambda, like{}
|
|||
U32, {} -> {}
|
||||
|
||||
Note: `Decoding` cannot be generated for functions.
|
||||
"
|
||||
"###
|
||||
);
|
||||
|
||||
test_no_problem!(
|
||||
|
|
|
@ -40,7 +40,7 @@ parking_lot.workspace = true
|
|||
tempfile.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
roc_test_utils = { path = "../../test_utils" }
|
||||
roc_test_utils_dir = { path = "../../test_utils_dir" }
|
||||
|
||||
indoc.workspace = true
|
||||
maplit.workspace = true
|
||||
|
|
|
@ -212,7 +212,7 @@ fn generate_entry_docs(
|
|||
match either_index.split() {
|
||||
Err(value_index) => match &defs.value_defs[value_index.index()] {
|
||||
ValueDef::Annotation(loc_pattern, loc_ann) => {
|
||||
if let Pattern::Identifier(identifier) = loc_pattern.value {
|
||||
if let Pattern::Identifier { ident: identifier } = loc_pattern.value {
|
||||
// Check if this module exposes the def
|
||||
if let Some(ident_id) = ident_ids.get_id(identifier) {
|
||||
let name = identifier.to_string();
|
||||
|
@ -233,7 +233,7 @@ fn generate_entry_docs(
|
|||
ann_type,
|
||||
..
|
||||
} => {
|
||||
if let Pattern::Identifier(identifier) = ann_pattern.value {
|
||||
if let Pattern::Identifier { ident: identifier } = ann_pattern.value {
|
||||
// Check if this module exposes the def
|
||||
if let Some(ident_id) = ident_ids.get_id(identifier) {
|
||||
let doc_def = DocDef {
|
||||
|
@ -249,7 +249,7 @@ fn generate_entry_docs(
|
|||
}
|
||||
|
||||
ValueDef::Body(pattern, _) => {
|
||||
if let Pattern::Identifier(identifier) = pattern.value {
|
||||
if let Pattern::Identifier { ident: identifier } = pattern.value {
|
||||
// Check if this module exposes the def
|
||||
if let Some(ident_id) = ident_ids.get_id(identifier) {
|
||||
let doc_def = DocDef {
|
||||
|
@ -275,6 +275,31 @@ fn generate_entry_docs(
|
|||
ValueDef::ExpectFx { .. } => {
|
||||
// Don't generate docs for `expect-fx`s
|
||||
}
|
||||
ValueDef::ModuleImport { .. } => {
|
||||
// Don't generate docs for module imports
|
||||
}
|
||||
ValueDef::IngestedFileImport { .. } => {
|
||||
// Don't generate docs for ingested file imports
|
||||
}
|
||||
|
||||
ValueDef::Stmt(loc_expr) => {
|
||||
if let roc_parse::ast::Expr::Var {
|
||||
ident: identifier, ..
|
||||
} = loc_expr.value
|
||||
{
|
||||
// Check if this module exposes the def
|
||||
if let Some(ident_id) = ident_ids.get_id(identifier) {
|
||||
let doc_def = DocDef {
|
||||
name: identifier.to_string(),
|
||||
type_annotation: TypeAnnotation::NoTypeAnn,
|
||||
type_vars: Vec::new(),
|
||||
symbol: Symbol::new(home, ident_id),
|
||||
docs,
|
||||
};
|
||||
doc_entries.push(DocEntry::DocDef(doc_def));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Ok(type_index) => match &defs.type_defs[type_index.index()] {
|
||||
|
@ -285,7 +310,7 @@ fn generate_entry_docs(
|
|||
let mut type_vars = Vec::new();
|
||||
|
||||
for var in vars.iter() {
|
||||
if let Pattern::Identifier(ident_name) = var.value {
|
||||
if let Pattern::Identifier { ident: ident_name } = var.value {
|
||||
type_vars.push(ident_name.to_string());
|
||||
}
|
||||
}
|
||||
|
@ -319,7 +344,7 @@ fn generate_entry_docs(
|
|||
let mut type_vars = Vec::new();
|
||||
|
||||
for var in vars.iter() {
|
||||
if let Pattern::Identifier(ident_name) = var.value {
|
||||
if let Pattern::Identifier { ident: ident_name } = var.value {
|
||||
type_vars.push(ident_name.to_string());
|
||||
}
|
||||
}
|
||||
|
@ -343,7 +368,7 @@ fn generate_entry_docs(
|
|||
let mut type_vars = Vec::new();
|
||||
|
||||
for var in vars.iter() {
|
||||
if let Pattern::Identifier(ident_name) = var.value {
|
||||
if let Pattern::Identifier { ident: ident_name } = var.value {
|
||||
type_vars.push(ident_name.to_string());
|
||||
}
|
||||
}
|
||||
|
@ -605,7 +630,7 @@ fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) ->
|
|||
.vars
|
||||
.iter()
|
||||
.filter_map(|loc_pattern| match loc_pattern.value {
|
||||
ast::Pattern::Identifier(ident) => Some(ident.to_string()),
|
||||
ast::Pattern::Identifier { ident } => Some(ident.to_string()),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,22 +0,0 @@
|
|||
fn report_missing_package_shorthand2<'a>(
|
||||
packages: &[Loc<PackageEntry>],
|
||||
imports: &[Loc<ImportsEntry>],
|
||||
) -> Option<LoadingProblem<'a>> {
|
||||
imports.iter().find_map(|i| match i.value {
|
||||
ImportsEntry::Module(_, _) | ImportsEntry::IngestedFile(_, _) => None,
|
||||
ImportsEntry::Package(shorthand, name, _) => {
|
||||
let name=name.as_str();
|
||||
if packages
|
||||
.iter()
|
||||
.find(|p| p.value.shorthand == shorthand)
|
||||
.is_none()
|
||||
{
|
||||
Some(
|
||||
LoadingProblem::FormattedReport(
|
||||
format!("The package shorthand '{shorthand}' that you are importing the module '{name}' from in '{shorthand}.{name}', doesn't exist in this module.\nImport it in the \"packages\" section of the header.")))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -13,7 +13,7 @@ use roc_module::symbol::{
|
|||
};
|
||||
use roc_mono::ir::{GlueLayouts, HostExposedLambdaSets, LambdaSetId, Proc, ProcLayout, ProcsBase};
|
||||
use roc_mono::layout::{LayoutCache, STLayoutInterner};
|
||||
use roc_parse::ast::{CommentOrNewline, Defs, TypeAnnotation, ValueDef};
|
||||
use roc_parse::ast::{CommentOrNewline, Defs, TypeAnnotation};
|
||||
use roc_parse::header::{HeaderType, PackageName};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_solve::module::Solved;
|
||||
|
@ -30,6 +30,7 @@ use std::time::{Duration, Instant};
|
|||
#[derive(Debug)]
|
||||
pub struct LoadedModule {
|
||||
pub module_id: ModuleId,
|
||||
pub filename: PathBuf,
|
||||
pub interns: Interns,
|
||||
pub solved: Solved<Subs>,
|
||||
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
|
||||
|
@ -54,6 +55,13 @@ pub struct LoadedModule {
|
|||
}
|
||||
|
||||
impl LoadedModule {
|
||||
/// Infer the filename for the given ModuleId, based on this root module's filename.
|
||||
pub fn filename(&self, module_id: ModuleId) -> PathBuf {
|
||||
let module_name = self.interns.module_name(module_id);
|
||||
|
||||
module_name.filename(&self.filename)
|
||||
}
|
||||
|
||||
pub fn total_problems(&self) -> usize {
|
||||
let mut total = 0;
|
||||
|
||||
|
@ -88,26 +96,20 @@ pub(crate) struct ModuleHeader<'a> {
|
|||
pub(crate) module_id: ModuleId,
|
||||
pub(crate) module_path: PathBuf,
|
||||
pub(crate) is_root_module: bool,
|
||||
pub(crate) exposed_ident_ids: IdentIds,
|
||||
pub(crate) deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
|
||||
pub(crate) packages: MutMap<&'a str, PackageName<'a>>,
|
||||
pub(crate) imported_modules: MutMap<ModuleId, Region>,
|
||||
pub(crate) package_qualified_imported_modules: MutSet<PackageQualified<'a, ModuleId>>,
|
||||
pub(crate) exposes: Vec<Symbol>,
|
||||
pub(crate) exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
||||
pub(crate) parse_state: roc_parse::state::State<'a>,
|
||||
pub(crate) header_type: HeaderType<'a>,
|
||||
pub(crate) header_comments: &'a [CommentOrNewline<'a>],
|
||||
pub(crate) symbols_from_requires: Vec<(Loc<Symbol>, Loc<TypeAnnotation<'a>>)>,
|
||||
pub(crate) header_imports: Option<roc_parse::header::ImportsKeywordItem<'a>>,
|
||||
pub(crate) module_timing: ModuleTiming,
|
||||
pub(crate) defined_values: Vec<ValueDef<'a>>,
|
||||
pub(crate) opt_shorthand: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ConstrainedModule {
|
||||
pub(crate) module: Module,
|
||||
pub(crate) declarations: Declarations,
|
||||
pub(crate) imported_modules: MutMap<ModuleId, Region>,
|
||||
pub(crate) available_modules: MutMap<ModuleId, Region>,
|
||||
pub(crate) constraints: Constraints,
|
||||
pub(crate) constraint: ConstraintSoa,
|
||||
pub(crate) ident_ids: IdentIds,
|
||||
|
@ -195,13 +197,17 @@ pub struct ParsedModule<'a> {
|
|||
pub src: &'a str,
|
||||
pub module_timing: ModuleTiming,
|
||||
pub deps_by_name: MutMap<PQModuleName<'a>, ModuleId>,
|
||||
pub imported_modules: MutMap<ModuleId, Region>,
|
||||
pub exposed_ident_ids: IdentIds,
|
||||
pub exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
||||
pub parsed_defs: Defs<'a>,
|
||||
pub symbols_from_requires: Vec<(Loc<Symbol>, Loc<TypeAnnotation<'a>>)>,
|
||||
pub header_type: HeaderType<'a>,
|
||||
pub header_comments: &'a [CommentOrNewline<'a>],
|
||||
pub available_modules: MutMap<ModuleId, Region>,
|
||||
pub package_qualified_available_modules: MutSet<PackageQualified<'a, ModuleId>>,
|
||||
pub packages: MutMap<&'a str, PackageName<'a>>,
|
||||
pub initial_scope: MutMap<Ident, (Symbol, Region)>,
|
||||
pub exposes: Vec<Symbol>,
|
||||
pub opt_shorthand: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -162,10 +162,6 @@ impl<'a> Dependencies<'a> {
|
|||
output.insert((dep, Phase::LoadHeader));
|
||||
}
|
||||
|
||||
// to parse and generate constraints, the headers of all dependencies must be loaded!
|
||||
// otherwise, we don't know whether an imported symbol is actually exposed
|
||||
self.add_dependency_help(module_id, dep, Phase::Parse, Phase::LoadHeader);
|
||||
|
||||
// to canonicalize a module, all its dependencies must be canonicalized
|
||||
self.add_dependency(module_id, dep, Phase::CanonicalizeAndConstrain);
|
||||
|
||||
|
@ -427,10 +423,10 @@ impl<'a> Dependencies<'a> {
|
|||
PrepareStartPhase::Recurse(new)
|
||||
}
|
||||
None => match phase {
|
||||
Phase::LoadHeader => {
|
||||
// this is fine, mark header loading as pending
|
||||
Phase::LoadHeader | Phase::Parse => {
|
||||
// this is fine, mark as pending
|
||||
self.status
|
||||
.insert(Job::Step(module_id, Phase::LoadHeader), Status::Pending);
|
||||
.insert(Job::Step(module_id, phase), Status::Pending);
|
||||
|
||||
PrepareStartPhase::Continue
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
interface Dep1
|
||||
exposes [three, str, Unit, Identity, one, two]
|
||||
imports [Dep3.Blah.{ foo }]
|
||||
imports []
|
||||
|
||||
import Dep3Blah exposing [foo]
|
||||
|
||||
one = 1
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
interface Dep2
|
||||
exposes [one, two, blah]
|
||||
imports [Dep3.Blah.{ foo, bar }]
|
||||
imports []
|
||||
|
||||
import Dep3Blah exposing [foo, bar]
|
||||
|
||||
one = 1
|
||||
|
||||
blah = foo
|
||||
|
||||
two = 2.0
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
interface Dep3.Other
|
||||
exposes [foo, bar]
|
||||
imports []
|
||||
|
||||
foo = "foo from Dep3.Other"
|
||||
bar = "bar from Dep3.Other"
|
|
@ -1,10 +1,12 @@
|
|||
interface Dep3.Blah
|
||||
interface Dep3Blah
|
||||
exposes [one, two, foo, bar]
|
||||
imports [Dep3.Other]
|
||||
imports []
|
||||
|
||||
import Dep3Other
|
||||
|
||||
one = 1
|
||||
|
||||
two = 2
|
||||
|
||||
foo = "foo from Dep3"
|
||||
bar = Dep3.Other.bar
|
||||
bar = Dep3Other.bar
|
6
crates/compiler/load_internal/tests/fixtures/build/app_with_deps/Dep3Other.roc
vendored
Normal file
6
crates/compiler/load_internal/tests/fixtures/build/app_with_deps/Dep3Other.roc
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
interface Dep3Other
|
||||
exposes [foo, bar]
|
||||
imports []
|
||||
|
||||
foo = "foo from Dep3Other"
|
||||
bar = "bar from Dep3Other"
|
|
@ -1,7 +1,8 @@
|
|||
interface ImportAlias
|
||||
exposes [unit]
|
||||
imports [Dep1]
|
||||
imports []
|
||||
|
||||
import Dep1
|
||||
|
||||
unit : Dep1.Unit
|
||||
unit = Unit
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
interface OneDep
|
||||
exposes [str]
|
||||
imports [Dep3.Blah.{ foo }]
|
||||
imports []
|
||||
|
||||
import Dep3Blah exposing [foo]
|
||||
|
||||
str = foo
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
interface Primary
|
||||
exposes [blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay]
|
||||
imports [Dep1, Dep2.{ two }, Dep3.Blah.{ bar }, Res]
|
||||
imports []
|
||||
|
||||
import Dep1
|
||||
import Dep2
|
||||
import Dep3Blah exposing [bar]
|
||||
import Res
|
||||
|
||||
blah2 = Dep2.two
|
||||
blah3 = bar
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
interface WithBuiltins
|
||||
exposes [floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2]
|
||||
imports [Dep1, Dep2.{ two }]
|
||||
imports []
|
||||
|
||||
import Dep1
|
||||
import Dep2 exposing [two]
|
||||
|
||||
floatTest = Num.maxF64
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
interface IngestedFile
|
||||
exposes [str]
|
||||
imports ["IngestedFile.roc" as foo : Str]
|
||||
|
||||
str = foo
|
|
@ -1,6 +1,8 @@
|
|||
interface Dep1
|
||||
exposes [three, str, Unit, Identity, one, two]
|
||||
imports [Dep3.Blah.{ foo }]
|
||||
imports []
|
||||
|
||||
import Dep3 exposing [foo]
|
||||
|
||||
one = 1
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
interface Dep2
|
||||
exposes [one, two, blah]
|
||||
imports [Dep3.Blah.{ foo, bar }]
|
||||
imports []
|
||||
|
||||
import Dep3 exposing [foo, bar]
|
||||
|
||||
one = 1
|
||||
|
||||
blah = foo
|
||||
|
||||
two = 2.0
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
interface Dep3.Blah
|
||||
interface Dep3
|
||||
exposes [one, two, foo, bar]
|
||||
imports []
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
interface ExposedUsedOutsideScope exposes [good, bad] imports []
|
||||
|
||||
good =
|
||||
import Dep2 exposing [two]
|
||||
two
|
||||
|
||||
bad =
|
||||
two
|
|
@ -1,7 +1,8 @@
|
|||
interface ImportAlias
|
||||
exposes [unit]
|
||||
imports [Dep1]
|
||||
imports []
|
||||
|
||||
import Dep1
|
||||
|
||||
unit : Dep1.Unit
|
||||
unit = Unit
|
||||
|
12
crates/compiler/load_internal/tests/fixtures/build/module_with_deps/ImportInsideDef.roc
vendored
Normal file
12
crates/compiler/load_internal/tests/fixtures/build/module_with_deps/ImportInsideDef.roc
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
interface ImportInsideDef exposes [dep1Str, dep2TwoDobuled] imports []
|
||||
|
||||
dep1Str =
|
||||
import Dep1
|
||||
Dep1.str
|
||||
|
||||
dep2TwoDobuled =
|
||||
2
|
||||
* (
|
||||
import Dep2 exposing [two]
|
||||
two
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
interface ImportUsedOutsideScope exposes [good, bad] imports []
|
||||
|
||||
good =
|
||||
import Dep2
|
||||
Dep2.two
|
||||
|
||||
bad =
|
||||
Dep2.two
|
11
crates/compiler/load_internal/tests/fixtures/build/module_with_deps/IngestedFile.roc
vendored
Normal file
11
crates/compiler/load_internal/tests/fixtures/build/module_with_deps/IngestedFile.roc
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
interface IngestedFile
|
||||
exposes [str, nested]
|
||||
imports []
|
||||
|
||||
import "IngestedFile.roc" as foo : Str
|
||||
|
||||
str = foo
|
||||
|
||||
nested =
|
||||
import "Dep1.roc" as dep1 : Str
|
||||
dep1
|
|
@ -1,5 +1,7 @@
|
|||
interface IngestedFileBytes
|
||||
exposes [str]
|
||||
imports ["IngestedFileBytes.roc" as foo : List U8]
|
||||
imports []
|
||||
|
||||
import "IngestedFileBytes.roc" as foo : List U8
|
||||
|
||||
str = Str.fromUtf8 foo |> Result.withDefault ""
|
|
@ -1,5 +1,7 @@
|
|||
interface OneDep
|
||||
exposes [str]
|
||||
imports [Dep3.Blah.{ foo }]
|
||||
imports []
|
||||
|
||||
import Dep3 exposing [foo]
|
||||
|
||||
str = foo
|
|
@ -1,6 +1,11 @@
|
|||
interface Primary
|
||||
exposes [blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay]
|
||||
imports [Dep1, Dep2.{ two }, Dep3.Blah.{ bar }, Res]
|
||||
imports []
|
||||
|
||||
import Dep1
|
||||
import Dep2
|
||||
import Dep3 exposing [bar]
|
||||
import Res
|
||||
|
||||
blah2 = Dep2.two
|
||||
blah3 = bar
|
|
@ -1,6 +1,9 @@
|
|||
interface WithBuiltins
|
||||
exposes [floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2]
|
||||
imports [Dep1, Dep2.{ two }]
|
||||
imports []
|
||||
|
||||
import Dep1
|
||||
import Dep2
|
||||
|
||||
floatTest = Num.maxF64
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
interface MissingDep
|
||||
exposes [unit]
|
||||
imports [ThisFileIsMissing]
|
||||
imports []
|
||||
|
||||
import ThisFileIsMissing
|
||||
|
||||
Unit : [Unit]
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
interface MissingIngestedFile
|
||||
exposes [unit]
|
||||
imports ["ThisFileIsMissing" as data: List U8]
|
||||
imports []
|
||||
|
||||
import "ThisFileIsMissing" as data : List U8
|
||||
|
||||
Unit : [Unit]
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,8 @@
|
|||
pub use roc_ident::IdentStr;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::symbol::PQModuleName;
|
||||
|
||||
|
@ -45,6 +48,19 @@ impl<'a> QualifiedModuleName<'a> {
|
|||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ModuleName(IdentStr);
|
||||
|
||||
impl ModuleName {
|
||||
/// Given the root module's path, infer this module's path based on its name.
|
||||
pub fn filename(&self, root_filename: impl AsRef<Path>) -> PathBuf {
|
||||
let mut answer = root_filename.as_ref().with_file_name("");
|
||||
|
||||
for part in self.split('.') {
|
||||
answer = answer.join(part);
|
||||
}
|
||||
|
||||
answer.with_extension("roc")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ModuleName {
|
||||
type Target = str;
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ pub enum LowLevel {
|
|||
ListGetCapacity,
|
||||
ListIsUnique,
|
||||
ListClone,
|
||||
ListConcatUtf8,
|
||||
NumAdd,
|
||||
NumAddWrap,
|
||||
NumAddChecked,
|
||||
|
@ -290,6 +291,7 @@ map_symbol_to_lowlevel! {
|
|||
ListSublist <= LIST_SUBLIST_LOWLEVEL;
|
||||
ListDropAt <= LIST_DROP_AT;
|
||||
ListSwap <= LIST_SWAP;
|
||||
ListConcatUtf8 <= LIST_CONCAT_UTF8;
|
||||
NumAdd <= NUM_ADD;
|
||||
NumAddWrap <= NUM_ADD_WRAP;
|
||||
NumAddChecked <= NUM_ADD_CHECKED_LOWLEVEL;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::ident::{Ident, ModuleName};
|
||||
use crate::ident::{Ident, Lowercase, ModuleName};
|
||||
use crate::module_err::{IdentIdNotFoundSnafu, ModuleIdNotFoundSnafu, ModuleResult};
|
||||
use roc_collections::{SmallStringInterner, VecMap};
|
||||
use roc_error_macros::internal_error;
|
||||
|
@ -115,6 +115,15 @@ impl Symbol {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn is_automatically_imported(self) -> bool {
|
||||
let module_id = self.module_id();
|
||||
|
||||
module_id.is_automatically_imported()
|
||||
&& Self::builtin_types_in_scope(module_id)
|
||||
.iter()
|
||||
.any(|(_, (s, _))| *s == self)
|
||||
}
|
||||
|
||||
pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a ModuleName {
|
||||
interns
|
||||
.module_ids
|
||||
|
@ -384,6 +393,11 @@ impl ModuleId {
|
|||
.get_name(self)
|
||||
.unwrap_or_else(|| internal_error!("Could not find ModuleIds for {:?}", self))
|
||||
}
|
||||
|
||||
pub fn is_automatically_imported(self) -> bool {
|
||||
// The deprecated TotallyNotJson module is not automatically imported.
|
||||
self.is_builtin() && self != ModuleId::JSON
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ModuleId {
|
||||
|
@ -448,6 +462,22 @@ impl<'a, T> PackageQualified<'a, T> {
|
|||
PackageQualified::Qualified(_, name) => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn package_shorthand(&self) -> Option<&'a str> {
|
||||
match self {
|
||||
PackageQualified::Unqualified(_) => None,
|
||||
PackageQualified::Qualified(package, _) => Some(package),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_module<B>(&self, f: impl FnOnce(&T) -> B) -> PackageQualified<'a, B> {
|
||||
match self {
|
||||
PackageQualified::Unqualified(name) => PackageQualified::Unqualified(f(name)),
|
||||
PackageQualified::Qualified(package, name) => {
|
||||
PackageQualified::Qualified(package, f(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -593,6 +623,68 @@ impl ModuleIds {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ScopeModules {
|
||||
modules: VecMap<ModuleName, ModuleId>,
|
||||
sources: VecMap<ModuleId, ScopeModuleSource>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ScopeModuleSource {
|
||||
Builtin,
|
||||
Current,
|
||||
Import(Region),
|
||||
}
|
||||
|
||||
impl ScopeModules {
|
||||
pub fn get_id(&self, module_name: &ModuleName) -> Option<ModuleId> {
|
||||
self.modules.get(module_name).copied()
|
||||
}
|
||||
|
||||
pub fn has_id(&self, module_id: ModuleId) -> bool {
|
||||
self.sources.contains_key(&module_id)
|
||||
}
|
||||
|
||||
pub fn available_names(&self) -> impl Iterator<Item = &ModuleName> {
|
||||
self.modules.keys()
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
module_name: ModuleName,
|
||||
module_id: ModuleId,
|
||||
region: Region,
|
||||
) -> Result<(), ScopeModuleSource> {
|
||||
if let Some(existing_module_id) = self.modules.get(&module_name) {
|
||||
if *existing_module_id == module_id {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Err(*self.sources.get(existing_module_id).unwrap());
|
||||
}
|
||||
|
||||
self.modules.insert(module_name, module_id);
|
||||
self.sources
|
||||
.insert(module_id, ScopeModuleSource::Import(region));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
debug_assert_eq!(self.modules.len(), self.sources.len());
|
||||
self.modules.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
debug_assert_eq!(self.modules.is_empty(), self.sources.is_empty());
|
||||
self.modules.is_empty()
|
||||
}
|
||||
|
||||
pub fn truncate(&mut self, len: usize) {
|
||||
self.modules.truncate(len);
|
||||
self.sources.truncate(len);
|
||||
}
|
||||
}
|
||||
|
||||
/// An ID that is assigned to interned string identifiers within a module.
|
||||
/// By turning these strings into numbers, post-canonicalization processes
|
||||
/// like unification and optimization can run a lot faster.
|
||||
|
@ -697,6 +789,13 @@ impl IdentIds {
|
|||
pub fn is_empty(&self) -> bool {
|
||||
self.interner.is_empty()
|
||||
}
|
||||
|
||||
pub fn exposed_values(&self) -> Vec<Lowercase> {
|
||||
self.ident_strs()
|
||||
.filter(|(_, ident)| ident.starts_with(|c: char| c.is_lowercase()))
|
||||
.map(|(_, ident)| Lowercase::from(ident))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
|
@ -876,7 +975,6 @@ macro_rules! define_builtins {
|
|||
module_id.register_debug_idents(&ident_ids);
|
||||
}
|
||||
|
||||
|
||||
exposed_idents_by_module.insert(
|
||||
module_id,
|
||||
ident_ids
|
||||
|
@ -927,6 +1025,32 @@ macro_rules! define_builtins {
|
|||
}
|
||||
}
|
||||
|
||||
impl ScopeModules {
|
||||
pub fn new(home_id: ModuleId, home_name: ModuleName) -> Self {
|
||||
// +1 because the user will be compiling at least 1 non-builtin module!
|
||||
let capacity = $total + 1;
|
||||
|
||||
let mut modules = VecMap::with_capacity(capacity);
|
||||
let mut sources = VecMap::with_capacity(capacity);
|
||||
|
||||
modules.insert(home_name, home_id);
|
||||
sources.insert(home_id, ScopeModuleSource::Current);
|
||||
|
||||
let mut insert_both = |id: ModuleId, name_str: &'static str| {
|
||||
let name: ModuleName = name_str.into();
|
||||
|
||||
modules.insert(name, id);
|
||||
sources.insert(id, ScopeModuleSource::Builtin);
|
||||
};
|
||||
|
||||
$(
|
||||
insert_both(ModuleId::$module_const, $module_name);
|
||||
)+
|
||||
|
||||
ScopeModules { modules, sources }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for PackageModuleIds<'a> {
|
||||
fn default() -> Self {
|
||||
// +1 because the user will be compiling at least 1 non-builtin module!
|
||||
|
@ -1016,27 +1140,6 @@ macro_rules! define_builtins {
|
|||
m => roc_error_macros::internal_error!("{:?} is not a builtin module!", m),
|
||||
}
|
||||
}
|
||||
|
||||
/// Symbols that should be added to the default scope, for hints as suggestions of
|
||||
/// names you might want to use.
|
||||
///
|
||||
/// TODO: this is a hack to get tag names to show up in error messages as suggestions,
|
||||
/// really we should be extracting tag names from candidate type aliases in scope.
|
||||
pub fn symbols_in_scope_for_hints() -> VecMap<Ident, (Symbol, Region)> {
|
||||
let mut scope = VecMap::default();
|
||||
|
||||
$(
|
||||
$(
|
||||
$(
|
||||
if $in_scope_for_hints {
|
||||
scope.insert($ident_name.into(), (Symbol::new(ModuleId::$module_const, IdentId($ident_id)), Region::zero()));
|
||||
}
|
||||
)?
|
||||
)*
|
||||
)+
|
||||
|
||||
scope
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1274,6 +1377,10 @@ define_builtins! {
|
|||
162 NUM_F64_TO_PARTS: "f64ToParts"
|
||||
163 NUM_F32_FROM_PARTS: "f32FromParts"
|
||||
164 NUM_F64_FROM_PARTS: "f64FromParts"
|
||||
165 NUM_NAN_F32: "nanF32"
|
||||
166 NUM_NAN_F64: "nanF64"
|
||||
167 NUM_INFINITY_F32: "infinityF32"
|
||||
168 NUM_INFINITY_F64: "infinityF64"
|
||||
}
|
||||
4 BOOL: "Bool" => {
|
||||
0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias
|
||||
|
@ -1431,18 +1538,17 @@ define_builtins! {
|
|||
86 LIST_WALK_WITH_INDEX_UNTIL: "walkWithIndexUntil"
|
||||
87 LIST_CLONE: "clone"
|
||||
88 LIST_LEN_USIZE: "lenUsize"
|
||||
89 LIST_CONCAT_UTF8: "concatUtf8"
|
||||
}
|
||||
7 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias
|
||||
1 RESULT_OK: "Ok" in_scope_for_hints=true // Result.Result a e = [Ok a, Err e]
|
||||
2 RESULT_ERR: "Err" in_scope_for_hints=true // Result.Result a e = [Ok a, Err e]
|
||||
1 RESULT_IS_ERR: "isErr"
|
||||
2 RESULT_ON_ERR: "onErr"
|
||||
3 RESULT_MAP: "map"
|
||||
4 RESULT_MAP_ERR: "mapErr"
|
||||
5 RESULT_WITH_DEFAULT: "withDefault"
|
||||
6 RESULT_TRY: "try"
|
||||
7 RESULT_IS_OK: "isOk"
|
||||
8 RESULT_IS_ERR: "isErr"
|
||||
9 RESULT_ON_ERR: "onErr"
|
||||
}
|
||||
8 DICT: "Dict" => {
|
||||
0 DICT_DICT: "Dict" exposed_type=true // the Dict.Dict type alias
|
||||
|
|
|
@ -415,7 +415,7 @@ fn eq_tag_union_help<'a>(
|
|||
};
|
||||
|
||||
//
|
||||
// combine all the statments
|
||||
// combine all the statements
|
||||
//
|
||||
let compare_values = tag_id_a_stmt(root.arena.alloc(
|
||||
//
|
||||
|
|
|
@ -1539,6 +1539,7 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC {
|
|||
StrGetUnsafe | ListGetUnsafe => RC::NoRc,
|
||||
ListConcat => RC::Rc,
|
||||
StrConcat => RC::Rc,
|
||||
ListConcatUtf8 => RC::Rc,
|
||||
StrSubstringUnsafe => RC::Rc,
|
||||
StrReserve => RC::Rc,
|
||||
StrTrim => RC::Rc,
|
||||
|
|
|
@ -1288,6 +1288,7 @@ pub(crate) fn lowlevel_borrow_signature(op: LowLevel) -> &'static [Ownership] {
|
|||
StrGetUnsafe | ListGetUnsafe => &[BORROWED, IRRELEVANT],
|
||||
ListConcat => &[OWNED, OWNED],
|
||||
StrConcat => &[OWNED, BORROWED],
|
||||
ListConcatUtf8 => &[OWNED, BORROWED],
|
||||
StrSubstringUnsafe => &[OWNED, IRRELEVANT, IRRELEVANT],
|
||||
StrReserve => &[OWNED, IRRELEVANT],
|
||||
StrTrim => &[OWNED],
|
||||
|
|
|
@ -185,6 +185,7 @@ impl<'r, 'a> Iterator for PatternBindingIter<'r, 'a> {
|
|||
List {
|
||||
element_layout,
|
||||
elements,
|
||||
opt_rest,
|
||||
..
|
||||
} => {
|
||||
let stack = elements
|
||||
|
@ -193,7 +194,11 @@ impl<'r, 'a> Iterator for PatternBindingIter<'r, 'a> {
|
|||
.rev()
|
||||
.collect();
|
||||
*self = Stack(stack);
|
||||
self.next()
|
||||
|
||||
match opt_rest {
|
||||
Some((_, Some(rest_sym))) => (*rest_sym, layout).into(),
|
||||
_ => self.next(),
|
||||
}
|
||||
}
|
||||
IntLiteral(_, _)
|
||||
| FloatLiteral(_, _)
|
||||
|
@ -236,11 +241,16 @@ impl<'r, 'a> Iterator for PatternBindingIter<'r, 'a> {
|
|||
List {
|
||||
element_layout,
|
||||
elements,
|
||||
opt_rest,
|
||||
..
|
||||
} => {
|
||||
stack.extend(
|
||||
elements.iter().map(|p| (Pat(p), *element_layout)).rev(),
|
||||
);
|
||||
|
||||
if let Some((_, Some(rest_sym))) = opt_rest {
|
||||
return (*rest_sym, layout).into();
|
||||
}
|
||||
}
|
||||
IntLiteral(_, _)
|
||||
| FloatLiteral(_, _)
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
hash::{BuildHasher, Hasher},
|
||||
marker::PhantomData,
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{cell::RefCell, hash::BuildHasher, marker::PhantomData, sync::Arc};
|
||||
|
||||
use bumpalo::Bump;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
|
@ -591,12 +586,8 @@ struct LockedGlobalInterner<'a, 'r> {
|
|||
///
|
||||
/// This uses the [default_hasher], so interner maps should also rely on [default_hasher].
|
||||
fn hash<V: std::hash::Hash>(val: V) -> u64 {
|
||||
let mut state = roc_collections::all::BuildHasher::default().build_hasher();
|
||||
val.hash(&mut state);
|
||||
|
||||
// clippy suggests a stylistic improvement but the suggested fix doesn't seem to work out
|
||||
#[allow(clippy::manual_hash_one)]
|
||||
state.finish()
|
||||
let hasher = roc_collections::all::BuildHasher::default();
|
||||
hasher.hash_one(&val)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue