roc/crates/compiler/test_gen/src/gen_abilities.rs
Ayaz Hafiz e36618b9e9
Support custom abilities for opaques with immaterial lambda sets
If a specialization of an ability member has a lambda set that is not
reflected in the unspecialized lambda sets of the member's prototype
signature, then the specialization lambda set is deemed to be immaterial
to the specialization lambda set mapping, and we don't need to associate
it with a particular region from the prototype signature.

This can happen when an opaque contains functions that are some specific
than the generalized prototype signature; for example, when we are
defining a custom impl for an opaque with functions.

Addresses a bug found in 8c3158c3e0
2022-11-21 18:03:56 -06:00

1807 lines
48 KiB
Rust

#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use indoc::indoc;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use roc_std::RocList;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use roc_std::RocStr;
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn hash_specialization() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
MHash has
hash : a -> U64 | a has MHash
Id := U64 has [MHash {hash}]
hash = \@Id n -> n
main = hash (@Id 1234)
"#
),
1234,
u64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn hash_specialization_multiple_add() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
MHash has
hash : a -> U64 | a has MHash
Id := U64 has [ MHash {hash: hashId} ]
hashId = \@Id n -> n
One := {} has [ MHash {hash: hashOne} ]
hashOne = \@One _ -> 1
main = hash (@Id 1234) + hash (@One {})
"#
),
1235,
u64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn alias_member_specialization() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
MHash has
hash : a -> U64 | a has MHash
Id := U64 has [MHash {hash}]
hash = \@Id n -> n
main =
aliasedMHash = hash
aliasedMHash (@Id 1234)
"#
),
1234,
u64
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn ability_constrained_in_non_member_usage() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [result] to "./platform"
MHash has
hash : a -> U64 | a has MHash
mulMHashes : a, a -> U64 | a has MHash
mulMHashes = \x, y -> hash x * hash y
Id := U64 has [MHash {hash}]
hash = \@Id n -> n
result = mulMHashes (@Id 5) (@Id 7)
"#
),
35,
u64
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn ability_constrained_in_non_member_usage_inferred() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [result] to "./platform"
MHash has
hash : a -> U64 | a has MHash
mulMHashes = \x, y -> hash x * hash y
Id := U64 has [MHash {hash}]
hash = \@Id n -> n
result = mulMHashes (@Id 5) (@Id 7)
"#
),
35,
u64
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn ability_constrained_in_non_member_multiple_specializations() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [result] to "./platform"
MHash has
hash : a -> U64 | a has MHash
mulMHashes : a, b -> U64 | a has MHash, b has MHash
mulMHashes = \x, y -> hash x * hash y
Id := U64 has [MHash { hash: hashId }]
hashId = \@Id n -> n
Three := {} has [MHash { hash: hashThree }]
hashThree = \@Three _ -> 3
result = mulMHashes (@Id 100) (@Three {})
"#
),
300,
u64
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn ability_constrained_in_non_member_multiple_specializations_inferred() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [result] to "./platform"
MHash has
hash : a -> U64 | a has MHash
mulMHashes = \x, y -> hash x * hash y
Id := U64 has [MHash { hash: hashId }]
hashId = \@Id n -> n
Three := {} has [MHash { hash: hashThree }]
hashThree = \@Three _ -> 3
result = mulMHashes (@Id 100) (@Three {})
"#
),
300,
u64
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn ability_used_as_type_still_compiles() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [result] to "./platform"
MHash has
hash : a -> U64 | a has MHash
mulMHashes : MHash, MHash -> U64
mulMHashes = \x, y -> hash x * hash y
Id := U64 has [MHash { hash: hashId }]
hashId = \@Id n -> n
Three := {} has [MHash { hash: hashThree }]
hashThree = \@Three _ -> 3
result = mulMHashes (@Id 100) (@Three {})
"#
),
300,
u64
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn bounds_to_multiple_abilities() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Idempot has idempot : a -> a | a has Idempot
Consume has consume : a -> Str | a has Consume
Hello := Str has [Idempot { idempot: idempotHello }, Consume { consume: consumeHello }]
idempotHello = \@Hello msg -> @Hello msg
consumeHello = \@Hello msg -> msg
lifecycle : a -> Str | a has Idempot & Consume
lifecycle = \x -> idempot x |> consume
main = lifecycle (@Hello "hello world")
"#
),
RocStr::from("hello world"),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [myU8Bytes] to "./platform"
MEncoder fmt := List U8, fmt -> List U8 | fmt has Format
MEncoding has
toEncoder : val -> MEncoder fmt | val has MEncoding, fmt has Format
Format has
u8 : U8 -> MEncoder fmt | fmt has Format
appendWith : List U8, MEncoder fmt, fmt -> List U8 | fmt has Format
appendWith = \lst, (@MEncoder doFormat), fmt -> doFormat lst fmt
toBytes : val, fmt -> List U8 | val has MEncoding, fmt has Format
toBytes = \val, fmt -> appendWith [] (toEncoder val) fmt
Linear := {} has [Format {u8}]
u8 = \n -> @MEncoder (\lst, @Linear {} -> List.append lst n)
Rgba := { r : U8, g : U8, b : U8, a : U8 } has [MEncoding {toEncoder}]
toEncoder = \@Rgba {r, g, b, a} ->
@MEncoder \lst, fmt -> lst
|> appendWith (u8 r) fmt
|> appendWith (u8 g) fmt
|> appendWith (u8 b) fmt
|> appendWith (u8 a) fmt
myU8Bytes = toBytes (@Rgba { r: 106, g: 90, b: 205, a: 255 }) (@Linear {})
"#
),
RocList::from_slice(&[106, 90, 205, 255]),
RocList<u8>
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[ignore = "running into weird let-generalization issue when a variable is only in output position, see #3660"]
fn decode() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [myU8] to "./platform"
MDecodeError : [TooShort, Leftover (List U8)]
MDecoder val fmt := List U8, fmt -> { result: Result val MDecodeError, rest: List U8 } | fmt has MDecoderFormatting
MDecoding has
decoder : MDecoder val fmt | val has MDecoding, fmt has MDecoderFormatting
MDecoderFormatting has
u8 : MDecoder U8 fmt | fmt has MDecoderFormatting
decodeWith : List U8, MDecoder val fmt, fmt -> { result: Result val MDecodeError, rest: List U8 } | fmt has MDecoderFormatting
decodeWith = \lst, (@MDecoder doDecode), fmt -> doDecode lst fmt
fromBytes : List U8, fmt -> Result val MDecodeError
| fmt has MDecoderFormatting, val has MDecoding
fromBytes = \lst, fmt ->
when decodeWith lst decoder fmt is
{ result, rest } ->
Result.try result \val ->
if List.isEmpty rest
then Ok val
else Err (Leftover rest)
Linear := {} has [MDecoderFormatting {u8}]
u8 = @MDecoder \lst, @Linear {} ->
when List.first lst is
Ok n -> { result: Ok n, rest: List.dropFirst lst }
Err _ -> { result: Err TooShort, rest: [] }
MyU8 := U8 has [MDecoding {decoder}]
# impl MDecoding for MyU8
decoder = @MDecoder \lst, fmt ->
{ result, rest } = decodeWith lst u8 fmt
{ result: Result.map result (\n -> @MyU8 n), rest }
myU8 =
when fromBytes [15] (@Linear {}) is
Ok (@MyU8 n) -> n
_ -> 27u8
"#
),
15,
u8
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_use_stdlib() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
HelloWorld := {} has [Encoding {toEncoder}]
toEncoder = \@HelloWorld {} ->
Encode.custom \bytes, fmt ->
bytes
|> Encode.appendWith (Encode.string "Hello, World!\n") fmt
main =
result = Str.fromUtf8 (Encode.toBytes (@HelloWorld {}) Json.toUtf8)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from("\"Hello, World!\n\""),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_use_stdlib_without_wrapping_custom() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
HelloWorld := {} has [Encoding {toEncoder}]
toEncoder = \@HelloWorld {} -> Encode.string "Hello, World!\n"
main =
result = Str.fromUtf8 (Encode.toBytes (@HelloWorld {}) Json.toUtf8)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from("\"Hello, World!\n\""),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_derive_to_encoder_for_opaque() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Json]
provides [main] to "./platform"
HelloWorld := { a: Str } has [Encoding]
main =
result = Str.fromUtf8 (Encode.toBytes (@HelloWorld { a: "Hello, World!" }) Json.toUtf8)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"a":"Hello, World!"}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn to_encoder_encode_custom_has_capture() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
HelloWorld := Str has [Encoding {toEncoder}]
toEncoder = \@HelloWorld s1 ->
Encode.custom \bytes, fmt ->
bytes
|> Encode.appendWith (Encode.string s1) fmt
main =
result = Str.fromUtf8 (Encode.toBytes (@HelloWorld "Hello, World!\n") Json.toUtf8)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from("\"Hello, World!\n\""),
RocStr
)
}
mod encode_immediate {
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use indoc::indoc;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use roc_std::RocStr;
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn string() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Encode, Json] provides [main] to "./platform"
main =
when Str.fromUtf8 (Encode.toBytes "foo" Json.toUtf8) is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from("\"foo\""),
RocStr
)
}
macro_rules! num_immediate {
($($num:expr, $typ:ident)*) => {$(
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn $typ() {
assert_evals_to!(
&format!(indoc!(
r#"
app "test" imports [Encode, Json] provides [main] to "./platform"
main =
when Str.fromUtf8 (Encode.toBytes {}{} Json.toUtf8) is
Ok s -> s
_ -> "<bad>"
"#
), $num, stringify!($typ)),
RocStr::from(format!(r#"{}"#, $num).as_str()),
RocStr
)
}
)*}
}
num_immediate! {
17, i8
17, i16
17, i32
17, i64
17, i128
17, u8
17, u16
17, u32
17, u64
17, u128
17.25, f32
17.23, f64
17.23, dec
}
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_derived_record_one_field_string() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
main =
result = Str.fromUtf8 (Encode.toBytes {a: "foo"} Json.toUtf8)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"a":"foo"}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_derived_record_two_fields_strings() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
main =
rcd = {a: "foo", b: "bar"}
result = Str.fromUtf8 (Encode.toBytes rcd Json.toUtf8)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"a":"foo","b":"bar"}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_derived_nested_record_string() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
main =
rcd = {a: {b: "bar"}}
encoded = Encode.toBytes rcd Json.toUtf8
result = Str.fromUtf8 encoded
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"a":{"b":"bar"}}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_derived_tag_one_payload_string() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
main =
x = A "foo"
result = Str.fromUtf8 (Encode.toBytes x Json.toUtf8)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"A":["foo"]}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_derived_tag_two_payloads_string() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
main =
x = A "foo" "bar"
result = Str.fromUtf8 (Encode.toBytes x Json.toUtf8)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"A":["foo","bar"]}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_derived_nested_tag_string() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
main =
x = A (B "foo" "bar")
encoded = Encode.toBytes x Json.toUtf8
result = Str.fromUtf8 encoded
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"A":[{"B":["foo","bar"]}]}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_derived_nested_record_tag_record() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
main =
x = {a: (B ({c: "foo"}))}
encoded = Encode.toBytes x Json.toUtf8
result = Str.fromUtf8 encoded
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"{"a":{"B":[{"c":"foo"}]}}"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_derived_list_string() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
main =
lst = ["foo", "bar", "baz"]
encoded = Encode.toBytes lst Json.toUtf8
result = Str.fromUtf8 encoded
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"["foo","bar","baz"]"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_derived_list_of_records() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
main =
lst = [{a: "foo"}, {a: "bar"}, {a: "baz"}]
encoded = Encode.toBytes lst Json.toUtf8
result = Str.fromUtf8 encoded
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"[{"a":"foo"},{"a":"bar"},{"a":"baz"}]"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_derived_list_of_lists_of_strings() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
main =
lst = [["a", "b"], ["c", "d", "e"], ["f"]]
encoded = Encode.toBytes lst Json.toUtf8
result = Str.fromUtf8 encoded
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(r#"[["a","b"],["c","d","e"],["f"]]"#),
RocStr
)
}
#[test]
#[cfg(all(any(feature = "gen-llvm", feature = "gen-wasm")))]
fn encode_derived_record_with_many_types() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode, Json]
provides [main] to "./platform"
main =
fresh : [Fresh Str, Rotten Str]
fresh = Fresh "tomatoes"
rcd = {actors: ["Idris Elba", "Mila Kunis"], year: 2004u16, rating: {average: 7u8, min: 1u8, max: 10u8, sentiment: fresh}}
result = Str.fromUtf8 (Encode.toBytes rcd Json.toUtf8)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from(
r#"{"actors":["Idris Elba","Mila Kunis"],"rating":{"average":7,"max":10,"min":1,"sentiment":{"Fresh":["tomatoes"]}},"year":2004}"#
),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode_use_stdlib() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Json]
provides [main] to "./platform"
MyNum := U8 has [Decoding {decoder: myDecoder}]
myDecoder =
Decode.custom \bytes, fmt ->
when Decode.decodeWith bytes Decode.u8 fmt is
{result, rest} ->
when result is
Ok n -> {result: Ok (@MyNum n), rest}
Err e -> {result: Err e, rest}
main =
when Decode.fromBytes [49, 53] Json.fromUtf8 is
Ok (@MyNum n) -> n
_ -> 101
"#
),
15,
u8
)
}
#[test]
#[cfg(all(
any(feature = "gen-llvm", feature = "gen-wasm"),
not(debug_assertions) // https://github.com/roc-lang/roc/issues/3898
))]
fn decode_derive_decoder_for_opaque() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Json]
provides [main] to "./platform"
HelloWorld := { a: Str } has [Decoding]
main =
when Str.toUtf8 """{"a":"Hello, World!"}""" |> Decode.fromBytes Json.fromUtf8 is
Ok (@HelloWorld {a}) -> a
_ -> "FAIL"
"#
),
RocStr::from(r#"Hello, World!"#),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode_use_stdlib_json_list() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Json]
provides [main] to "./platform"
MyNumList := List U8 has [Decoding {decoder: myDecoder}]
myDecoder =
Decode.custom \bytes, fmt ->
when Decode.decodeWith bytes (Decode.list Decode.u8) fmt is
{result, rest} ->
when result is
Ok lst -> {result: Ok (@MyNumList lst), rest}
Err e -> {result: Err e, rest}
main =
when Str.toUtf8 "[1,2,3]" |> Decode.fromBytes Json.fromUtf8 is
Ok (@MyNumList lst) -> lst
_ -> []
"#
),
RocList::from_slice(&[1u8, 2u8, 3u8]),
RocList<u8>
)
}
mod decode_immediate {
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use indoc::indoc;
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use roc_std::RocStr;
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn string() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
when Str.toUtf8 "\"foo\"" |> Decode.fromBytes Json.fromUtf8 is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from("foo"),
RocStr
)
}
macro_rules! num_immediate {
($($num:expr, $typ:ident)*) => {$(
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn $typ() {
assert_evals_to!(
&format!(indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
when Num.toStr {}{} |> Str.toUtf8 |> Decode.fromBytes Json.fromUtf8 is
Ok n -> n
_ -> 101{}
"#
), $num, stringify!($typ), stringify!($typ)),
$num,
$typ
)
}
)*}
}
num_immediate! {
17, i8
17, i16
17, i32
17, i64
17, i128
17, u8
17, u16
17, u32
17, u64
17, u128
17.23, f32
17.23, f64
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn dec() {
use roc_std::RocDec;
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
when Num.toStr 17.23dec |> Str.toUtf8 |> Decode.fromBytes Json.fromUtf8 is
Ok n -> n
_ -> 101dec
"#
),
RocDec::from_str("17.23").unwrap(),
RocDec
)
}
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode_list_of_strings() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
when Str.toUtf8 "[\"a\",\"b\",\"c\"]" |> Decode.fromBytes Json.fromUtf8 is
Ok l -> Str.joinWith l ","
_ -> "<bad>"
"#
),
RocStr::from("a,b,c"),
RocStr
)
}
#[test]
#[cfg(all(any(feature = "gen-llvm", feature = "gen-wasm")))]
fn encode_then_decode_list_of_strings() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
when Encode.toBytes ["a", "b", "c"] Json.fromUtf8 |> Decode.fromBytes Json.fromUtf8 is
Ok l -> Str.joinWith l ","
_ -> "something went wrong"
"#
),
RocStr::from("a,b,c"),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[ignore = "#3696: Currently hits some weird panic in borrow checking, not sure if it's directly related to abilities."]
fn encode_then_decode_list_of_lists_of_strings() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
when Encode.toBytes [["a", "b"], ["c", "d", "e"], ["f"]] Json.fromUtf8 |> Decode.fromBytes Json.fromUtf8 is
Ok list -> (List.map list \inner -> Str.joinWith inner ",") |> Str.joinWith l ";"
_ -> "something went wrong"
"#
),
RocStr::from("a,b;c,d,e;f"),
RocStr
)
}
#[test]
#[cfg(all(
any(feature = "gen-llvm", feature = "gen-wasm"),
not(debug_assertions) // https://github.com/roc-lang/roc/issues/3898
))]
fn decode_record_two_fields() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
when Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes Json.fromUtf8 is
Ok {first: "ab", second: "cd"} -> "abcd"
_ -> "something went wrong"
"#
),
RocStr::from("abcd"),
RocStr
)
}
#[test]
#[cfg(all(
any(feature = "gen-llvm", feature = "gen-wasm"),
not(debug_assertions) // https://github.com/roc-lang/roc/issues/3898
))]
fn decode_record_two_fields_string_and_int() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
when Str.toUtf8 "{\"first\":\"ab\",\"second\":10}" |> Decode.fromBytes Json.fromUtf8 is
Ok {first: "ab", second: 10u8} -> "ab10"
_ -> "something went wrong"
"#
),
RocStr::from("ab10"),
RocStr
)
}
#[test]
#[cfg(all(
any(feature = "gen-llvm", feature = "gen-wasm"),
not(debug_assertions) // https://github.com/roc-lang/roc/issues/3898
))]
fn decode_record_two_fields_string_and_string_infer() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
when Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes Json.fromUtf8 is
Ok {first, second} -> Str.concat first second
_ -> "something went wrong"
"#
),
RocStr::from("abcd"),
RocStr
)
}
#[test]
#[cfg(all(
any(feature = "gen-llvm", feature = "gen-wasm"),
not(debug_assertions) // https://github.com/roc-lang/roc/issues/3898
))]
fn decode_record_two_fields_string_and_string_infer_local_var() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
decoded = Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes Json.fromUtf8
when decoded is
Ok rcd -> Str.concat rcd.first rcd.second
_ -> "something went wrong"
"#
),
RocStr::from("abcd"),
RocStr
)
}
#[test]
#[cfg(all(
any(feature = "gen-llvm", feature = "gen-wasm"),
not(debug_assertions) // https://github.com/roc-lang/roc/issues/3898
))]
fn decode_record_two_fields_string_and_string_infer_local_var_destructured() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
decoded = Str.toUtf8 "{\"first\":\"ab\",\"second\":\"cd\"}" |> Decode.fromBytes Json.fromUtf8
when decoded is
Ok {first, second} -> Str.concat first second
_ -> "something went wrong"
"#
),
RocStr::from("abcd"),
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[ignore = "json parsing impl must be fixed first"]
fn decode_empty_record() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
when Str.toUtf8 "{}" |> Decode.fromBytes Json.fromUtf8 is
Ok {} -> "empty"
_ -> "something went wrong"
"#
),
RocStr::from("empty"),
RocStr
)
}
#[test]
#[cfg(all(
any(feature = "gen-llvm", feature = "gen-wasm"),
not(feature = "gen-llvm-wasm"), // hits a wasm3 stack overflow
not(debug_assertions) // https://github.com/roc-lang/roc/issues/3898
))]
fn decode_record_of_record() {
assert_evals_to!(
indoc!(
r#"
app "test" imports [Json] provides [main] to "./platform"
main =
when Str.toUtf8 "{\"outer\":{\"inner\":\"a\"},\"other\":{\"one\":\"b\",\"two\":10}}" |> Decode.fromBytes Json.fromUtf8 is
Ok {outer: {inner: "a"}, other: {one: "b", two: 10u8}} -> "ab10"
_ -> "something went wrong"
"#
),
RocStr::from("ab10"),
RocStr
)
}
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
mod hash {
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
use indoc::indoc;
const TEST_HASHER: &str = indoc!(
r#"
THasher := List U8 has [Hasher {
addBytes: tAddBytes,
addU8: tAddU8,
addU16: tAddU16,
addU32: tAddU32,
addU64: tAddU64,
addU128: tAddU128,
complete: tComplete,
}]
# ignores endian-ness
byteAt = \n, shift ->
Num.bitwiseAnd (Num.shiftRightBy n (shift * 8)) 0xFF
|> Num.toU8
do8 = \total, n ->
total
|> List.append (byteAt n 0)
do16 = \total, n ->
total
|> do8 (n |> Num.toU8)
|> do8 (Num.shiftRightBy n 8 |> Num.toU8)
do32 = \total, n ->
total
|> do16 (n |> Num.toU16)
|> do16 (Num.shiftRightBy n 16 |> Num.toU16)
do64 = \total, n ->
total
|> do32 (n |> Num.toU32)
|> do32 (Num.shiftRightBy n 32 |> Num.toU32)
do128 = \total, n ->
total
|> do64 (n |> Num.toU64)
|> do64 (Num.shiftRightBy n 64 |> Num.toU64)
tAddBytes = \@THasher total, bytes -> @THasher (List.concat total bytes)
tAddU8 = \@THasher total, n -> @THasher (do8 total n)
tAddU16 = \@THasher total, n -> @THasher (do16 total n)
tAddU32 = \@THasher total, n -> @THasher (do32 total n)
tAddU64 = \@THasher total, n -> @THasher (do64 total n)
tAddU128 = \@THasher total, n -> @THasher (do128 total n)
tComplete = \@THasher _ -> Num.maxU64
tRead = \@THasher bytes -> bytes
"#
);
fn build_test(input: &str) -> String {
format!(
indoc!(
r#"
app "test" provides [main] to "./platform"
{}
main =
@THasher []
|> Hash.hash ({})
|> tRead
"#
),
TEST_HASHER, input,
)
}
mod immediate {
use super::{assert_evals_to, build_test};
use roc_std::RocList;
#[test]
fn i8() {
assert_evals_to!(
&build_test("-2i8"),
RocList::from_slice(&[254]),
RocList<u8>
)
}
#[test]
fn u8() {
assert_evals_to!(
&build_test("254u8"),
RocList::from_slice(&[254]),
RocList<u8>
)
}
#[test]
fn i16() {
assert_evals_to!(
&build_test("-2i16"),
RocList::from_slice(&[254, 255]),
RocList<u8>
)
}
#[test]
fn u16() {
assert_evals_to!(
&build_test("Num.maxU16 - 1"),
RocList::from_slice(&[254, 255]),
RocList<u8>
)
}
#[test]
fn i32() {
assert_evals_to!(
&build_test("-2i32"),
RocList::from_slice(&[254, 255, 255, 255]),
RocList<u8>
)
}
#[test]
fn u32() {
assert_evals_to!(
&build_test("Num.maxU32 - 1"),
RocList::from_slice(&[254, 255, 255, 255]),
RocList<u8>
)
}
#[test]
fn i64() {
assert_evals_to!(
&build_test("-2i64"),
RocList::from_slice(&[254, 255, 255, 255, 255, 255, 255, 255]),
RocList<u8>
)
}
#[test]
fn u64() {
assert_evals_to!(
&build_test("Num.maxU64 - 1"),
RocList::from_slice(&[254, 255, 255, 255, 255, 255, 255, 255]),
RocList<u8>
)
}
#[test]
#[cfg(not(feature = "gen-wasm"))] // shr not implemented for U128
fn i128() {
assert_evals_to!(
&build_test("-2i128"),
RocList::from_slice(&[
254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
]),
RocList<u8>
)
}
#[test]
#[cfg(not(feature = "gen-wasm"))] // shr not implemented for U128
fn u128() {
assert_evals_to!(
&build_test("Num.maxU128 - 1"),
RocList::from_slice(&[
254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
]),
RocList<u8>
)
}
#[test]
fn string() {
assert_evals_to!(
&build_test(r#""ab☃AB""#),
RocList::from_slice(&[97, 98, 226, 152, 131, 65, 66]),
RocList<u8>
)
}
#[test]
fn list_u8() {
assert_evals_to!(
&build_test(r#"[15u8, 23u8, 37u8]"#),
RocList::from_slice(&[15, 23, 37]),
RocList<u8>
)
}
#[test]
fn list_string() {
assert_evals_to!(
&build_test(r#"["ab", "cd", "ef"]"#),
RocList::from_slice(&[97, 98, 99, 100, 101, 102]),
RocList<u8>
)
}
#[test]
fn list_list_string() {
assert_evals_to!(
&build_test(r#"[[ "ab", "cd" ], [ "ef" ]]"#),
RocList::from_slice(&[97, 98, 99, 100, 101, 102]),
RocList<u8>
)
}
}
mod derived {
use super::{assert_evals_to, build_test, indoc, TEST_HASHER};
use roc_std::RocList;
#[test]
fn empty_record() {
assert_evals_to!(
&build_test(r#"{}"#),
RocList::from_slice(&[] as &[u8]),
RocList<u8>
)
}
#[test]
fn record_of_u8_and_str() {
assert_evals_to!(
&build_test(r#"{ a: 15u8, b: "bc" }"#),
RocList::from_slice(&[15, 98, 99]),
RocList<u8>
)
}
#[test]
fn record_of_records() {
assert_evals_to!(
&build_test(r#"{ a: { b: 15u8, c: "bc" }, d: { b: 23u8, e: "ef" } }"#),
RocList::from_slice(&[15, 98, 99, 23, 101, 102]),
RocList<u8>
)
}
#[test]
fn record_of_list_of_records() {
assert_evals_to!(
&build_test(
r#"{ a: [ { b: 15u8 }, { b: 23u8 } ], b: [ { c: 45u8 }, { c: 73u8 } ] }"#
),
RocList::from_slice(&[15, 23, 45, 73]),
RocList<u8>
)
}
#[test]
fn hash_singleton_union() {
assert_evals_to!(
&format!(
indoc!(
r#"
app "test" provides [main] to "./platform"
{}
a : [A]
a = A
main =
@THasher []
|> Hash.hash a
|> tRead
"#
),
TEST_HASHER,
),
RocList::from_slice(&[
// hash nothing because this is a newtype of a unit layout.
] as &[u8]),
RocList<u8>
)
}
#[test]
fn hash_bool_tag_union() {
assert_evals_to!(
&format!(
indoc!(
r#"
app "test" provides [main] to "./platform"
{}
a : [A, B]
a = A
b : [A, B]
b = B
main =
@THasher []
|> Hash.hash a
|> Hash.hash b
|> tRead
"#
),
TEST_HASHER,
),
RocList::from_slice(&[
0, // A
1, // B
]),
RocList<u8>
)
}
#[test]
fn hash_byte_tag_union() {
assert_evals_to!(
&format!(
indoc!(
r#"
app "test" provides [main] to "./platform"
{}
l : List [A, B, C, D, E, F, G, H]
l = [A, B, C, D, E, F, G, H]
main =
@THasher []
|> Hash.hash l
|> tRead
"#
),
TEST_HASHER,
),
RocList::from_slice(&[
0, // A
1, // B
2, // C
3, // D
4, // E
5, // F
6, // G
7, // H
]),
RocList<u8>
)
}
#[test]
fn hash_newtype_tag_union() {
assert_evals_to!(
&format!(
indoc!(
r#"
app "test" provides [main] to "./platform"
{}
a : [A U8 U8 U8]
a = A 15 23 47
main =
@THasher []
|> Hash.hash a
|> tRead
"#
),
TEST_HASHER,
),
RocList::from_slice(&[
// discriminant is skipped because it's a newtype
15, 23, 47
]),
RocList<u8>
)
}
#[test]
fn hash_newtype_by_void_tag_union() {
assert_evals_to!(
&format!(
indoc!(
r#"
app "test" provides [main] to "./platform"
{}
a : Result [A U8 U8 U8] []
a = Ok (A 15 23 47)
main =
@THasher []
|> Hash.hash a
|> tRead
"#
),
TEST_HASHER,
),
RocList::from_slice(&[
1, // Ok
// A is skipped because it is a newtype
15, 23, 47
]),
RocList<u8>
)
}
#[test]
fn hash_heterogenous_tags() {
assert_evals_to!(
&format!(
indoc!(
r#"
app "test" provides [main] to "./platform"
{}
a : [A U8 U8, B {{ a: U8 }}, C Str]
a = A 15 23
b : [A U8 U8, B {{ a: U8 }}, C Str]
b = B {{ a: 37 }}
c : [A U8 U8, B {{ a: U8 }}, C Str]
c = C "abc"
main =
@THasher []
|> Hash.hash a
|> Hash.hash b
|> Hash.hash c
|> tRead
"#
),
TEST_HASHER,
),
RocList::from_slice(&[
0, // dicsr A
15, 23, // payloads A
1, // discr B
37, // payloads B
2, // discr C
97, 98, 99 // payloads C
]),
RocList<u8>
)
}
#[test]
fn hash_recursive_tag_union() {
assert_evals_to!(
&format!(
indoc!(
r#"
app "test" provides [main] to "./platform"
{}
ConsList : [Cons U8 ConsList, Nil]
c : ConsList
c = Cons 1 (Cons 2 Nil)
main =
@THasher []
|> Hash.hash c
|> tRead
"#
),
TEST_HASHER,
),
RocList::from_slice(&[
0, 1, // Cons 1
0, 2, // Cons 2
1, // Nil
]),
RocList<u8>
)
}
#[test]
fn derived_hash_for_opaque_record() {
assert_evals_to!(
&format!(
indoc!(
r#"
app "test" provides [main] to "./platform"
{}
Q := {{ a: U8, b: U8, c: U8 }} has [Hash]
q = @Q {{ a: 15, b: 27, c: 31 }}
main =
@THasher []
|> Hash.hash q
|> tRead
"#
),
TEST_HASHER,
),
RocList::from_slice(&[15, 27, 31]),
RocList<u8>
)
}
}
}
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
mod eq {
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
use indoc::indoc;
use roc_std::RocStr;
#[test]
fn custom_eq_impl() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
LyingEq := U8 has [Eq {isEq}]
isEq = \@LyingEq m, @LyingEq n -> m != n
main =
a = @LyingEq 10
b = @LyingEq 5
c = @LyingEq 5
if Bool.isEq a b && !(Bool.isEq b c) then
"okay"
else
"fail"
"#
),
RocStr::from("okay"),
RocStr
)
}
#[test]
fn custom_eq_impl_for_fn_opaque() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Q := ({} -> Str) has [Eq {isEq: isEqQ}]
isEqQ = \@Q _, @Q _ -> Bool.true
main = isEqQ (@Q \{} -> "a") (@Q \{} -> "a")
"#
),
true,
bool
)
}
#[test]
#[ignore = "needs https://github.com/roc-lang/roc/issues/4557 first"]
fn custom_eq_impl_for_fn_opaque_material() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Q := ({} -> Str) has [Eq {isEq: isEqQ}]
isEqQ = \@Q f1, @Q f2 -> (f1 {} == f2 {})
main = isEqQ (@Q \{} -> "a") (@Q \{} -> "a")
"#
),
true,
bool
)
}
#[test]
fn derive_structural_eq() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main = Bool.isEq 10u8 10u8
"#
),
true,
bool
)
}
#[test]
fn derive_structural_eq_for_opaque() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Q := U8 has [Eq]
main = (@Q 15) == (@Q 15)
"#
),
true,
bool
)
}
}