Merge branch 'main' of github.com:roc-lang/roc into rust-1-77-2-upgrade

This commit is contained in:
Anton-4 2024-06-18 18:47:09 +02:00
commit fb7fa99b2c
No known key found for this signature in database
GPG key ID: 0971D718C0A9B937
631 changed files with 21948 additions and 16419 deletions

View file

@ -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

View file

@ -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,
)

View file

@ -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.

View file

@ -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));
}

View file

@ -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

View file

@ -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.
##

View file

@ -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

View file

@ -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.
##

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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,

View file

@ -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 }

View file

@ -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,
] {}

View file

@ -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";

View file

@ -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" }

View file

@ -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());

View file

@ -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,

View file

@ -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"),
}
}

View file

@ -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,
));

View file

@ -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,

View file

@ -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)
}

View file

@ -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!(),
}
}

View file

@ -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;

View file

@ -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,

View file

@ -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);

View file

@ -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)
}
}

View file

@ -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();

View 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(&current_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(&current_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,
}
}

View file

@ -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,
};

View 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));
}
}

View file

@ -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"

View file

@ -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

View file

@ -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![],
}
}

View file

@ -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

View file

@ -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 {

View file

@ -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) {

View file

@ -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(&params.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);
}

View file

@ -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);

View file

@ -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(

View file

@ -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,

View file

@ -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

View file

@ -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));

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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,
};

View file

@ -0,0 +1,9 @@
platform "test-platform"
requires {} { main : * }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : {} -> {}
mainForHost = \{} -> {}

View file

@ -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!(

View file

@ -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

View file

@ -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

View file

@ -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
}
}
})
}

View file

@ -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)]

View file

@ -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
}

View file

@ -1,6 +1,8 @@
interface Dep1
exposes [three, str, Unit, Identity, one, two]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3Blah exposing [foo]
one = 1

View file

@ -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

View file

@ -1,6 +0,0 @@
interface Dep3.Other
exposes [foo, bar]
imports []
foo = "foo from Dep3.Other"
bar = "bar from Dep3.Other"

View file

@ -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

View file

@ -0,0 +1,6 @@
interface Dep3Other
exposes [foo, bar]
imports []
foo = "foo from Dep3Other"
bar = "bar from Dep3Other"

View file

@ -1,7 +1,8 @@
interface ImportAlias
exposes [unit]
imports [Dep1]
imports []
import Dep1
unit : Dep1.Unit
unit = Unit

View file

@ -1,5 +1,7 @@
interface OneDep
exposes [str]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3Blah exposing [foo]
str = foo

View file

@ -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

View file

@ -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

View file

@ -1,5 +0,0 @@
interface IngestedFile
exposes [str]
imports ["IngestedFile.roc" as foo : Str]
str = foo

View file

@ -1,6 +1,8 @@
interface Dep1
exposes [three, str, Unit, Identity, one, two]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3 exposing [foo]
one = 1

View file

@ -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

View file

@ -1,4 +1,4 @@
interface Dep3.Blah
interface Dep3
exposes [one, two, foo, bar]
imports []

View file

@ -0,0 +1,8 @@
interface ExposedUsedOutsideScope exposes [good, bad] imports []
good =
import Dep2 exposing [two]
two
bad =
two

View file

@ -1,7 +1,8 @@
interface ImportAlias
exposes [unit]
imports [Dep1]
imports []
import Dep1
unit : Dep1.Unit
unit = Unit

View file

@ -0,0 +1,12 @@
interface ImportInsideDef exposes [dep1Str, dep2TwoDobuled] imports []
dep1Str =
import Dep1
Dep1.str
dep2TwoDobuled =
2
* (
import Dep2 exposing [two]
two
)

View file

@ -0,0 +1,8 @@
interface ImportUsedOutsideScope exposes [good, bad] imports []
good =
import Dep2
Dep2.two
bad =
Dep2.two

View 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

View file

@ -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 ""

View file

@ -1,5 +1,7 @@
interface OneDep
exposes [str]
imports [Dep3.Blah.{ foo }]
imports []
import Dep3 exposing [foo]
str = foo

View file

@ -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

View file

@ -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

View file

@ -1,6 +1,8 @@
interface MissingDep
exposes [unit]
imports [ThisFileIsMissing]
imports []
import ThisFileIsMissing
Unit : [Unit]

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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(
//

View file

@ -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,

View file

@ -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],

View file

@ -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(_, _)

View file

@ -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