mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
Merge branch 'trunk' into 3378
This commit is contained in:
commit
22f02984d3
55 changed files with 1740 additions and 813 deletions
|
@ -1100,7 +1100,7 @@ fn link_windows(
|
|||
todo!("Add windows support to the surgical linker. See issue #2608.")
|
||||
}
|
||||
|
||||
pub fn module_to_dylib(
|
||||
pub fn llvm_module_to_dylib(
|
||||
module: &inkwell::module::Module,
|
||||
target: &Triple,
|
||||
opt_level: OptLevel,
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
interface Dict
|
||||
exposes
|
||||
[
|
||||
empty,
|
||||
single,
|
||||
get,
|
||||
walk,
|
||||
insert,
|
||||
len,
|
||||
remove,
|
||||
contains,
|
||||
keys,
|
||||
values,
|
||||
union,
|
||||
intersection,
|
||||
difference,
|
||||
]
|
||||
imports
|
||||
[
|
||||
Bool.{ Bool },
|
||||
]
|
||||
exposes [
|
||||
empty,
|
||||
single,
|
||||
get,
|
||||
walk,
|
||||
insert,
|
||||
len,
|
||||
remove,
|
||||
contains,
|
||||
keys,
|
||||
values,
|
||||
union,
|
||||
intersection,
|
||||
difference,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool },
|
||||
]
|
||||
|
||||
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values.
|
||||
##
|
||||
|
|
|
@ -1,35 +1,33 @@
|
|||
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,
|
||||
custom,
|
||||
appendWith,
|
||||
append,
|
||||
toBytes,
|
||||
]
|
||||
imports
|
||||
[]
|
||||
exposes [
|
||||
Encoder,
|
||||
Encoding,
|
||||
toEncoder,
|
||||
EncoderFormatting,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
u128,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
i128,
|
||||
f32,
|
||||
f64,
|
||||
dec,
|
||||
bool,
|
||||
string,
|
||||
list,
|
||||
record,
|
||||
tag,
|
||||
custom,
|
||||
appendWith,
|
||||
append,
|
||||
toBytes,
|
||||
]
|
||||
imports []
|
||||
|
||||
Encoder fmt := List U8, fmt -> List U8 | fmt has EncoderFormatting
|
||||
|
||||
|
|
|
@ -1,35 +1,33 @@
|
|||
interface Json
|
||||
exposes
|
||||
[
|
||||
Json,
|
||||
format,
|
||||
]
|
||||
imports
|
||||
[
|
||||
Encode.{
|
||||
Encoder,
|
||||
custom,
|
||||
appendWith,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
u128,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
i128,
|
||||
f32,
|
||||
f64,
|
||||
dec,
|
||||
bool,
|
||||
string,
|
||||
list,
|
||||
record,
|
||||
tag,
|
||||
},
|
||||
]
|
||||
exposes [
|
||||
Json,
|
||||
format,
|
||||
]
|
||||
imports [
|
||||
Encode.{
|
||||
Encoder,
|
||||
custom,
|
||||
appendWith,
|
||||
u8,
|
||||
u16,
|
||||
u32,
|
||||
u64,
|
||||
u128,
|
||||
i8,
|
||||
i16,
|
||||
i32,
|
||||
i64,
|
||||
i128,
|
||||
f32,
|
||||
f64,
|
||||
dec,
|
||||
bool,
|
||||
string,
|
||||
list,
|
||||
record,
|
||||
tag,
|
||||
},
|
||||
]
|
||||
|
||||
Json := {}
|
||||
|
||||
|
|
|
@ -1,63 +1,61 @@
|
|||
interface List
|
||||
exposes
|
||||
[
|
||||
isEmpty,
|
||||
get,
|
||||
set,
|
||||
replace,
|
||||
append,
|
||||
map,
|
||||
len,
|
||||
withCapacity,
|
||||
iterate,
|
||||
walkBackwards,
|
||||
concat,
|
||||
first,
|
||||
single,
|
||||
repeat,
|
||||
reverse,
|
||||
prepend,
|
||||
join,
|
||||
keepIf,
|
||||
contains,
|
||||
sum,
|
||||
walk,
|
||||
last,
|
||||
keepOks,
|
||||
keepErrs,
|
||||
mapWithIndex,
|
||||
map2,
|
||||
map3,
|
||||
product,
|
||||
walkUntil,
|
||||
range,
|
||||
sortWith,
|
||||
drop,
|
||||
swap,
|
||||
dropAt,
|
||||
dropLast,
|
||||
min,
|
||||
max,
|
||||
map4,
|
||||
dropFirst,
|
||||
joinMap,
|
||||
any,
|
||||
takeFirst,
|
||||
takeLast,
|
||||
find,
|
||||
findIndex,
|
||||
sublist,
|
||||
intersperse,
|
||||
split,
|
||||
all,
|
||||
dropIf,
|
||||
sortAsc,
|
||||
sortDesc,
|
||||
]
|
||||
imports
|
||||
[
|
||||
Bool.{ Bool },
|
||||
]
|
||||
exposes [
|
||||
isEmpty,
|
||||
get,
|
||||
set,
|
||||
replace,
|
||||
append,
|
||||
map,
|
||||
len,
|
||||
withCapacity,
|
||||
iterate,
|
||||
walkBackwards,
|
||||
concat,
|
||||
first,
|
||||
single,
|
||||
repeat,
|
||||
reverse,
|
||||
prepend,
|
||||
join,
|
||||
keepIf,
|
||||
contains,
|
||||
sum,
|
||||
walk,
|
||||
last,
|
||||
keepOks,
|
||||
keepErrs,
|
||||
mapWithIndex,
|
||||
map2,
|
||||
map3,
|
||||
product,
|
||||
walkUntil,
|
||||
range,
|
||||
sortWith,
|
||||
drop,
|
||||
swap,
|
||||
dropAt,
|
||||
dropLast,
|
||||
min,
|
||||
max,
|
||||
map4,
|
||||
dropFirst,
|
||||
joinMap,
|
||||
any,
|
||||
takeFirst,
|
||||
takeLast,
|
||||
find,
|
||||
findIndex,
|
||||
sublist,
|
||||
intersperse,
|
||||
split,
|
||||
all,
|
||||
dropIf,
|
||||
sortAsc,
|
||||
sortDesc,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool },
|
||||
]
|
||||
|
||||
## Types
|
||||
## A sequential list of values.
|
||||
|
@ -373,17 +371,11 @@ contains = \list, needle ->
|
|||
## `fold`, `foldLeft`, or `foldl`.
|
||||
walk : List elem, state, (state, elem -> state) -> state
|
||||
walk = \list, state, func ->
|
||||
walkHelp list state func 0 (len list)
|
||||
walkHelp = \currentState, element -> Continue (func currentState element)
|
||||
|
||||
## internal helper
|
||||
walkHelp : List elem, state, (state, elem -> state), Nat, Nat -> state
|
||||
walkHelp = \list, state, f, index, length ->
|
||||
if index < length then
|
||||
nextState = f state (getUnsafe list index)
|
||||
|
||||
walkHelp list nextState f (index + 1) length
|
||||
else
|
||||
state
|
||||
when List.iterate list state walkHelp is
|
||||
Continue newState -> newState
|
||||
Break void -> List.unreachable void
|
||||
|
||||
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
|
||||
## `fold`, `foldRight`, or `foldr`.
|
||||
|
@ -841,3 +833,6 @@ iterHelp = \list, state, f, index, length ->
|
|||
Break b
|
||||
else
|
||||
Continue state
|
||||
|
||||
## useful for typechecking guaranteed-unreachable cases
|
||||
unreachable : [] -> a
|
||||
|
|
|
@ -1,153 +1,151 @@
|
|||
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,
|
||||
Nat,
|
||||
Dec,
|
||||
F32,
|
||||
F64,
|
||||
Natural,
|
||||
Decimal,
|
||||
Binary32,
|
||||
Binary64,
|
||||
abs,
|
||||
neg,
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
isLt,
|
||||
isLte,
|
||||
isGt,
|
||||
isGte,
|
||||
sin,
|
||||
cos,
|
||||
tan,
|
||||
atan,
|
||||
acos,
|
||||
asin,
|
||||
isZero,
|
||||
isEven,
|
||||
isOdd,
|
||||
toFrac,
|
||||
isPositive,
|
||||
isNegative,
|
||||
rem,
|
||||
remChecked,
|
||||
div,
|
||||
divChecked,
|
||||
sqrt,
|
||||
sqrtChecked,
|
||||
log,
|
||||
logChecked,
|
||||
round,
|
||||
ceiling,
|
||||
floor,
|
||||
compare,
|
||||
pow,
|
||||
powInt,
|
||||
addWrap,
|
||||
addChecked,
|
||||
addSaturated,
|
||||
bitwiseAnd,
|
||||
bitwiseXor,
|
||||
bitwiseOr,
|
||||
shiftLeftBy,
|
||||
shiftRightBy,
|
||||
shiftRightZfBy,
|
||||
subWrap,
|
||||
subChecked,
|
||||
subSaturated,
|
||||
mulWrap,
|
||||
mulSaturated,
|
||||
mulChecked,
|
||||
intCast,
|
||||
bytesToU16,
|
||||
bytesToU32,
|
||||
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,
|
||||
toNat,
|
||||
toNatChecked,
|
||||
toF32,
|
||||
toF32Checked,
|
||||
toF64,
|
||||
toF64Checked,
|
||||
]
|
||||
imports
|
||||
[
|
||||
Bool.{ Bool },
|
||||
]
|
||||
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,
|
||||
Nat,
|
||||
Dec,
|
||||
F32,
|
||||
F64,
|
||||
Natural,
|
||||
Decimal,
|
||||
Binary32,
|
||||
Binary64,
|
||||
abs,
|
||||
neg,
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
isLt,
|
||||
isLte,
|
||||
isGt,
|
||||
isGte,
|
||||
sin,
|
||||
cos,
|
||||
tan,
|
||||
atan,
|
||||
acos,
|
||||
asin,
|
||||
isZero,
|
||||
isEven,
|
||||
isOdd,
|
||||
toFrac,
|
||||
isPositive,
|
||||
isNegative,
|
||||
rem,
|
||||
remChecked,
|
||||
div,
|
||||
divChecked,
|
||||
sqrt,
|
||||
sqrtChecked,
|
||||
log,
|
||||
logChecked,
|
||||
round,
|
||||
ceiling,
|
||||
floor,
|
||||
compare,
|
||||
pow,
|
||||
powInt,
|
||||
addWrap,
|
||||
addChecked,
|
||||
addSaturated,
|
||||
bitwiseAnd,
|
||||
bitwiseXor,
|
||||
bitwiseOr,
|
||||
shiftLeftBy,
|
||||
shiftRightBy,
|
||||
shiftRightZfBy,
|
||||
subWrap,
|
||||
subChecked,
|
||||
subSaturated,
|
||||
mulWrap,
|
||||
mulSaturated,
|
||||
mulChecked,
|
||||
intCast,
|
||||
bytesToU16,
|
||||
bytesToU32,
|
||||
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,
|
||||
toNat,
|
||||
toNatChecked,
|
||||
toF32,
|
||||
toF32Checked,
|
||||
toF64,
|
||||
toF64Checked,
|
||||
]
|
||||
imports [
|
||||
Bool.{ Bool },
|
||||
]
|
||||
|
||||
## Represents a number that could be either an [Int] or a [Frac].
|
||||
##
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
interface Set
|
||||
exposes
|
||||
[
|
||||
empty,
|
||||
single,
|
||||
walk,
|
||||
insert,
|
||||
len,
|
||||
remove,
|
||||
contains,
|
||||
toList,
|
||||
fromList,
|
||||
union,
|
||||
intersection,
|
||||
difference,
|
||||
]
|
||||
exposes [
|
||||
empty,
|
||||
single,
|
||||
walk,
|
||||
insert,
|
||||
len,
|
||||
remove,
|
||||
contains,
|
||||
toList,
|
||||
fromList,
|
||||
union,
|
||||
intersection,
|
||||
difference,
|
||||
]
|
||||
imports [List, Bool.{ Bool }, Dict.{ values }]
|
||||
|
||||
## An empty set.
|
||||
|
|
|
@ -1,47 +1,46 @@
|
|||
interface Str
|
||||
exposes
|
||||
[
|
||||
Utf8Problem,
|
||||
Utf8ByteProblem,
|
||||
concat,
|
||||
isEmpty,
|
||||
joinWith,
|
||||
split,
|
||||
repeat,
|
||||
countGraphemes,
|
||||
countUtf8Bytes,
|
||||
startsWithScalar,
|
||||
toUtf8,
|
||||
fromUtf8,
|
||||
fromUtf8Range,
|
||||
startsWith,
|
||||
endsWith,
|
||||
trim,
|
||||
trimLeft,
|
||||
trimRight,
|
||||
toDec,
|
||||
toF64,
|
||||
toF32,
|
||||
toNat,
|
||||
toU128,
|
||||
toI128,
|
||||
toU64,
|
||||
toI64,
|
||||
toU32,
|
||||
toI32,
|
||||
toU16,
|
||||
toI16,
|
||||
toU8,
|
||||
toI8,
|
||||
toScalars,
|
||||
splitFirst,
|
||||
splitLast,
|
||||
walkUtf8WithIndex,
|
||||
reserve,
|
||||
appendScalar,
|
||||
walkScalars,
|
||||
walkScalarsUntil,
|
||||
]
|
||||
exposes [
|
||||
Utf8Problem,
|
||||
Utf8ByteProblem,
|
||||
concat,
|
||||
isEmpty,
|
||||
joinWith,
|
||||
split,
|
||||
repeat,
|
||||
countGraphemes,
|
||||
countUtf8Bytes,
|
||||
startsWithScalar,
|
||||
toUtf8,
|
||||
fromUtf8,
|
||||
fromUtf8Range,
|
||||
startsWith,
|
||||
endsWith,
|
||||
trim,
|
||||
trimLeft,
|
||||
trimRight,
|
||||
toDec,
|
||||
toF64,
|
||||
toF32,
|
||||
toNat,
|
||||
toU128,
|
||||
toI128,
|
||||
toU64,
|
||||
toI64,
|
||||
toU32,
|
||||
toI32,
|
||||
toU16,
|
||||
toI16,
|
||||
toU8,
|
||||
toI8,
|
||||
toScalars,
|
||||
splitFirst,
|
||||
splitLast,
|
||||
walkUtf8WithIndex,
|
||||
reserve,
|
||||
appendScalar,
|
||||
walkScalars,
|
||||
walkScalarsUntil,
|
||||
]
|
||||
imports [Bool.{ Bool }, Result.{ Result }]
|
||||
|
||||
## # Types
|
||||
|
@ -118,15 +117,14 @@ interface Str
|
|||
## and you can use it as many times as you like inside a string. The name
|
||||
## between the parentheses must refer to a `Str` value that is currently in
|
||||
## scope, and it must be a name - it can't be an arbitrary expression like a function call.
|
||||
Utf8ByteProblem :
|
||||
[
|
||||
InvalidStartByte,
|
||||
UnexpectedEndOfSequence,
|
||||
ExpectedContinuation,
|
||||
OverlongEncoding,
|
||||
CodepointTooLarge,
|
||||
EncodesSurrogateHalf,
|
||||
]
|
||||
Utf8ByteProblem : [
|
||||
InvalidStartByte,
|
||||
UnexpectedEndOfSequence,
|
||||
ExpectedContinuation,
|
||||
OverlongEncoding,
|
||||
CodepointTooLarge,
|
||||
EncodesSurrogateHalf,
|
||||
]
|
||||
|
||||
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
|
||||
|
||||
|
|
|
@ -107,6 +107,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
STR_TO_I16 => str_to_num,
|
||||
STR_TO_U8 => str_to_num,
|
||||
STR_TO_I8 => str_to_num,
|
||||
LIST_UNREACHABLE => roc_unreachable,
|
||||
LIST_LEN => list_len,
|
||||
LIST_WITH_CAPACITY => list_with_capacity,
|
||||
LIST_GET_UNSAFE => list_get_unsafe,
|
||||
|
@ -2359,6 +2360,11 @@ fn list_prepend(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
)
|
||||
}
|
||||
|
||||
/// List.unreachable : [] -> a
|
||||
fn roc_unreachable(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_1(symbol, LowLevel::Unreachable, var_store)
|
||||
}
|
||||
|
||||
/// List.map : List before, (before -> after) -> List after
|
||||
fn list_map(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_2(symbol, LowLevel::ListMap, var_store)
|
||||
|
|
|
@ -104,6 +104,9 @@ flags! {
|
|||
/// Prints debug information during the alias analysis pass.
|
||||
ROC_DEBUG_ALIAS_ANALYSIS
|
||||
|
||||
/// Print to stderr when a runtime error function is generated.
|
||||
ROC_PRINT_RUNTIME_ERROR_GEN
|
||||
|
||||
// ===LLVM Gen===
|
||||
|
||||
/// Prints LLVM function verification output.
|
||||
|
|
|
@ -74,7 +74,7 @@ impl<'a> Formattable for TypeDef<'a> {
|
|||
buf.push_str(" :");
|
||||
buf.spaces(1);
|
||||
|
||||
ann.format(buf, indent + INDENT)
|
||||
ann.format(buf, indent)
|
||||
}
|
||||
Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
|
@ -113,7 +113,7 @@ impl<'a> Formattable for TypeDef<'a> {
|
|||
buf.indent(indent + INDENT);
|
||||
}
|
||||
|
||||
ann.format(buf, indent + INDENT);
|
||||
ann.format(buf, indent);
|
||||
|
||||
if let Some(derived) = derived {
|
||||
if !make_multiline {
|
||||
|
|
|
@ -2,7 +2,10 @@ use crate::annotation::{Formattable, Newlines, Parens};
|
|||
use crate::collection::{fmt_collection, Braces};
|
||||
use crate::def::fmt_defs;
|
||||
use crate::pattern::fmt_pattern;
|
||||
use crate::spaces::{count_leading_newlines, fmt_comments_only, fmt_spaces, NewlineAt, INDENT};
|
||||
use crate::spaces::{
|
||||
count_leading_newlines, fmt_comments_only, fmt_spaces, fmt_spaces_no_blank_lines, NewlineAt,
|
||||
INDENT,
|
||||
};
|
||||
use crate::Buf;
|
||||
use roc_module::called_via::{self, BinOp};
|
||||
use roc_parse::ast::{
|
||||
|
@ -330,7 +333,7 @@ impl<'a> Formattable for Expr<'a> {
|
|||
match &ret.value {
|
||||
SpaceBefore(sub_expr, spaces) => {
|
||||
let empty_line_before_return = empty_line_before_expr(&ret.value);
|
||||
let has_inline_comment = with_inline_comment(&ret.value);
|
||||
let has_inline_comment = has_line_comment_before(&ret.value);
|
||||
|
||||
if has_inline_comment {
|
||||
buf.spaces(1);
|
||||
|
@ -369,7 +372,7 @@ impl<'a> Formattable for Expr<'a> {
|
|||
}
|
||||
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
|
||||
List(items) => fmt_collection(buf, indent, Braces::Square, *items, Newlines::No),
|
||||
BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
|
||||
BinOps(lefts, right) => fmt_binops(buf, lefts, right, false, parens, indent),
|
||||
UnaryOp(sub_expr, unary_op) => {
|
||||
buf.indent(indent);
|
||||
match &unary_op.value {
|
||||
|
@ -405,20 +408,7 @@ fn starts_with_newline(expr: &Expr) -> bool {
|
|||
|
||||
match expr {
|
||||
SpaceBefore(_, comment_or_newline) => {
|
||||
if !comment_or_newline.is_empty() {
|
||||
// safe because we check the length before
|
||||
comment_or_newline.get(0).unwrap().is_newline()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
SpaceAfter(_, comment_or_newline) => {
|
||||
if !(**comment_or_newline).is_empty() {
|
||||
// safe because we check the length before
|
||||
comment_or_newline.get(0).unwrap().is_newline()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
matches!(comment_or_newline.first(), Some(CommentOrNewline::Newline))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
|
@ -530,34 +520,34 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
|
|||
buf.push('"');
|
||||
}
|
||||
|
||||
fn fmt_bin_ops<'a, 'buf>(
|
||||
fn fmt_binops<'a, 'buf>(
|
||||
buf: &mut Buf<'buf>,
|
||||
lefts: &'a [(Loc<Expr<'a>>, Loc<BinOp>)],
|
||||
loc_right_side: &'a Loc<Expr<'a>>,
|
||||
part_of_multi_line_bin_ops: bool,
|
||||
part_of_multi_line_binops: bool,
|
||||
apply_needs_parens: Parens,
|
||||
indent: u16,
|
||||
) {
|
||||
let is_multiline = part_of_multi_line_bin_ops
|
||||
let is_multiline = part_of_multi_line_binops
|
||||
|| (&loc_right_side.value).is_multiline()
|
||||
|| lefts.iter().any(|(expr, _)| expr.value.is_multiline());
|
||||
|
||||
let mut curr_indent = indent;
|
||||
|
||||
for (loc_left_side, loc_bin_op) in lefts {
|
||||
let bin_op = loc_bin_op.value;
|
||||
for (loc_left_side, loc_binop) in lefts {
|
||||
let binop = loc_binop.value;
|
||||
|
||||
loc_left_side.format_with_options(buf, apply_needs_parens, Newlines::No, curr_indent);
|
||||
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
buf.ensure_ends_in_newline();
|
||||
curr_indent = indent + INDENT;
|
||||
buf.indent(curr_indent);
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
push_op(buf, bin_op);
|
||||
push_op(buf, binop);
|
||||
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
@ -577,24 +567,23 @@ fn format_spaces<'a, 'buf>(
|
|||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
let format_newlines = newlines == Newlines::Yes;
|
||||
|
||||
if format_newlines {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
} else {
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
match newlines {
|
||||
Newlines::Yes => {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
Newlines::No => {
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_inline_comment<'a>(expr: &'a Expr<'a>) -> bool {
|
||||
fn has_line_comment_before<'a>(expr: &'a Expr<'a>) -> bool {
|
||||
use roc_parse::ast::Expr::*;
|
||||
|
||||
match expr {
|
||||
SpaceBefore(_, spaces) => match spaces.iter().next() {
|
||||
Some(CommentOrNewline::LineComment(_)) => true,
|
||||
Some(_) => false,
|
||||
None => false,
|
||||
},
|
||||
SpaceBefore(_, spaces) => {
|
||||
matches!(spaces.iter().next(), Some(CommentOrNewline::LineComment(_)))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -670,11 +659,22 @@ fn fmt_when<'a, 'buf>(
|
|||
buf.newline();
|
||||
match &expr_below {
|
||||
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
|
||||
// If any of the spaces is a newline, add a newline at the top.
|
||||
// Otherwise leave it as just a comment.
|
||||
let newline_at = if spaces_below_expr
|
||||
.iter()
|
||||
.any(|spaces| matches!(spaces, CommentOrNewline::Newline))
|
||||
{
|
||||
NewlineAt::Top
|
||||
} else {
|
||||
NewlineAt::None
|
||||
};
|
||||
|
||||
expr_above.format(buf, condition_indent);
|
||||
fmt_comments_only(
|
||||
buf,
|
||||
spaces_below_expr.iter(),
|
||||
NewlineAt::Top,
|
||||
newline_at,
|
||||
condition_indent,
|
||||
);
|
||||
buf.newline();
|
||||
|
@ -699,25 +699,35 @@ fn fmt_when<'a, 'buf>(
|
|||
buf.push_str("is");
|
||||
buf.newline();
|
||||
|
||||
let mut it = branches.iter().enumerate().peekable();
|
||||
|
||||
while let Some((branch_index, branch)) = it.next() {
|
||||
for (branch_index, branch) in branches.iter().enumerate() {
|
||||
let expr = &branch.value;
|
||||
let patterns = &branch.patterns;
|
||||
let is_multiline_expr = expr.is_multiline();
|
||||
let is_multiline_patterns = is_when_patterns_multiline(branch);
|
||||
|
||||
for (index, pattern) in patterns.iter().enumerate() {
|
||||
if index == 0 {
|
||||
for (pattern_index, pattern) in patterns.iter().enumerate() {
|
||||
if pattern_index == 0 {
|
||||
match &pattern.value {
|
||||
Pattern::SpaceBefore(sub_pattern, spaces) if branch_index == 0 => {
|
||||
// Never include extra newlines before the first branch.
|
||||
// Instead, write the comments and that's it.
|
||||
Pattern::SpaceBefore(sub_pattern, spaces) => {
|
||||
if branch_index > 0 // Never render newlines before the first branch.
|
||||
&& matches!(spaces.first(), Some(CommentOrNewline::Newline))
|
||||
{
|
||||
buf.ensure_ends_in_newline();
|
||||
}
|
||||
|
||||
// Write comments (which may have been attached to the previous
|
||||
// branch's expr, if there was a previous branch).
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + INDENT);
|
||||
|
||||
if branch_index > 0 {
|
||||
buf.ensure_ends_in_newline();
|
||||
}
|
||||
|
||||
fmt_pattern(buf, sub_pattern, indent + INDENT, Parens::NotNeeded);
|
||||
}
|
||||
other => {
|
||||
buf.ensure_ends_in_newline();
|
||||
|
||||
fmt_pattern(buf, other, indent + INDENT, Parens::NotNeeded);
|
||||
}
|
||||
}
|
||||
|
@ -747,15 +757,16 @@ fn fmt_when<'a, 'buf>(
|
|||
|
||||
buf.push_str(" ->");
|
||||
|
||||
if is_multiline_expr {
|
||||
buf.newline();
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
match expr.value {
|
||||
Expr::SpaceBefore(nested, spaces) => {
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + (INDENT * 2));
|
||||
fmt_spaces_no_blank_lines(buf, spaces.iter(), indent + (INDENT * 2));
|
||||
|
||||
if is_multiline_expr {
|
||||
buf.ensure_ends_in_newline();
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
nested.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
|
@ -764,6 +775,12 @@ fn fmt_when<'a, 'buf>(
|
|||
);
|
||||
}
|
||||
_ => {
|
||||
if is_multiline_expr {
|
||||
buf.ensure_ends_in_newline();
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
expr.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
|
@ -772,10 +789,6 @@ fn fmt_when<'a, 'buf>(
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.newline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -830,17 +843,34 @@ fn fmt_if<'a, 'buf>(
|
|||
|
||||
if is_multiline_condition {
|
||||
match &loc_condition.value {
|
||||
Expr::SpaceBefore(expr_below, spaces_above_expr) => {
|
||||
fmt_comments_only(buf, spaces_above_expr.iter(), NewlineAt::Top, return_indent);
|
||||
Expr::SpaceBefore(expr_below, spaces_before_expr) => {
|
||||
fmt_comments_only(
|
||||
buf,
|
||||
spaces_before_expr.iter(),
|
||||
NewlineAt::Top,
|
||||
return_indent,
|
||||
);
|
||||
buf.newline();
|
||||
|
||||
match &expr_below {
|
||||
Expr::SpaceAfter(expr_above, spaces_below_expr) => {
|
||||
Expr::SpaceAfter(expr_above, spaces_after_expr) => {
|
||||
expr_above.format(buf, return_indent);
|
||||
|
||||
// If any of the spaces is a newline, add a newline at the top.
|
||||
// Otherwise leave it as just a comment.
|
||||
let newline_at = if spaces_after_expr
|
||||
.iter()
|
||||
.any(|spaces| matches!(spaces, CommentOrNewline::Newline))
|
||||
{
|
||||
NewlineAt::Top
|
||||
} else {
|
||||
NewlineAt::None
|
||||
};
|
||||
|
||||
fmt_comments_only(
|
||||
buf,
|
||||
spaces_below_expr.iter(),
|
||||
NewlineAt::Top,
|
||||
spaces_after_expr.iter(),
|
||||
newline_at,
|
||||
return_indent,
|
||||
);
|
||||
buf.newline();
|
||||
|
@ -885,12 +915,18 @@ fn fmt_if<'a, 'buf>(
|
|||
Expr::SpaceAfter(expr_above, spaces_above) => {
|
||||
expr_above.format(buf, return_indent);
|
||||
|
||||
fmt_comments_only(
|
||||
buf,
|
||||
spaces_above.iter(),
|
||||
NewlineAt::Top,
|
||||
return_indent,
|
||||
);
|
||||
// If any of the spaces is a newline, add a newline at the top.
|
||||
// Otherwise leave it as just a comment.
|
||||
let newline_at = if spaces_above
|
||||
.iter()
|
||||
.any(|spaces| matches!(spaces, CommentOrNewline::Newline))
|
||||
{
|
||||
NewlineAt::Top
|
||||
} else {
|
||||
NewlineAt::None
|
||||
};
|
||||
|
||||
fmt_comments_only(buf, spaces_above.iter(), newline_at, return_indent);
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
|
@ -1294,7 +1330,7 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
|
|||
Expr::BinOps(left_side, _) => {
|
||||
left_side
|
||||
.iter()
|
||||
.any(|(_, loc_bin_op)| match loc_bin_op.value {
|
||||
.any(|(_, loc_binop)| match loc_binop.value {
|
||||
BinOp::Caret
|
||||
| BinOp::Star
|
||||
| BinOp::Slash
|
||||
|
|
|
@ -53,7 +53,14 @@ impl<'a> Buf<'a> {
|
|||
|
||||
pub fn push(&mut self, ch: char) {
|
||||
debug_assert!(!self.beginning_of_line);
|
||||
debug_assert!(ch != '\n' && ch != ' ');
|
||||
debug_assert!(
|
||||
ch != '\n',
|
||||
"Don't call buf.push('\\n') - rather, call buf.newline()"
|
||||
);
|
||||
debug_assert!(
|
||||
ch != ' ',
|
||||
"Don't call buf.push(' ') - rather, call buf.spaces(1)"
|
||||
);
|
||||
|
||||
self.flush_spaces();
|
||||
|
||||
|
@ -92,7 +99,10 @@ impl<'a> Buf<'a> {
|
|||
/// Ensures the current buffer ends in a newline, if it didn't already.
|
||||
/// Doesn't add a newline if the buffer already ends in one.
|
||||
pub fn ensure_ends_in_newline(&mut self) {
|
||||
if !self.text.ends_with('\n') {
|
||||
if self.spaces_to_flush > 0 {
|
||||
self.flush_spaces();
|
||||
self.newline();
|
||||
} else if !self.text.ends_with('\n') {
|
||||
self.newline()
|
||||
}
|
||||
}
|
||||
|
@ -110,6 +120,18 @@ impl<'a> Buf<'a> {
|
|||
pub fn fmt_end_of_file(&mut self) {
|
||||
fmt_text_eof(&mut self.text)
|
||||
}
|
||||
|
||||
pub fn ends_with_space(&self) -> bool {
|
||||
self.spaces_to_flush > 0 || self.text.ends_with(' ')
|
||||
}
|
||||
|
||||
pub fn ends_with_newline(&self) -> bool {
|
||||
self.spaces_to_flush == 0 && self.text.ends_with('\n')
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.spaces_to_flush == 0 && self.text.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures the text ends in a newline with no whitespace preceding it.
|
||||
|
|
|
@ -242,13 +242,7 @@ fn fmt_imports<'a, 'buf>(
|
|||
loc_entries: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(
|
||||
buf,
|
||||
indent + INDENT,
|
||||
Braces::Square,
|
||||
loc_entries,
|
||||
Newlines::No,
|
||||
)
|
||||
fmt_collection(buf, indent, Braces::Square, loc_entries, Newlines::No)
|
||||
}
|
||||
|
||||
fn fmt_provides<'a, 'buf>(
|
||||
|
@ -260,13 +254,7 @@ fn fmt_provides<'a, 'buf>(
|
|||
fmt_collection(buf, indent, Braces::Square, loc_exposed_names, Newlines::No);
|
||||
if let Some(loc_provided) = loc_provided_types {
|
||||
fmt_default_spaces(buf, &[], indent);
|
||||
fmt_collection(
|
||||
buf,
|
||||
indent + INDENT,
|
||||
Braces::Curly,
|
||||
loc_provided,
|
||||
Newlines::No,
|
||||
);
|
||||
fmt_collection(buf, indent, Braces::Curly, loc_provided, Newlines::No);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -279,18 +267,12 @@ fn fmt_to<'buf>(buf: &mut Buf<'buf>, to: To, indent: u16) {
|
|||
}
|
||||
}
|
||||
|
||||
fn fmt_exposes<'buf, N: Formattable + Copy>(
|
||||
fn fmt_exposes<'buf, N: Formattable + Copy + core::fmt::Debug>(
|
||||
buf: &mut Buf<'buf>,
|
||||
loc_entries: Collection<'_, Loc<Spaced<'_, N>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(
|
||||
buf,
|
||||
indent + INDENT,
|
||||
Braces::Square,
|
||||
loc_entries,
|
||||
Newlines::No,
|
||||
)
|
||||
fmt_collection(buf, indent, Braces::Square, loc_entries, Newlines::No)
|
||||
}
|
||||
|
||||
pub trait FormatName {
|
||||
|
|
|
@ -3,7 +3,7 @@ use bumpalo::Bump;
|
|||
use roc_module::called_via::{BinOp, UnaryOp};
|
||||
use roc_parse::{
|
||||
ast::{
|
||||
AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Derived, Expr, Has,
|
||||
AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Defs, Derived, Expr, Has,
|
||||
HasClause, Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
|
||||
TypeHeader, ValueDef, WhenBranch,
|
||||
},
|
||||
|
@ -32,26 +32,44 @@ pub fn fmt_default_spaces<'a, 'buf>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Like fmt_spaces, but disallows two consecutive newlines.
|
||||
pub fn fmt_spaces_no_blank_lines<'a, 'buf, I>(buf: &mut Buf<'buf>, spaces: I, indent: u16)
|
||||
where
|
||||
I: Iterator<Item = &'a CommentOrNewline<'a>>,
|
||||
{
|
||||
fmt_spaces_max_consecutive_newlines(buf, spaces, 1, indent)
|
||||
}
|
||||
|
||||
pub fn fmt_spaces<'a, 'buf, I>(buf: &mut Buf<'buf>, spaces: I, indent: u16)
|
||||
where
|
||||
I: Iterator<Item = &'a CommentOrNewline<'a>>,
|
||||
{
|
||||
fmt_spaces_max_consecutive_newlines(buf, spaces, 2, indent)
|
||||
}
|
||||
|
||||
fn fmt_spaces_max_consecutive_newlines<'a, 'buf, I>(
|
||||
buf: &mut Buf<'buf>,
|
||||
spaces: I,
|
||||
max_consecutive_newlines: usize,
|
||||
indent: u16,
|
||||
) where
|
||||
I: Iterator<Item = &'a CommentOrNewline<'a>>,
|
||||
{
|
||||
use self::CommentOrNewline::*;
|
||||
|
||||
// Only ever print two newlines back to back.
|
||||
// (Two newlines renders as one blank line.)
|
||||
let mut consecutive_newlines = 0;
|
||||
|
||||
let mut encountered_comment = false;
|
||||
|
||||
for space in spaces {
|
||||
match space {
|
||||
Newline => {
|
||||
if !encountered_comment && (consecutive_newlines < 2) {
|
||||
if !encountered_comment && (consecutive_newlines < max_consecutive_newlines) {
|
||||
buf.newline();
|
||||
|
||||
// Don't bother incrementing it if we're already over the limit.
|
||||
// There's no upside, and it might eventually overflow,
|
||||
// There's no upside, and it might eventually overflow.
|
||||
consecutive_newlines += 1;
|
||||
}
|
||||
}
|
||||
|
@ -125,6 +143,12 @@ pub fn fmt_comments_only<'a, 'buf, I>(
|
|||
}
|
||||
|
||||
fn fmt_comment<'buf>(buf: &mut Buf<'buf>, comment: &str) {
|
||||
// The '#' in a comment should always be preceded by a newline or a space,
|
||||
// unless it's the very beginning of the buffer.
|
||||
if !buf.is_empty() && !buf.ends_with_space() && !buf.ends_with_newline() {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
buf.push('#');
|
||||
if !comment.starts_with(' ') {
|
||||
buf.spaces(1);
|
||||
|
@ -158,6 +182,12 @@ where
|
|||
}
|
||||
|
||||
fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
|
||||
// The "##" in a doc comment should always be preceded by a newline or a space,
|
||||
// unless it's the very beginning of the buffer.
|
||||
if !buf.is_empty() && !buf.ends_with_space() && !buf.ends_with_newline() {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
buf.push_str("##");
|
||||
if !docs.is_empty() {
|
||||
buf.spaces(1);
|
||||
|
@ -183,27 +213,35 @@ impl<'a> RemoveSpaces<'a> for Ast<'a> {
|
|||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
Ast {
|
||||
module: self.module.remove_spaces(arena),
|
||||
defs: {
|
||||
let mut defs = self.defs.clone();
|
||||
|
||||
for type_def in defs.type_defs.iter_mut() {
|
||||
*type_def = type_def.remove_spaces(arena);
|
||||
}
|
||||
|
||||
for value_def in defs.value_defs.iter_mut() {
|
||||
*value_def = value_def.remove_spaces(arena);
|
||||
}
|
||||
|
||||
for region_def in defs.regions.iter_mut() {
|
||||
*region_def = region_def.remove_spaces(arena);
|
||||
}
|
||||
|
||||
defs
|
||||
},
|
||||
defs: self.defs.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Defs<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let mut defs = self.clone();
|
||||
|
||||
defs.spaces.clear();
|
||||
defs.space_before.clear();
|
||||
defs.space_after.clear();
|
||||
|
||||
for type_def in defs.type_defs.iter_mut() {
|
||||
*type_def = type_def.remove_spaces(arena);
|
||||
}
|
||||
|
||||
for value_def in defs.value_defs.iter_mut() {
|
||||
*value_def = value_def.remove_spaces(arena);
|
||||
}
|
||||
|
||||
for region_def in defs.regions.iter_mut() {
|
||||
*region_def = region_def.remove_spaces(arena);
|
||||
}
|
||||
|
||||
defs
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Module<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match self {
|
||||
|
|
|
@ -199,6 +199,31 @@ mod test_fmt {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_with_trailing_space() {
|
||||
expr_formats_to(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
# first comment{space}
|
||||
x = 0 # second comment{space}
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
space = " ",
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
# first comment
|
||||
x = 0 # second comment
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn def_with_inline_comment() {
|
||||
expr_formats_same(indoc!(
|
||||
|
@ -3516,34 +3541,6 @@ mod test_fmt {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_with_moving_comments() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
when b is
|
||||
1 ->
|
||||
1 # when 1
|
||||
|
||||
# fall through
|
||||
_ ->
|
||||
2
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
when b is
|
||||
1 ->
|
||||
1
|
||||
# when 1
|
||||
# fall through
|
||||
_ ->
|
||||
2
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_line_when_condition_1() {
|
||||
expr_formats_same(indoc!(
|
||||
|
@ -3958,6 +3955,108 @@ mod test_fmt {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_binop_with_comments() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
x = 1
|
||||
+ 1 # comment 1
|
||||
- 1 # comment 2
|
||||
* 1 # comment 3
|
||||
|
||||
x
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
x = 1
|
||||
+ 1 # comment 1
|
||||
* 1 # comment 2
|
||||
|
||||
x
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
x = 1
|
||||
+ 1 # comment
|
||||
|
||||
x
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
x = 1
|
||||
* 1
|
||||
+ 1 # comment
|
||||
|
||||
x
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
x = 1
|
||||
- 1
|
||||
* 1
|
||||
+ 1
|
||||
|
||||
x
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_binop_if_with_comments() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
if
|
||||
x
|
||||
+ 1 # comment 1
|
||||
> 0 # comment 2
|
||||
then
|
||||
y
|
||||
* 2 # comment 3
|
||||
< 1 # comment 4
|
||||
else
|
||||
42
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_binop_when_with_comments() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
when
|
||||
x
|
||||
+ 1 # comment 1
|
||||
> 0 # comment 2
|
||||
is
|
||||
y ->
|
||||
3
|
||||
* 2 # comment 3
|
||||
< 1 # comment 4
|
||||
z ->
|
||||
4
|
||||
/ 5 # comment 5
|
||||
< 1 # comment 6
|
||||
46 # first pattern comment
|
||||
| 95 # alternative comment 1
|
||||
| 126 # alternative comment 2
|
||||
| 150 -> # This comment goes after the ->
|
||||
# This comment is for the expr
|
||||
Str.appendScalar output (Num.toU32 byte)
|
||||
|> Result.withDefault "" # this will never fail
|
||||
_ ->
|
||||
42
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn precedence_conflict_greater_than() {
|
||||
expr_formats_same(indoc!(
|
||||
|
@ -4266,7 +4365,7 @@ mod test_fmt {
|
|||
indoc!(
|
||||
r#"
|
||||
interface Foo exposes [] imports []
|
||||
a = 42# Yay greetings
|
||||
a = 42 # Yay greetings
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -4307,17 +4406,15 @@ mod test_fmt {
|
|||
module_formats_same(indoc!(
|
||||
r#"
|
||||
interface Foo
|
||||
exposes
|
||||
[
|
||||
Stuff,
|
||||
Things,
|
||||
somethingElse,
|
||||
]
|
||||
imports
|
||||
[
|
||||
Blah,
|
||||
Baz.{ stuff, things },
|
||||
]"#
|
||||
exposes [
|
||||
Stuff,
|
||||
Things,
|
||||
somethingElse,
|
||||
]
|
||||
imports [
|
||||
Blah,
|
||||
Baz.{ stuff, things },
|
||||
]"#
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -4341,6 +4438,37 @@ mod test_fmt {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_defs_with_comments() {
|
||||
module_formats_to(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
interface Foo
|
||||
exposes []
|
||||
imports []
|
||||
|
||||
# comment 1{space}
|
||||
def = "" # comment 2{space}
|
||||
# comment 3{space}
|
||||
"#
|
||||
),
|
||||
space = " "
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
interface Foo
|
||||
exposes []
|
||||
imports []
|
||||
|
||||
# comment 1
|
||||
def = "" # comment 2
|
||||
# comment 3
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_tui_package_config() {
|
||||
// At one point this failed to reformat.
|
||||
|
@ -4387,23 +4515,20 @@ mod test_fmt {
|
|||
module_formats_same(indoc!(
|
||||
r#"
|
||||
hosted Foo
|
||||
exposes
|
||||
[
|
||||
Stuff,
|
||||
Things,
|
||||
somethingElse,
|
||||
]
|
||||
imports
|
||||
[
|
||||
Blah,
|
||||
Baz.{ stuff, things },
|
||||
]
|
||||
generates Bar with
|
||||
[
|
||||
map,
|
||||
after,
|
||||
loop,
|
||||
]"#
|
||||
exposes [
|
||||
Stuff,
|
||||
Things,
|
||||
somethingElse,
|
||||
]
|
||||
imports [
|
||||
Blah,
|
||||
Baz.{ stuff, things },
|
||||
]
|
||||
generates Bar with [
|
||||
map,
|
||||
after,
|
||||
loop,
|
||||
]"#
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -4522,11 +4647,11 @@ mod test_fmt {
|
|||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
Expr : [
|
||||
Add Expr Expr,
|
||||
Mul Expr Expr,
|
||||
Val I64,
|
||||
Var I64,
|
||||
]
|
||||
Add Expr Expr,
|
||||
Mul Expr Expr,
|
||||
Val I64,
|
||||
Var I64,
|
||||
]
|
||||
|
||||
Expr"#
|
||||
));
|
||||
|
|
|
@ -3525,15 +3525,31 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
|
|||
|
||||
let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena);
|
||||
|
||||
let it = args.iter().zip(roc_function.get_type().get_param_types());
|
||||
for (arg, fastcc_type) in it {
|
||||
let it = args
|
||||
.iter()
|
||||
.zip(roc_function.get_type().get_param_types())
|
||||
.zip(arguments);
|
||||
for ((arg, fastcc_type), layout) in it {
|
||||
let arg_type = arg.get_type();
|
||||
if arg_type == fastcc_type {
|
||||
// the C and Fast calling conventions agree
|
||||
arguments_for_call.push(*arg);
|
||||
} else {
|
||||
let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type");
|
||||
arguments_for_call.push(cast);
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::List(_)) => {
|
||||
let loaded = env
|
||||
.builder
|
||||
.build_load(arg.into_pointer_value(), "load_list_pointer");
|
||||
let cast =
|
||||
complex_bitcast_check_size(env, loaded, fastcc_type, "to_fastcc_type_1");
|
||||
arguments_for_call.push(cast);
|
||||
}
|
||||
_ => {
|
||||
let cast =
|
||||
complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type_1");
|
||||
arguments_for_call.push(cast);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3657,7 +3673,7 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
|
|||
// the C and Fast calling conventions agree
|
||||
*arg
|
||||
} else {
|
||||
complex_bitcast_check_size(env, *arg, *fastcc_type, "to_fastcc_type")
|
||||
complex_bitcast_check_size(env, *arg, *fastcc_type, "to_fastcc_type_2")
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -4165,6 +4181,89 @@ pub fn build_procedures_return_main<'a, 'ctx, 'env>(
|
|||
promote_to_main_function(env, mod_solutions, entry_point.symbol, entry_point.layout)
|
||||
}
|
||||
|
||||
pub fn build_procedures_expose_expects<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
opt_level: OptLevel,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
|
||||
entry_point: EntryPoint<'a>,
|
||||
) -> Vec<'a, &'a str> {
|
||||
use bumpalo::collections::CollectIn;
|
||||
|
||||
// this is not entirely accurate: it will treat every top-level bool value (turned into a
|
||||
// zero-argument thunk) as an expect.
|
||||
let expects: Vec<_> = procedures
|
||||
.keys()
|
||||
.filter_map(|(symbol, proc_layout)| {
|
||||
if proc_layout.arguments.is_empty() && proc_layout.result == Layout::bool() {
|
||||
Some(*symbol)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect_in(env.arena);
|
||||
|
||||
let mod_solutions = build_procedures_help(
|
||||
env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
Some(Path::new("/tmp/test.ll")),
|
||||
);
|
||||
|
||||
let captures_niche = CapturesNiche::no_niche();
|
||||
|
||||
let top_level = ProcLayout {
|
||||
arguments: &[],
|
||||
result: Layout::bool(),
|
||||
captures_niche,
|
||||
};
|
||||
|
||||
let mut expect_names = Vec::with_capacity_in(expects.len(), env.arena);
|
||||
|
||||
for symbol in expects.iter().copied() {
|
||||
let it = top_level.arguments.iter().copied();
|
||||
let bytes =
|
||||
roc_alias_analysis::func_name_bytes_help(symbol, it, captures_niche, &top_level.result);
|
||||
let func_name = FuncName(&bytes);
|
||||
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
|
||||
|
||||
let mut it = func_solutions.specs();
|
||||
let func_spec = it.next().unwrap();
|
||||
debug_assert!(
|
||||
it.next().is_none(),
|
||||
"we expect only one specialization of this symbol"
|
||||
);
|
||||
|
||||
// NOTE fake layout; it is only used for debug prints
|
||||
let roc_main_fn = function_value_by_func_spec(
|
||||
env,
|
||||
*func_spec,
|
||||
symbol,
|
||||
&[],
|
||||
captures_niche,
|
||||
&Layout::UNIT,
|
||||
);
|
||||
|
||||
let name = roc_main_fn.get_name().to_str().unwrap();
|
||||
|
||||
let expect_name = &format!("Expect_{}", name);
|
||||
let expect_name = env.arena.alloc_str(expect_name);
|
||||
expect_names.push(&*expect_name);
|
||||
|
||||
// Add main to the module.
|
||||
let _ = expose_function_to_host_help_c_abi(
|
||||
env,
|
||||
name,
|
||||
roc_main_fn,
|
||||
top_level.arguments,
|
||||
top_level.result,
|
||||
&format!("Expect_{}", name),
|
||||
);
|
||||
}
|
||||
|
||||
expect_names
|
||||
}
|
||||
|
||||
fn build_procedures_help<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
opt_level: OptLevel,
|
||||
|
@ -6007,6 +6106,20 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
PtrCast | RefCountInc | RefCountDec => {
|
||||
unreachable!("Not used in LLVM backend: {:?}", op);
|
||||
}
|
||||
|
||||
Unreachable => match RocReturn::from_layout(env, layout) {
|
||||
RocReturn::Return => {
|
||||
let basic_type = basic_type_from_layout(env, layout);
|
||||
basic_type.const_zero()
|
||||
}
|
||||
RocReturn::ByPointer => {
|
||||
let basic_type = basic_type_from_layout(env, layout);
|
||||
let ptr = env.builder.build_alloca(basic_type, "unreachable_alloca");
|
||||
env.builder.build_store(ptr, basic_type.const_zero());
|
||||
|
||||
ptr.into()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,26 @@ pub struct RocCallResult<T> {
|
|||
value: MaybeUninit<T>,
|
||||
}
|
||||
|
||||
impl<T> RocCallResult<T> {
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
tag: 0,
|
||||
error_msg: std::ptr::null_mut(),
|
||||
value: MaybeUninit::new(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Default for RocCallResult<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tag: 0,
|
||||
error_msg: std::ptr::null_mut(),
|
||||
value: MaybeUninit::new(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
|
||||
fn from(call_result: RocCallResult<T>) -> Self {
|
||||
match call_result.tag {
|
||||
|
@ -30,6 +50,29 @@ impl<T: Sized> From<RocCallResult<T>> for Result<T, String> {
|
|||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! run_roc_dylib {
|
||||
($lib:expr, $main_fn_name:expr, $argument_type:ty, $return_type:ty, $errors:expr) => {{
|
||||
use inkwell::context::Context;
|
||||
use roc_builtins::bitcode;
|
||||
use roc_gen_llvm::run_roc::RocCallResult;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
// NOTE: return value is not first argument currently (may/should change in the future)
|
||||
type Main = unsafe extern "C" fn($argument_type, *mut RocCallResult<$return_type>);
|
||||
|
||||
unsafe {
|
||||
let main: libloading::Symbol<Main> = $lib
|
||||
.get($main_fn_name.as_bytes())
|
||||
.ok()
|
||||
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
|
||||
.expect("errored");
|
||||
|
||||
main
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! run_jit_function {
|
||||
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{
|
||||
|
|
|
@ -1696,6 +1696,17 @@ impl<'a> LowLevelCall<'a> {
|
|||
BoxExpr | UnboxExpr => {
|
||||
unreachable!("The {:?} operation is turned into mono Expr", self.lowlevel)
|
||||
}
|
||||
|
||||
Unreachable => match self.ret_storage {
|
||||
StoredValue::VirtualMachineStack { value_type, .. }
|
||||
| StoredValue::Local { value_type, .. } => match value_type {
|
||||
ValueType::I32 => backend.code_builder.i32_const(0),
|
||||
ValueType::I64 => backend.code_builder.i64_const(0),
|
||||
ValueType::F32 => backend.code_builder.f32_const(0.0),
|
||||
ValueType::F64 => backend.code_builder.f64_const(0.0),
|
||||
},
|
||||
StoredValue::StackMemory { .. } => { /* do nothing */ }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -121,6 +121,7 @@ pub enum LowLevel {
|
|||
RefCountDec,
|
||||
BoxExpr,
|
||||
UnboxExpr,
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
macro_rules! higher_order {
|
||||
|
|
|
@ -1271,6 +1271,7 @@ define_builtins! {
|
|||
61 LIST_REPLACE_UNSAFE: "replaceUnsafe"
|
||||
62 LIST_WITH_CAPACITY: "withCapacity"
|
||||
63 LIST_ITERATE: "iterate"
|
||||
64 LIST_UNREACHABLE: "unreachable"
|
||||
}
|
||||
6 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" // the Result.Result type alias
|
||||
|
|
|
@ -884,6 +884,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
|||
// - arguments that we may want to update destructively must be Owned
|
||||
// - other refcounted arguments are Borrowed
|
||||
match op {
|
||||
Unreachable => arena.alloc_slice_copy(&[irrelevant]),
|
||||
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes => {
|
||||
arena.alloc_slice_copy(&[borrowed])
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ use roc_debug_flags::dbg_do;
|
|||
#[cfg(debug_assertions)]
|
||||
use roc_debug_flags::{
|
||||
ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, ROC_PRINT_IR_AFTER_SPECIALIZATION,
|
||||
ROC_PRINT_RUNTIME_ERROR_GEN,
|
||||
};
|
||||
use roc_derive_key::GlobalDerivedSymbols;
|
||||
use roc_error_macros::{internal_error, todo_abilities};
|
||||
|
@ -2807,22 +2808,26 @@ fn generate_runtime_error_function<'a>(
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
eprintln!(
|
||||
"emitted runtime error function {:?} for layout {:?}",
|
||||
&msg, layout
|
||||
);
|
||||
dbg_do!(ROC_PRINT_RUNTIME_ERROR_GEN, {
|
||||
eprintln!(
|
||||
"emitted runtime error function {:?} for layout {:?}",
|
||||
&msg, layout
|
||||
);
|
||||
});
|
||||
|
||||
let runtime_error = Stmt::RuntimeError(msg.into_bump_str());
|
||||
|
||||
let (args, ret_layout) = match layout {
|
||||
RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => {
|
||||
let mut args = Vec::with_capacity_in(arg_layouts.len(), env.arena);
|
||||
let real_arg_layouts = lambda_set.extend_argument_list(env.arena, arg_layouts);
|
||||
let mut args = Vec::with_capacity_in(real_arg_layouts.len(), env.arena);
|
||||
|
||||
for arg in arg_layouts {
|
||||
args.push((*arg, env.unique_symbol()));
|
||||
}
|
||||
|
||||
args.push((Layout::LambdaSet(lambda_set), Symbol::ARG_CLOSURE));
|
||||
if real_arg_layouts.len() != arg_layouts.len() {
|
||||
args.push((Layout::LambdaSet(lambda_set), Symbol::ARG_CLOSURE));
|
||||
}
|
||||
|
||||
(args.into_bump_slice(), *ret_layout)
|
||||
}
|
||||
|
@ -2878,6 +2883,15 @@ fn specialize_external<'a>(
|
|||
let specialized =
|
||||
build_specialized_proc_from_var(env, layout_cache, lambda_name, pattern_symbols, fn_var)?;
|
||||
|
||||
let recursivity = if partial_proc.is_self_recursive {
|
||||
SelfRecursive::SelfRecursive(JoinPointId(env.unique_symbol()))
|
||||
} else {
|
||||
SelfRecursive::NotSelfRecursive
|
||||
};
|
||||
|
||||
let body = partial_proc.body.clone();
|
||||
let body_var = partial_proc.body_var;
|
||||
|
||||
// determine the layout of aliases/rigids exposed to the host
|
||||
let host_exposed_layouts = if host_exposed_variables.is_empty() {
|
||||
HostExposedLayouts::NotHostExposed
|
||||
|
@ -2922,6 +2936,7 @@ fn specialize_external<'a>(
|
|||
|
||||
let body = match_on_lambda_set(
|
||||
env,
|
||||
procs,
|
||||
lambda_set,
|
||||
Symbol::ARG_CLOSURE,
|
||||
argument_symbols.into_bump_slice(),
|
||||
|
@ -2965,15 +2980,7 @@ fn specialize_external<'a>(
|
|||
}
|
||||
};
|
||||
|
||||
let recursivity = if partial_proc.is_self_recursive {
|
||||
SelfRecursive::SelfRecursive(JoinPointId(env.unique_symbol()))
|
||||
} else {
|
||||
SelfRecursive::NotSelfRecursive
|
||||
};
|
||||
|
||||
let body = partial_proc.body.clone();
|
||||
|
||||
let mut specialized_body = from_can(env, partial_proc.body_var, body, procs, layout_cache);
|
||||
let mut specialized_body = from_can(env, body_var, body, procs, layout_cache);
|
||||
|
||||
match specialized {
|
||||
SpecializedLayout::FunctionPointerBody {
|
||||
|
@ -4712,6 +4719,7 @@ pub fn with_hole<'a>(
|
|||
|
||||
result = match_on_lambda_set(
|
||||
env,
|
||||
procs,
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
arg_symbols,
|
||||
|
@ -4752,6 +4760,7 @@ pub fn with_hole<'a>(
|
|||
|
||||
result = match_on_lambda_set(
|
||||
env,
|
||||
procs,
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
arg_symbols,
|
||||
|
@ -4809,6 +4818,7 @@ pub fn with_hole<'a>(
|
|||
|
||||
result = match_on_lambda_set(
|
||||
env,
|
||||
procs,
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
arg_symbols,
|
||||
|
@ -7485,6 +7495,7 @@ fn call_by_name<'a>(
|
|||
|
||||
let result = match_on_lambda_set(
|
||||
env,
|
||||
procs,
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
arg_symbols,
|
||||
|
@ -8030,6 +8041,7 @@ fn call_specialized_proc<'a>(
|
|||
|
||||
let new_hole = match_on_lambda_set(
|
||||
env,
|
||||
procs,
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
field_symbols,
|
||||
|
@ -9103,6 +9115,7 @@ where
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn match_on_lambda_set<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
lambda_set: LambdaSet<'a>,
|
||||
closure_data_symbol: Symbol,
|
||||
argument_symbols: &'a [Symbol],
|
||||
|
@ -9146,7 +9159,22 @@ fn match_on_lambda_set<'a>(
|
|||
field_layouts,
|
||||
field_order_hash,
|
||||
} => {
|
||||
let function_symbol = lambda_set.iter_set().next().unwrap();
|
||||
let function_symbol = match lambda_set.iter_set().next() {
|
||||
Some(function_symbol) => function_symbol,
|
||||
None => {
|
||||
// Lambda set is empty, so this function is never called; synthesize a function
|
||||
// that always yields a runtime error.
|
||||
let name = env.unique_symbol();
|
||||
let function_layout =
|
||||
RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout);
|
||||
let proc = generate_runtime_error_function(env, name, function_layout);
|
||||
let top_level =
|
||||
ProcLayout::from_raw(env.arena, function_layout, CapturesNiche::no_niche());
|
||||
|
||||
procs.specialized.insert_specialized(name, top_level, proc);
|
||||
LambdaName::no_niche(name)
|
||||
}
|
||||
};
|
||||
|
||||
let closure_info = match field_layouts {
|
||||
[] => ClosureInfo::DoesNotCapture,
|
||||
|
|
|
@ -16,7 +16,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
|
|||
encode_unicode = "0.3.6"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3.5", features = ["html_reports"] }
|
||||
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"]}
|
||||
pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
quickcheck = "1.0.3"
|
||||
|
|
|
@ -43,6 +43,7 @@ libloading = "0.7.1"
|
|||
wasmer-wasi = "2.2.1"
|
||||
tempfile = "3.2.0"
|
||||
indoc = "1.0.3"
|
||||
criterion = { git = "https://github.com/Anton-4/criterion.rs" }
|
||||
|
||||
# Wasmer singlepass compiler only works on x86_64.
|
||||
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
|
||||
|
@ -57,3 +58,7 @@ gen-llvm = []
|
|||
gen-dev = []
|
||||
gen-wasm = []
|
||||
wasm-cli-run = []
|
||||
|
||||
[[bench]]
|
||||
name = "list_map"
|
||||
harness = false
|
||||
|
|
119
crates/compiler/test_gen/benches/list_map.rs
Normal file
119
crates/compiler/test_gen/benches/list_map.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
#[path = "../src/helpers/mod.rs"]
|
||||
mod helpers;
|
||||
|
||||
// defines roc_alloc and friends
|
||||
pub use helpers::platform_functions::*;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use roc_gen_llvm::{run_roc::RocCallResult, run_roc_dylib};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_std::RocList;
|
||||
|
||||
// results July 6th, 2022
|
||||
//
|
||||
// roc sum map time: [612.73 ns 614.24 ns 615.98 ns]
|
||||
// roc sum map_with_index time: [5.3177 us 5.3218 us 5.3255 us]
|
||||
// rust (debug) time: [24.081 us 24.163 us 24.268 us]
|
||||
|
||||
type Input = RocList<i64>;
|
||||
type Output = i64;
|
||||
|
||||
type Main<I, O> = unsafe extern "C" fn(I, *mut RocCallResult<O>);
|
||||
|
||||
const ROC_LIST_MAP: &str = indoc::indoc!(
|
||||
r#"
|
||||
app "bench" provides [main] to "./platform"
|
||||
|
||||
main : List I64 -> Nat
|
||||
main = \list ->
|
||||
list
|
||||
|> List.map (\x -> x + 2)
|
||||
|> List.len
|
||||
"#
|
||||
);
|
||||
|
||||
const ROC_LIST_MAP_WITH_INDEX: &str = indoc::indoc!(
|
||||
r#"
|
||||
app "bench" provides [main] to "./platform"
|
||||
|
||||
main : List I64 -> Nat
|
||||
main = \list ->
|
||||
list
|
||||
|> List.mapWithIndex (\x, _ -> x + 2)
|
||||
|> List.len
|
||||
"#
|
||||
);
|
||||
|
||||
fn roc_function<'a, 'b>(
|
||||
arena: &'a Bump,
|
||||
source: &str,
|
||||
) -> libloading::Symbol<'a, Main<&'b Input, Output>> {
|
||||
let config = helpers::llvm::HelperConfig {
|
||||
is_gen_test: true,
|
||||
ignore_problems: false,
|
||||
add_debug_info: true,
|
||||
opt_level: OptLevel::Optimize,
|
||||
};
|
||||
|
||||
let context = inkwell::context::Context::create();
|
||||
let (main_fn_name, errors, lib) =
|
||||
helpers::llvm::helper(arena, config, source, arena.alloc(context));
|
||||
|
||||
assert!(errors.is_empty(), "Encountered errors:\n{}", errors);
|
||||
|
||||
run_roc_dylib!(arena.alloc(lib), main_fn_name, &Input, Output, errors)
|
||||
}
|
||||
|
||||
fn rust_main(argument: &RocList<i64>, output: &mut RocCallResult<i64>) {
|
||||
let mut answer = 0;
|
||||
|
||||
for x in argument.iter() {
|
||||
answer += x;
|
||||
}
|
||||
|
||||
*output = RocCallResult::new(answer);
|
||||
}
|
||||
|
||||
fn create_input_list() -> RocList<i64> {
|
||||
let numbers = Vec::from_iter(0..1_000);
|
||||
|
||||
RocList::from_slice(&numbers)
|
||||
}
|
||||
|
||||
pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
let arena = Bump::new();
|
||||
|
||||
let list_map_with_index_main = roc_function(&arena, ROC_LIST_MAP_WITH_INDEX);
|
||||
let list_map_main = roc_function(&arena, ROC_LIST_MAP);
|
||||
|
||||
let input = &*arena.alloc(create_input_list());
|
||||
|
||||
c.bench_function("roc sum map", |b| {
|
||||
b.iter(|| unsafe {
|
||||
let mut main_result = RocCallResult::default();
|
||||
|
||||
list_map_main(black_box(input), &mut main_result);
|
||||
})
|
||||
});
|
||||
|
||||
c.bench_function("roc sum map_with_index", |b| {
|
||||
b.iter(|| unsafe {
|
||||
let mut main_result = RocCallResult::default();
|
||||
|
||||
list_map_with_index_main(black_box(input), &mut main_result);
|
||||
})
|
||||
});
|
||||
|
||||
let input = &*arena.alloc(create_input_list());
|
||||
c.bench_function("rust", |b| {
|
||||
b.iter(|| {
|
||||
let mut main_result = RocCallResult::default();
|
||||
|
||||
rust_main(black_box(input), &mut main_result);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
|
@ -2958,3 +2958,37 @@ fn with_capacity() {
|
|||
RocList<u64>
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn call_function_in_empty_list() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
lst : List ({} -> {})
|
||||
lst = []
|
||||
List.map lst \f -> f {}
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&[]),
|
||||
RocList<()>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
// TODO to be improved, if we can generate the void function type for the function in the list,
|
||||
// this should succeed.
|
||||
#[should_panic(expected = r#"Roc failed with message: "#)]
|
||||
fn call_function_in_empty_list_unbound() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
lst = []
|
||||
List.map lst \f -> f {}
|
||||
"#
|
||||
),
|
||||
RocList::from_slice(&[]),
|
||||
RocList<()>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::helpers::from_wasmer_memory::FromWasmerMemory;
|
||||
use inkwell::module::Module;
|
||||
use libloading::Library;
|
||||
use roc_build::link::module_to_dylib;
|
||||
use roc_build::link::llvm_module_to_dylib;
|
||||
use roc_build::program::FunctionIterator;
|
||||
use roc_collections::all::MutSet;
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
|
@ -246,39 +246,84 @@ fn create_llvm_module<'a>(
|
|||
(main_fn_name, delayed_errors.join("\n"), env.module)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct HelperConfig {
|
||||
pub is_gen_test: bool,
|
||||
pub ignore_problems: bool,
|
||||
pub add_debug_info: bool,
|
||||
pub opt_level: OptLevel,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline(never)]
|
||||
pub fn helper<'a>(
|
||||
arena: &'a bumpalo::Bump,
|
||||
config: HelperConfig,
|
||||
src: &str,
|
||||
is_gen_test: bool,
|
||||
ignore_problems: bool,
|
||||
context: &'a inkwell::context::Context,
|
||||
) -> (&'static str, String, Library) {
|
||||
let target = target_lexicon::Triple::host();
|
||||
|
||||
let opt_level = if cfg!(debug_assertions) {
|
||||
OptLevel::Normal
|
||||
} else {
|
||||
OptLevel::Optimize
|
||||
};
|
||||
|
||||
let (main_fn_name, delayed_errors, module) = create_llvm_module(
|
||||
arena,
|
||||
src,
|
||||
is_gen_test,
|
||||
ignore_problems,
|
||||
config.is_gen_test,
|
||||
config.ignore_problems,
|
||||
context,
|
||||
&target,
|
||||
opt_level,
|
||||
config.opt_level,
|
||||
);
|
||||
|
||||
let lib =
|
||||
module_to_dylib(module, &target, opt_level).expect("Error loading compiled dylib for test");
|
||||
let res_lib = if config.add_debug_info {
|
||||
let module = annotate_with_debug_info(module, context);
|
||||
llvm_module_to_dylib(&module, &target, config.opt_level)
|
||||
} else {
|
||||
llvm_module_to_dylib(module, &target, config.opt_level)
|
||||
};
|
||||
|
||||
let lib = res_lib.expect("Error loading compiled dylib for test");
|
||||
|
||||
(main_fn_name, delayed_errors, lib)
|
||||
}
|
||||
|
||||
fn annotate_with_debug_info<'ctx>(
|
||||
module: &Module<'ctx>,
|
||||
context: &'ctx inkwell::context::Context,
|
||||
) -> Module<'ctx> {
|
||||
use std::process::Command;
|
||||
|
||||
let app_ll_file = "/tmp/roc-debugir.ll";
|
||||
let app_dbg_ll_file = "/tmp/roc-debugir.dbg.ll";
|
||||
let app_bc_file = "/tmp/roc-debugir.bc";
|
||||
|
||||
// write the ll code to a file, so we can modify it
|
||||
module.print_to_file(&app_ll_file).unwrap();
|
||||
|
||||
// run the debugir https://github.com/vaivaswatha/debugir tool
|
||||
match Command::new("debugir")
|
||||
.args(&["-instnamer", app_ll_file])
|
||||
.output()
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
use std::io::ErrorKind;
|
||||
match error.kind() {
|
||||
ErrorKind::NotFound => panic!(
|
||||
r"I could not find the `debugir` tool on the PATH, install it from https://github.com/vaivaswatha/debugir"
|
||||
),
|
||||
_ => panic!("{:?}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::new("llvm-as")
|
||||
.args(&[app_dbg_ll_file, "-o", app_bc_file])
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
inkwell::module::Module::parse_bitcode_from_path(&app_bc_file, context).unwrap()
|
||||
}
|
||||
|
||||
fn wasm32_target_tripple() -> Triple {
|
||||
use target_lexicon::{Architecture, BinaryFormat};
|
||||
|
||||
|
@ -513,13 +558,26 @@ macro_rules! assert_llvm_evals_to {
|
|||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use roc_gen_llvm::run_jit_function;
|
||||
use roc_mono::ir::OptLevel;
|
||||
|
||||
let arena = Bump::new();
|
||||
let context = Context::create();
|
||||
|
||||
let is_gen_test = true;
|
||||
let opt_level = if cfg!(debug_assertions) {
|
||||
OptLevel::Normal
|
||||
} else {
|
||||
OptLevel::Optimize
|
||||
};
|
||||
|
||||
let config = $crate::helpers::llvm::HelperConfig {
|
||||
is_gen_test: true,
|
||||
add_debug_info: false,
|
||||
ignore_problems: $ignore_problems,
|
||||
opt_level,
|
||||
};
|
||||
|
||||
let (main_fn_name, errors, lib) =
|
||||
$crate::helpers::llvm::helper(&arena, $src, is_gen_test, $ignore_problems, &context);
|
||||
$crate::helpers::llvm::helper(&arena, config, $src, &context);
|
||||
|
||||
let transform = |success| {
|
||||
let expected = $expected;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
extern crate bumpalo;
|
||||
|
||||
pub mod platform_functions;
|
||||
|
||||
#[cfg(feature = "gen-dev")]
|
||||
pub mod dev;
|
||||
pub mod from_wasmer_memory;
|
||||
|
|
54
crates/compiler/test_gen/src/helpers/platform_functions.rs
Normal file
54
crates/compiler/test_gen/src/helpers/platform_functions.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use core::ffi::c_void;
|
||||
|
||||
/// # Safety
|
||||
/// The Roc application needs this.
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
|
||||
libc::malloc(size)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The Roc application needs this.
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_memcpy(dest: *mut c_void, src: *const c_void, bytes: usize) -> *mut c_void {
|
||||
libc::memcpy(dest, src, bytes)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The Roc application needs this.
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_realloc(
|
||||
c_ptr: *mut c_void,
|
||||
new_size: usize,
|
||||
_old_size: usize,
|
||||
_alignment: u32,
|
||||
) -> *mut c_void {
|
||||
libc::realloc(c_ptr, new_size)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The Roc application needs this.
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
|
||||
libc::free(c_ptr)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The Roc application needs this.
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
|
||||
use roc_gen_llvm::llvm::build::PanicTagId;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
match PanicTagId::try_from(tag_id) {
|
||||
Ok(PanicTagId::NullTerminatedString) => {
|
||||
let slice = CStr::from_ptr(c_ptr as *const c_char);
|
||||
let string = slice.to_str().unwrap();
|
||||
eprintln!("Roc hit a panic: {}", string);
|
||||
std::process::exit(1);
|
||||
}
|
||||
Err(_) => unreachable!(),
|
||||
}
|
||||
}
|
|
@ -22,57 +22,4 @@ pub mod wasm_str;
|
|||
#[cfg(feature = "gen-wasm")]
|
||||
pub mod wasm_linking;
|
||||
|
||||
use core::ffi::c_void;
|
||||
|
||||
/// # Safety
|
||||
/// The Roc application needs this.
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
|
||||
libc::malloc(size)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The Roc application needs this.
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_memcpy(dest: *mut c_void, src: *const c_void, bytes: usize) -> *mut c_void {
|
||||
libc::memcpy(dest, src, bytes)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The Roc application needs this.
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_realloc(
|
||||
c_ptr: *mut c_void,
|
||||
new_size: usize,
|
||||
_old_size: usize,
|
||||
_alignment: u32,
|
||||
) -> *mut c_void {
|
||||
libc::realloc(c_ptr, new_size)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The Roc application needs this.
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
|
||||
libc::free(c_ptr)
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The Roc application needs this.
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
|
||||
use roc_gen_llvm::llvm::build::PanicTagId;
|
||||
|
||||
use std::ffi::CStr;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
match PanicTagId::try_from(tag_id) {
|
||||
Ok(PanicTagId::NullTerminatedString) => {
|
||||
let slice = CStr::from_ptr(c_ptr as *const c_char);
|
||||
let string = slice.to_str().unwrap();
|
||||
eprintln!("Roc hit a panic: {}", string);
|
||||
std::process::exit(1);
|
||||
}
|
||||
Err(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
pub use helpers::platform_functions::*;
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
procedure List.5 (#Attr.2, #Attr.3):
|
||||
let List.279 : List {} = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.2 #Attr.3;
|
||||
decref #Attr.2;
|
||||
ret List.279;
|
||||
|
||||
procedure Test.2 (Test.3):
|
||||
let Test.7 : {} = Struct {};
|
||||
let Test.6 : {} = CallByName Test.8 Test.7;
|
||||
ret Test.6;
|
||||
|
||||
procedure Test.8 (Test.9):
|
||||
Error The `#UserApp.IdentId(8)` function could not be generated, likely due to a type error.
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.1 : List {} = Array [];
|
||||
let Test.5 : {} = Struct {};
|
||||
let Test.4 : List {} = CallByName List.5 Test.1 Test.5;
|
||||
ret Test.4;
|
|
@ -0,0 +1,6 @@
|
|||
procedure List.5 (#Attr.2, #Attr.3):
|
||||
Error UnresolvedTypeVar crates/compiler/mono/src/ir.rs line 5030
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.1 : List {} = Array [];
|
||||
Error UnresolvedTypeVar crates/compiler/mono/src/ir.rs line 4557
|
|
@ -1697,3 +1697,24 @@ fn recursive_call_capturing_function() {
|
|||
"#
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn call_function_in_empty_list() {
|
||||
indoc!(
|
||||
r#"
|
||||
lst : List ({} -> {})
|
||||
lst = []
|
||||
List.map lst \f -> f {}
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn call_function_in_empty_list_unbound() {
|
||||
indoc!(
|
||||
r#"
|
||||
lst = []
|
||||
List.map lst \f -> f {}
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue