Merge pull request #7005 from smores56/deprecate-backpassing

Deprecate backpassing to prepare for eventual removal
This commit is contained in:
Sam Mohr 2024-08-18 08:08:29 -04:00 committed by GitHub
commit 7c7485bb04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 818 additions and 556 deletions

View file

@ -1,4 +1,4 @@
interface Base64.Decode exposes [fromBytes] imports [] module [fromBytes]
import Bytes.Decode exposing [ByteDecoder, DecodeProblem] import Bytes.Decode exposing [ByteDecoder, DecodeProblem]
@ -12,40 +12,39 @@ decodeBase64 = \width -> Bytes.Decode.loop loopHelp { remaining: width, string:
loopHelp : { remaining : U64, string : Str } -> ByteDecoder (Bytes.Decode.Step { remaining : U64, string : Str } Str) loopHelp : { remaining : U64, string : Str } -> ByteDecoder (Bytes.Decode.Step { remaining : U64, string : Str } Str)
loopHelp = \{ remaining, string } -> loopHelp = \{ remaining, string } ->
if remaining >= 3 then if remaining >= 3 then
x, y, z <- Bytes.Decode.map3 Bytes.Decode.u8 Bytes.Decode.u8 Bytes.Decode.u8 Bytes.Decode.map3 Bytes.Decode.u8 Bytes.Decode.u8 Bytes.Decode.u8 \x, y, z ->
a : U32
a = Num.intCast x
b : U32
b = Num.intCast y
c : U32
c = Num.intCast z
combined = Num.bitwiseOr (Num.bitwiseOr (Num.shiftLeftBy a 16) (Num.shiftLeftBy b 8)) c
a : U32 Loop {
a = Num.intCast x remaining: remaining - 3,
b : U32 string: Str.concat string (bitsToChars combined 0),
b = Num.intCast y }
c : U32
c = Num.intCast z
combined = Num.bitwiseOr (Num.bitwiseOr (Num.shiftLeftBy a 16) (Num.shiftLeftBy b 8)) c
Loop {
remaining: remaining - 3,
string: Str.concat string (bitsToChars combined 0),
}
else if remaining == 0 then else if remaining == 0 then
Bytes.Decode.succeed (Done string) Bytes.Decode.succeed (Done string)
else if remaining == 2 then else if remaining == 2 then
x, y <- Bytes.Decode.map2 Bytes.Decode.u8 Bytes.Decode.u8 Bytes.Decode.map2 Bytes.Decode.u8 Bytes.Decode.u8 \x, y ->
a : U32 a : U32
a = Num.intCast x a = Num.intCast x
b : U32 b : U32
b = Num.intCast y b = Num.intCast y
combined = Num.bitwiseOr (Num.shiftLeftBy a 16) (Num.shiftLeftBy b 8) combined = Num.bitwiseOr (Num.shiftLeftBy a 16) (Num.shiftLeftBy b 8)
Done (Str.concat string (bitsToChars combined 1)) Done (Str.concat string (bitsToChars combined 1))
else else
# remaining = 1 # remaining = 1
x <- Bytes.Decode.map Bytes.Decode.u8 Bytes.Decode.map Bytes.Decode.u8 \x ->
a : U32 a : U32
a = Num.intCast x a = Num.intCast x
Done (Str.concat string (bitsToChars (Num.shiftLeftBy a 16) 2)) Done (Str.concat string (bitsToChars (Num.shiftLeftBy a 16) 2))
bitsToChars : U32, Int * -> Str bitsToChars : U32, Int * -> Str
bitsToChars = \bits, missing -> bitsToChars = \bits, missing ->

View file

@ -1,12 +1,11 @@
app "cfold" app [main] { pf: platform "platform/main.roc" }
packages { pf: "platform/main.roc" }
imports [pf.Task] import pf.Task
provides [main] to pf
# adapted from https://github.com/koka-lang/koka/blob/master/test/bench/haskell/cfold.hs # adapted from https://github.com/koka-lang/koka/blob/master/test/bench/haskell/cfold.hs
main : Task.Task {} [] main : Task.Task {} []
main = main =
inputResult <- Task.attempt Task.getInt inputResult = Task.getInt |> Task.result!
when inputResult is when inputResult is
Ok n -> Ok n ->

View file

@ -1,14 +1,13 @@
app "deriv" app [main] { pf: platform "platform/main.roc" }
packages { pf: "platform/main.roc" }
imports [pf.Task] import pf.Task
provides [main] to pf
# based on: https://github.com/koka-lang/koka/blob/master/test/bench/haskell/deriv.hs # based on: https://github.com/koka-lang/koka/blob/master/test/bench/haskell/deriv.hs
IO a : Task.Task a [] IO a : Task.Task a []
main : Task.Task {} [] main : Task.Task {} []
main = main =
inputResult <- Task.attempt Task.getInt inputResult = Task.getInt |> Task.result!
when inputResult is when inputResult is
Ok n -> Ok n ->
@ -25,11 +24,12 @@ main =
Task.putLine "Error: Failed to get Integer from stdin." Task.putLine "Error: Failed to get Integer from stdin."
nestHelp : I64, (I64, Expr -> IO Expr), I64, Expr -> IO Expr nestHelp : I64, (I64, Expr -> IO Expr), I64, Expr -> IO Expr
nestHelp = \s, f, m, x -> when m is nestHelp = \s, f, m, x ->
0 -> Task.succeed x when m is
_ -> 0 -> Task.succeed x
w <- Task.after (f (s - m) x) _ ->
nestHelp s f (m - 1) w Task.after (f (s - m) x) \w ->
nestHelp s f (m - 1) w
nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr
nest = \f, n, e -> nestHelp n f n e nest = \f, n, e -> nestHelp n f n e

View file

@ -1,11 +1,10 @@
app "nqueens" app [main] { pf: platform "platform/main.roc" }
packages { pf: "platform/main.roc" }
imports [pf.Task] import pf.Task
provides [main] to pf
main : Task.Task {} [] main : Task.Task {} []
main = main =
inputResult <- Task.attempt Task.getInt inputResult = Task.getInt |> Task.result!
when inputResult is when inputResult is
Ok n -> Ok n ->

View file

@ -1,6 +1,20 @@
interface Task module [
exposes [Task, succeed, fail, after, map, putLine, putInt, getInt, forever, loop, attempt] Task,
imports [pf.Effect] await,
succeed,
fail,
after,
map,
result,
putLine,
putInt,
getInt,
forever,
loop,
attempt,
]
import pf.Effect
Task ok err : Effect.Effect (Result ok err) Task ok err : Effect.Effect (Result ok err)
@ -24,7 +38,7 @@ loop = \state, step ->
\res -> \res ->
when res is when res is
Ok (Step newState) -> Step newState Ok (Step newState) -> Step newState
Ok (Done result) -> Done (Ok result) Ok (Done res2) -> Done (Ok res2)
Err e -> Done (Err e) Err e -> Done (Err e)
Effect.loop state looper Effect.loop state looper
@ -41,8 +55,17 @@ after : Task a err, (a -> Task b err) -> Task b err
after = \effect, transform -> after = \effect, transform ->
Effect.after Effect.after
effect effect
\result -> \res ->
when result is when res is
Ok a -> transform a
Err err -> Task.fail err
await : Task a err, (a -> Task b err) -> Task b err
await = \effect, transform ->
Effect.after
effect
\res ->
when res is
Ok a -> transform a Ok a -> transform a
Err err -> Task.fail err Err err -> Task.fail err
@ -50,8 +73,8 @@ attempt : Task a b, (Result a b -> Task c d) -> Task c d
attempt = \task, transform -> attempt = \task, transform ->
Effect.after Effect.after
task task
\result -> \res ->
when result is when res is
Ok ok -> transform (Ok ok) Ok ok -> transform (Ok ok)
Err err -> transform (Err err) Err err -> transform (Err err)
@ -59,11 +82,17 @@ map : Task a err, (a -> b) -> Task b err
map = \effect, transform -> map = \effect, transform ->
Effect.map Effect.map
effect effect
\result -> \res ->
when result is when res is
Ok a -> Ok (transform a) Ok a -> Ok (transform a)
Err err -> Err err Err err -> Err err
result : Task ok err -> Task (Result ok err) *
result = \effect ->
Effect.after
effect
\res -> Task.succeed res
putLine : Str -> Task {} * putLine : Str -> Task {} *
putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {})

View file

@ -1,11 +1,11 @@
app "quicksortapp" app [main] { pf: platform "platform/main.roc" }
packages { pf: "platform/main.roc" }
imports [pf.Task, Quicksort] import pf.Task
provides [main] to pf import Quicksort
main : Task.Task {} [] main : Task.Task {} []
main = main =
inputResult <- Task.attempt Task.getInt inputResult = Task.getInt |> Task.result!
when inputResult is when inputResult is
Ok n -> Ok n ->

View file

@ -1,7 +1,6 @@
app "rbtree-ck" app [main] { pf: platform "platform/main.roc" }
packages { pf: "platform/main.roc" }
imports [pf.Task] import pf.Task
provides [main] to pf
Color : [Red, Black] Color : [Red, Black]
@ -40,7 +39,7 @@ fold = \f, tree, b ->
main : Task.Task {} [] main : Task.Task {} []
main = main =
inputResult <- Task.attempt Task.getInt inputResult = Task.getInt |> Task.result!
when inputResult is when inputResult is
Ok n -> Ok n ->

View file

@ -1,7 +1,6 @@
app "rbtree-del" app [main] { pf: platform "platform/main.roc" }
packages { pf: "platform/main.roc" }
imports [pf.Task] import pf.Task
provides [main] to pf
Color : [Red, Black] Color : [Red, Black]
@ -13,7 +12,7 @@ ConsList a : [Nil, Cons a (ConsList a)]
main : Task.Task {} [] main : Task.Task {} []
main = main =
inputResult <- Task.attempt Task.getInt inputResult = Task.getInt |> Task.result!
when inputResult is when inputResult is
Ok n -> Ok n ->

View file

@ -2,18 +2,18 @@ app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/downlo
import pf.Stdin import pf.Stdin
import pf.Stdout import pf.Stdout
import pf.Task exposing [await, loop] import pf.Task exposing [Task, loop]
main = main =
_ <- await (Stdout.line "\nLet's count down from 3 together - all you have to do is press <ENTER>.") Stdout.line! "\nLet's count down from 3 together - all you have to do is press <ENTER>."
_ <- await Stdin.line _ = Stdin.line!
loop 3 tick loop 3 tick
tick = \n -> tick = \n ->
if n == 0 then if n == 0 then
_ <- await (Stdout.line "🎉 SURPRISE! Happy Birthday! 🎂") Stdout.line! "🎉 SURPRISE! Happy Birthday! 🎂"
Task.ok (Done {}) Task.ok (Done {})
else else
_ <- await (n |> Num.toStr |> \s -> "$(s)..." |> Stdout.line) Stdout.line! (n |> Num.toStr |> \s -> "$(s)...")
_ <- await Stdin.line _ = Stdin.line!
Task.ok (Step (n - 1)) Task.ok (Step (n - 1))

View file

@ -5,7 +5,7 @@ import pf.Stdout
import pf.Task exposing [Task] import pf.Task exposing [Task]
main = main =
_ <- Task.await (Stdout.line "🗣 Shout into this cave and hear the echo! 👂👂👂") Stdout.line! "🗣 Shout into this cave and hear the echo! 👂👂👂"
Task.loop {} tick Task.loop {} tick

View file

@ -55,23 +55,23 @@ numParam = \{ name } ->
{ params: [param], parser } { params: [param], parser }
cliMap : ArgParser a, (a -> b) -> ArgParser b cliMap : ArgParser a, (a -> b) -> ArgParser b
cliMap = \{ params, parser }, mapper -> { cliMap = \{ params, parser }, mapper ->
params, mappedParser = \args ->
parser: \args -> (data, afterData) = parser? args
(data, afterData) <- parser args
|> Result.try
Ok (mapper data, afterData), Ok (mapper data, afterData)
}
{
params,
parser: mappedParser,
}
cliBuild : ArgParser a, ArgParser b, (a, b -> c) -> ArgParser c cliBuild : ArgParser a, ArgParser b, (a, b -> c) -> ArgParser c
cliBuild = \firstWeaver, secondWeaver, combine -> cliBuild = \firstWeaver, secondWeaver, combine ->
allParams = List.concat firstWeaver.params secondWeaver.params allParams = List.concat firstWeaver.params secondWeaver.params
combinedParser = \args -> combinedParser = \args ->
(firstValue, afterFirst) <- firstWeaver.parser args (firstValue, afterFirst) = firstWeaver.parser? args
|> Result.try (secondValue, afterSecond) = secondWeaver.parser? afterFirst
(secondValue, afterSecond) <- secondWeaver.parser afterFirst
|> Result.try
Ok (combine firstValue secondValue, afterSecond) Ok (combine firstValue secondValue, afterSecond)

View file

@ -26,18 +26,17 @@ Letter : [A, B, C, Other]
letterParser : Parser (List U8) Letter letterParser : Parser (List U8) Letter
letterParser = letterParser =
input <- buildPrimitiveParser buildPrimitiveParser \input ->
valResult =
when input is
[] -> Err (ParsingFailure "Nothing to parse")
['A', ..] -> Ok A
['B', ..] -> Ok B
['C', ..] -> Ok C
_ -> Ok Other
valResult = valResult
when input is |> Result.map \val -> { val, input: List.dropFirst input 1 }
[] -> Err (ParsingFailure "Nothing to parse")
['A', ..] -> Ok A
['B', ..] -> Ok B
['C', ..] -> Ok C
_ -> Ok Other
valResult
|> Result.map \val -> { val, input: List.dropFirst input 1 }
expect expect
input = "B" input = "B"

View file

@ -23,6 +23,7 @@ mod cli_run {
use serial_test::serial; use serial_test::serial;
use std::iter; use std::iter;
use std::path::Path; use std::path::Path;
use std::process::ExitStatus;
#[cfg(all(unix, not(target_os = "macos")))] #[cfg(all(unix, not(target_os = "macos")))]
const ALLOW_VALGRIND: bool = true; const ALLOW_VALGRIND: bool = true;
@ -106,6 +107,11 @@ mod cli_run {
assert_multiline_str_eq!(err.as_str(), expected); assert_multiline_str_eq!(err.as_str(), expected);
} }
fn assert_valid_roc_check_status(status: ExitStatus) {
// 0 means no errors or warnings, 2 means just warnings
assert!(status.code().is_some_and(|code| code == 0 || code == 2))
}
fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) { fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[], &[]); let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[], &[]);
@ -803,7 +809,7 @@ mod cli_run {
fn check_virtual_dom_server() { fn check_virtual_dom_server() {
let path = file_path_from_root("examples/virtual-dom-wip", "example-server.roc"); let path = file_path_from_root("examples/virtual-dom-wip", "example-server.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success()); assert_valid_roc_check_status(out.status);
} }
// TODO: write a new test once mono bugs are resolved in investigation // TODO: write a new test once mono bugs are resolved in investigation
@ -812,7 +818,7 @@ mod cli_run {
fn check_virtual_dom_client() { fn check_virtual_dom_client() {
let path = file_path_from_root("examples/virtual-dom-wip", "example-client.roc"); let path = file_path_from_root("examples/virtual-dom-wip", "example-client.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success()); assert_valid_roc_check_status(out.status);
} }
#[test] #[test]
@ -821,7 +827,7 @@ mod cli_run {
fn cli_countdown_check() { fn cli_countdown_check() {
let path = file_path_from_root("crates/cli/tests/cli", "countdown.roc"); let path = file_path_from_root("crates/cli/tests/cli", "countdown.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success()); assert_valid_roc_check_status(out.status);
} }
#[test] #[test]
@ -830,7 +836,7 @@ mod cli_run {
fn cli_echo_check() { fn cli_echo_check() {
let path = file_path_from_root("crates/cli/tests/cli", "echo.roc"); let path = file_path_from_root("crates/cli/tests/cli", "echo.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success()); assert_valid_roc_check_status(out.status);
} }
#[test] #[test]
@ -839,7 +845,7 @@ mod cli_run {
fn cli_file_check() { fn cli_file_check() {
let path = file_path_from_root("crates/cli/tests/cli", "fileBROKEN.roc"); let path = file_path_from_root("crates/cli/tests/cli", "fileBROKEN.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success()); assert_valid_roc_check_status(out.status);
} }
#[test] #[test]
@ -848,7 +854,8 @@ mod cli_run {
fn cli_form_check() { fn cli_form_check() {
let path = file_path_from_root("crates/cli/tests/cli", "form.roc"); let path = file_path_from_root("crates/cli/tests/cli", "form.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success()); dbg!(out.stdout, out.stderr);
assert_valid_roc_check_status(out.status);
} }
#[test] #[test]
@ -857,7 +864,7 @@ mod cli_run {
fn cli_http_get_check() { fn cli_http_get_check() {
let path = file_path_from_root("crates/cli/tests/cli", "http-get.roc"); let path = file_path_from_root("crates/cli/tests/cli", "http-get.roc");
let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]);
assert!(out.status.success()); assert_valid_roc_check_status(out.status);
} }
#[test] #[test]
@ -1482,9 +1489,9 @@ mod cli_run {
Something is off with the body of the main definition: Something is off with the body of the main definition:
6 main : Str -> Task {} [] 5 main : Str -> Task {} []
7 main = /_ -> 6 main = /_ ->
8 "this is a string, not a Task {} [] function like the platform expects." 7 "this is a string, not a Task {} [] function like the platform expects."
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The body is a string of type: The body is a string of type:

View file

@ -1,7 +1,6 @@
app "type-error" app [main] { pf: platform "../../../../examples/cli/false-interpreter/platform/main.roc" }
packages { pf: "../../../../examples/cli/false-interpreter/platform/main.roc" }
imports [pf.Task.{ Task }] import pf.Task exposing [Task]
provides [main] to pf
main : Str -> Task {} [] main : Str -> Task {} []
main = \_ -> main = \_ ->

View file

@ -129,8 +129,8 @@ hashDict = \hasher, dict -> Hash.hashUnordered hasher (toList dict) List.walk
toInspectorDict : Dict k v -> Inspector f where k implements Inspect & Hash & Eq, v implements Inspect, f implements InspectFormatter toInspectorDict : Dict k v -> Inspector f where k implements Inspect & Hash & Eq, v implements Inspect, f implements InspectFormatter
toInspectorDict = \dict -> toInspectorDict = \dict ->
fmt <- Inspect.custom Inspect.custom \fmt ->
Inspect.apply (Inspect.dict dict walk Inspect.toInspector Inspect.toInspector) fmt Inspect.apply (Inspect.dict dict walk Inspect.toInspector Inspect.toInspector) fmt
## Return an empty dictionary. ## Return an empty dictionary.
## ```roc ## ```roc
@ -894,9 +894,9 @@ calcNumBuckets = \shifts ->
maxBucketCount maxBucketCount
fillBucketsFromData = \buckets0, data, shifts -> fillBucketsFromData = \buckets0, data, shifts ->
buckets1, (key, _), dataIndex <- List.walkWithIndex data buckets0 List.walkWithIndex data buckets0 \buckets1, (key, _), dataIndex ->
(bucketIndex, distAndFingerprint) = nextWhileLess buckets1 key shifts (bucketIndex, distAndFingerprint) = nextWhileLess buckets1 key shifts
placeAndShiftUp buckets1 { distAndFingerprint, dataIndex: Num.toU32 dataIndex } bucketIndex placeAndShiftUp buckets1 { distAndFingerprint, dataIndex: Num.toU32 dataIndex } bucketIndex
nextWhileLess : List Bucket, k, U8 -> (U64, U32) where k implements Hash & Eq nextWhileLess : List Bucket, k, U8 -> (U64, U32) where k implements Hash & Eq
nextWhileLess = \buckets, key, shifts -> nextWhileLess = \buckets, key, shifts ->
@ -1213,15 +1213,15 @@ expect
] ]
dict = dict =
acc, k <- List.walk badKeys (Dict.empty {}) List.walk badKeys (Dict.empty {}) \acc, k ->
Dict.update acc k \val -> Dict.update acc k \val ->
when val is when val is
Present p -> Present (p |> Num.addWrap 1) Present p -> Present (p |> Num.addWrap 1)
Missing -> Present 0 Missing -> Present 0
allInsertedCorrectly = allInsertedCorrectly =
acc, k <- List.walk badKeys Bool.true List.walk badKeys Bool.true \acc, k ->
acc && Dict.contains dict k acc && Dict.contains dict k
allInsertedCorrectly allInsertedCorrectly

View file

@ -138,203 +138,203 @@ dbgInit = \{} -> @DbgFormatter { data: "" }
dbgList : list, ElemWalker (DbgFormatter, Bool) list elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter dbgList : list, ElemWalker (DbgFormatter, Bool) list elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgList = \content, walkFn, toDbgInspector -> dbgList = \content, walkFn, toDbgInspector ->
f0 <- custom custom \f0 ->
dbgWrite f0 "[" dbgWrite f0 "["
|> \f1 -> |> \f1 ->
(f2, prependSep), elem <- walkFn content (f1, Bool.false) walkFn content (f1, Bool.false) \(f2, prependSep), elem ->
f3 = f3 =
if prependSep then if prependSep then
dbgWrite f2 ", " dbgWrite f2 ", "
else else
f2 f2
elem elem
|> toDbgInspector |> toDbgInspector
|> apply f3 |> apply f3
|> \f4 -> (f4, Bool.true) |> \f4 -> (f4, Bool.true)
|> .0 |> .0
|> dbgWrite "]" |> dbgWrite "]"
dbgSet : set, ElemWalker (DbgFormatter, Bool) set elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter dbgSet : set, ElemWalker (DbgFormatter, Bool) set elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgSet = \content, walkFn, toDbgInspector -> dbgSet = \content, walkFn, toDbgInspector ->
f0 <- custom custom \f0 ->
dbgWrite f0 "{" dbgWrite f0 "{"
|> \f1 -> |> \f1 ->
(f2, prependSep), elem <- walkFn content (f1, Bool.false) walkFn content (f1, Bool.false) \(f2, prependSep), elem ->
f3 = f3 =
if prependSep then if prependSep then
dbgWrite f2 ", " dbgWrite f2 ", "
else else
f2 f2
elem elem
|> toDbgInspector |> toDbgInspector
|> apply f3 |> apply f3
|> \f4 -> (f4, Bool.true) |> \f4 -> (f4, Bool.true)
|> .0 |> .0
|> dbgWrite "}" |> dbgWrite "}"
dbgDict : dict, KeyValWalker (DbgFormatter, Bool) dict key value, (key -> Inspector DbgFormatter), (value -> Inspector DbgFormatter) -> Inspector DbgFormatter dbgDict : dict, KeyValWalker (DbgFormatter, Bool) dict key value, (key -> Inspector DbgFormatter), (value -> Inspector DbgFormatter) -> Inspector DbgFormatter
dbgDict = \d, walkFn, keyToInspector, valueToInspector -> dbgDict = \d, walkFn, keyToInspector, valueToInspector ->
f0 <- custom custom \f0 ->
dbgWrite f0 "{" dbgWrite f0 "{"
|> \f1 -> |> \f1 ->
(f2, prependSep), key, value <- walkFn d (f1, Bool.false) walkFn d (f1, Bool.false) \(f2, prependSep), key, value ->
f3 = f3 =
if prependSep then if prependSep then
dbgWrite f2 ", " dbgWrite f2 ", "
else else
f2 f2
apply (keyToInspector key) f3 apply (keyToInspector key) f3
|> dbgWrite ": " |> dbgWrite ": "
|> \x -> apply (valueToInspector value) x |> \x -> apply (valueToInspector value) x
|> \f4 -> (f4, Bool.true) |> \f4 -> (f4, Bool.true)
|> .0 |> .0
|> dbgWrite "}" |> dbgWrite "}"
dbgTag : Str, List (Inspector DbgFormatter) -> Inspector DbgFormatter dbgTag : Str, List (Inspector DbgFormatter) -> Inspector DbgFormatter
dbgTag = \name, fields -> dbgTag = \name, fields ->
if List.isEmpty fields then if List.isEmpty fields then
f0 <- custom custom \f0 ->
dbgWrite f0 name dbgWrite f0 name
else else
f0 <- custom custom \f0 ->
dbgWrite f0 "(" dbgWrite f0 "("
|> dbgWrite name |> dbgWrite name
|> \f1 -> |> \f1 ->
f2, inspector <- List.walk fields f1 List.walk fields f1 \f2, inspector ->
dbgWrite f2 " " dbgWrite f2 " "
|> \x -> apply inspector x |> \x -> apply inspector x
|> dbgWrite ")" |> dbgWrite ")"
dbgTuple : List (Inspector DbgFormatter) -> Inspector DbgFormatter dbgTuple : List (Inspector DbgFormatter) -> Inspector DbgFormatter
dbgTuple = \fields -> dbgTuple = \fields ->
f0 <- custom custom \f0 ->
dbgWrite f0 "(" dbgWrite f0 "("
|> \f1 -> |> \f1 ->
(f2, prependSep), inspector <- List.walk fields (f1, Bool.false) List.walk fields (f1, Bool.false) \(f2, prependSep), inspector ->
f3 = f3 =
if prependSep then if prependSep then
dbgWrite f2 ", " dbgWrite f2 ", "
else else
f2 f2
apply inspector f3 apply inspector f3
|> \f4 -> (f4, Bool.true) |> \f4 -> (f4, Bool.true)
|> .0 |> .0
|> dbgWrite ")" |> dbgWrite ")"
dbgRecord : List { key : Str, value : Inspector DbgFormatter } -> Inspector DbgFormatter dbgRecord : List { key : Str, value : Inspector DbgFormatter } -> Inspector DbgFormatter
dbgRecord = \fields -> dbgRecord = \fields ->
f0 <- custom custom \f0 ->
dbgWrite f0 "{" dbgWrite f0 "{"
|> \f1 -> |> \f1 ->
(f2, prependSep), { key, value } <- List.walk fields (f1, Bool.false) List.walk fields (f1, Bool.false) \(f2, prependSep), { key, value } ->
f3 = f3 =
if prependSep then if prependSep then
dbgWrite f2 ", " dbgWrite f2 ", "
else else
f2 f2
dbgWrite f3 key dbgWrite f3 key
|> dbgWrite ": " |> dbgWrite ": "
|> \x -> apply value x |> \x -> apply value x
|> \f4 -> (f4, Bool.true) |> \f4 -> (f4, Bool.true)
|> .0 |> .0
|> dbgWrite "}" |> dbgWrite "}"
dbgBool : Bool -> Inspector DbgFormatter dbgBool : Bool -> Inspector DbgFormatter
dbgBool = \b -> dbgBool = \b ->
if b then if b then
f0 <- custom custom \f0 ->
dbgWrite f0 "Bool.true" dbgWrite f0 "Bool.true"
else else
f0 <- custom custom \f0 ->
dbgWrite f0 "Bool.false" dbgWrite f0 "Bool.false"
dbgStr : Str -> Inspector DbgFormatter dbgStr : Str -> Inspector DbgFormatter
dbgStr = \s -> dbgStr = \s ->
f0 <- custom custom \f0 ->
f0 f0
|> dbgWrite "\"" |> dbgWrite "\""
|> dbgWrite s # TODO: Should we be escaping strings for dbg/logging? |> dbgWrite s # TODO: Should we be escaping strings for dbg/logging?
|> dbgWrite "\"" |> dbgWrite "\""
dbgOpaque : * -> Inspector DbgFormatter dbgOpaque : * -> Inspector DbgFormatter
dbgOpaque = \_ -> dbgOpaque = \_ ->
f0 <- custom custom \f0 ->
dbgWrite f0 "<opaque>" dbgWrite f0 "<opaque>"
dbgFunction : * -> Inspector DbgFormatter dbgFunction : * -> Inspector DbgFormatter
dbgFunction = \_ -> dbgFunction = \_ ->
f0 <- custom custom \f0 ->
dbgWrite f0 "<function>" dbgWrite f0 "<function>"
dbgU8 : U8 -> Inspector DbgFormatter dbgU8 : U8 -> Inspector DbgFormatter
dbgU8 = \num -> dbgU8 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgI8 : I8 -> Inspector DbgFormatter dbgI8 : I8 -> Inspector DbgFormatter
dbgI8 = \num -> dbgI8 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgU16 : U16 -> Inspector DbgFormatter dbgU16 : U16 -> Inspector DbgFormatter
dbgU16 = \num -> dbgU16 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgI16 : I16 -> Inspector DbgFormatter dbgI16 : I16 -> Inspector DbgFormatter
dbgI16 = \num -> dbgI16 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgU32 : U32 -> Inspector DbgFormatter dbgU32 : U32 -> Inspector DbgFormatter
dbgU32 = \num -> dbgU32 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgI32 : I32 -> Inspector DbgFormatter dbgI32 : I32 -> Inspector DbgFormatter
dbgI32 = \num -> dbgI32 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgU64 : U64 -> Inspector DbgFormatter dbgU64 : U64 -> Inspector DbgFormatter
dbgU64 = \num -> dbgU64 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgI64 : I64 -> Inspector DbgFormatter dbgI64 : I64 -> Inspector DbgFormatter
dbgI64 = \num -> dbgI64 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgU128 : U128 -> Inspector DbgFormatter dbgU128 : U128 -> Inspector DbgFormatter
dbgU128 = \num -> dbgU128 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgI128 : I128 -> Inspector DbgFormatter dbgI128 : I128 -> Inspector DbgFormatter
dbgI128 = \num -> dbgI128 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgF32 : F32 -> Inspector DbgFormatter dbgF32 : F32 -> Inspector DbgFormatter
dbgF32 = \num -> dbgF32 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgF64 : F64 -> Inspector DbgFormatter dbgF64 : F64 -> Inspector DbgFormatter
dbgF64 = \num -> dbgF64 = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgDec : Dec -> Inspector DbgFormatter dbgDec : Dec -> Inspector DbgFormatter
dbgDec = \num -> dbgDec = \num ->
f0 <- custom custom \f0 ->
dbgWrite f0 (num |> Num.toStr) dbgWrite f0 (num |> Num.toStr)
dbgWrite : DbgFormatter, Str -> DbgFormatter dbgWrite : DbgFormatter, Str -> DbgFormatter
dbgWrite = \@DbgFormatter { data }, added -> dbgWrite = \@DbgFormatter { data }, added ->

View file

@ -62,8 +62,8 @@ hashSet = \hasher, @Set inner -> Hash.hash hasher inner
toInspectorSet : Set k -> Inspector f where k implements Inspect & Hash & Eq, f implements InspectFormatter toInspectorSet : Set k -> Inspector f where k implements Inspect & Hash & Eq, f implements InspectFormatter
toInspectorSet = \set -> toInspectorSet = \set ->
fmt <- Inspect.custom Inspect.custom \fmt ->
Inspect.apply (Inspect.set set walk Inspect.toInspector) fmt Inspect.apply (Inspect.set set walk Inspect.toInspector) fmt
## Creates a new empty `Set`. ## Creates a new empty `Set`.
## ```roc ## ```roc

View file

@ -12,6 +12,7 @@ use roc_parse::ast::{
AssignedField, Collection, ModuleImportParams, OldRecordBuilderField, Pattern, StrLiteral, AssignedField, Collection, ModuleImportParams, OldRecordBuilderField, Pattern, StrLiteral,
StrSegment, TypeAnnotation, ValueDef, WhenBranch, StrSegment, TypeAnnotation, ValueDef, WhenBranch,
}; };
use roc_problem::can::Problem;
use roc_region::all::{LineInfo, Loc, Region}; use roc_region::all::{LineInfo, Loc, Region};
// BinOp precedence logic adapted from Gluon by Markus Westerlind // BinOp precedence logic adapted from Gluon by Markus Westerlind
@ -74,13 +75,14 @@ fn desugar_value_def<'a>(
src: &'a str, src: &'a str,
line_info: &mut Option<LineInfo>, line_info: &mut Option<LineInfo>,
module_path: &str, module_path: &str,
problems: &mut std::vec::Vec<Problem>,
) -> ValueDef<'a> { ) -> ValueDef<'a> {
use ValueDef::*; use ValueDef::*;
match def { match def {
Body(loc_pattern, loc_expr) => Body( Body(loc_pattern, loc_expr) => Body(
desugar_loc_pattern(arena, loc_pattern, src, line_info, module_path), desugar_loc_pattern(arena, loc_pattern, src, line_info, module_path, problems),
desugar_expr(arena, loc_expr, src, line_info, module_path), desugar_expr(arena, loc_expr, src, line_info, module_path, problems),
), ),
ann @ Annotation(_, _) => *ann, ann @ Annotation(_, _) => *ann,
AnnotatedBody { AnnotatedBody {
@ -93,16 +95,29 @@ fn desugar_value_def<'a>(
ann_pattern, ann_pattern,
ann_type, ann_type,
lines_between, lines_between,
body_pattern: desugar_loc_pattern(arena, body_pattern, src, line_info, module_path), body_pattern: desugar_loc_pattern(
body_expr: desugar_expr(arena, body_expr, src, line_info, module_path), arena,
body_pattern,
src,
line_info,
module_path,
problems,
),
body_expr: desugar_expr(arena, body_expr, src, line_info, module_path, problems),
}, },
Dbg { Dbg {
condition, condition,
preceding_comment, preceding_comment,
} => { } => {
let desugared_condition = let desugared_condition = &*arena.alloc(desugar_expr(
&*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); arena,
condition,
src,
line_info,
module_path,
problems,
));
Dbg { Dbg {
condition: desugared_condition, condition: desugared_condition,
preceding_comment: *preceding_comment, preceding_comment: *preceding_comment,
@ -112,8 +127,14 @@ fn desugar_value_def<'a>(
condition, condition,
preceding_comment, preceding_comment,
} => { } => {
let desugared_condition = let desugared_condition = &*arena.alloc(desugar_expr(
&*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); arena,
condition,
src,
line_info,
module_path,
problems,
));
Expect { Expect {
condition: desugared_condition, condition: desugared_condition,
preceding_comment: *preceding_comment, preceding_comment: *preceding_comment,
@ -123,8 +144,14 @@ fn desugar_value_def<'a>(
condition, condition,
preceding_comment, preceding_comment,
} => { } => {
let desugared_condition = let desugared_condition = &*arena.alloc(desugar_expr(
&*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); arena,
condition,
src,
line_info,
module_path,
problems,
));
ExpectFx { ExpectFx {
condition: desugared_condition, condition: desugared_condition,
preceding_comment: *preceding_comment, preceding_comment: *preceding_comment,
@ -141,7 +168,14 @@ fn desugar_value_def<'a>(
params.map(|ModuleImportParams { before, params }| ModuleImportParams { params.map(|ModuleImportParams { before, params }| ModuleImportParams {
before, before,
params: params.map(|params| { params: params.map(|params| {
desugar_field_collection(arena, *params, src, line_info, module_path) desugar_field_collection(
arena,
*params,
src,
line_info,
module_path,
problems,
)
}), }),
}); });
@ -174,7 +208,7 @@ fn desugar_value_def<'a>(
)), )),
lines_between: &[], lines_between: &[],
body_pattern: new_pat, body_pattern: new_pat,
body_expr: desugar_expr(arena, stmt_expr, src, line_info, module_path), body_expr: desugar_expr(arena, stmt_expr, src, line_info, module_path, problems),
} }
} }
} }
@ -187,9 +221,17 @@ pub fn desugar_defs_node_values<'a>(
line_info: &mut Option<LineInfo>, line_info: &mut Option<LineInfo>,
module_path: &str, module_path: &str,
top_level_def: bool, top_level_def: bool,
problems: &mut std::vec::Vec<Problem>,
) { ) {
for value_def in defs.value_defs.iter_mut() { for value_def in defs.value_defs.iter_mut() {
*value_def = desugar_value_def(arena, arena.alloc(*value_def), src, line_info, module_path); *value_def = desugar_value_def(
arena,
arena.alloc(*value_def),
src,
line_info,
module_path,
problems,
);
} }
// `desugar_defs_node_values` is called recursively in `desugar_expr` // `desugar_defs_node_values` is called recursively in `desugar_expr`
@ -312,6 +354,7 @@ pub fn desugar_expr<'a>(
src: &'a str, src: &'a str,
line_info: &mut Option<LineInfo>, line_info: &mut Option<LineInfo>,
module_path: &str, module_path: &str,
problems: &mut std::vec::Vec<Problem>,
) -> &'a Loc<Expr<'a>> { ) -> &'a Loc<Expr<'a>> {
match &loc_expr.value { match &loc_expr.value {
Float(..) Float(..)
@ -344,6 +387,7 @@ pub fn desugar_expr<'a>(
src, src,
line_info, line_info,
module_path, module_path,
problems,
))); )));
arena.alloc(Loc { region, value }) arena.alloc(Loc { region, value })
@ -352,7 +396,7 @@ pub fn desugar_expr<'a>(
let region = loc_expr.region; let region = loc_expr.region;
let new_lines = Vec::from_iter_in( let new_lines = Vec::from_iter_in(
lines.iter().map(|segments| { lines.iter().map(|segments| {
desugar_str_segments(arena, segments, src, line_info, module_path) desugar_str_segments(arena, segments, src, line_info, module_path, problems)
}), }),
arena, arena,
); );
@ -375,6 +419,7 @@ pub fn desugar_expr<'a>(
src, src,
line_info, line_info,
module_path, module_path,
problems,
) )
.value, .value,
paths, paths,
@ -389,7 +434,8 @@ pub fn desugar_expr<'a>(
target, target,
} => { } => {
let intermediate = arena.alloc(Loc::at(loc_expr.region, **sub_expr)); let intermediate = arena.alloc(Loc::at(loc_expr.region, **sub_expr));
let new_sub_loc_expr = desugar_expr(arena, intermediate, src, line_info, module_path); let new_sub_loc_expr =
desugar_expr(arena, intermediate, src, line_info, module_path, problems);
let new_sub_expr = arena.alloc(new_sub_loc_expr.value); let new_sub_expr = arena.alloc(new_sub_loc_expr.value);
arena.alloc(Loc::at( arena.alloc(Loc::at(
@ -413,6 +459,7 @@ pub fn desugar_expr<'a>(
src, src,
line_info, line_info,
module_path, module_path,
problems,
) )
.value, .value,
paths, paths,
@ -424,7 +471,14 @@ pub fn desugar_expr<'a>(
let mut new_items = Vec::with_capacity_in(items.len(), arena); let mut new_items = Vec::with_capacity_in(items.len(), arena);
for item in items.iter() { for item in items.iter() {
new_items.push(desugar_expr(arena, item, src, line_info, module_path)); new_items.push(desugar_expr(
arena,
item,
src,
line_info,
module_path,
problems,
));
} }
let new_items = new_items.into_bump_slice(); let new_items = new_items.into_bump_slice();
let value: Expr<'a> = List(items.replace_items(new_items)); let value: Expr<'a> = List(items.replace_items(new_items));
@ -435,7 +489,8 @@ pub fn desugar_expr<'a>(
}) })
} }
Record(fields) => { Record(fields) => {
let fields = desugar_field_collection(arena, *fields, src, line_info, module_path); let fields =
desugar_field_collection(arena, *fields, src, line_info, module_path, problems);
arena.alloc(Loc { arena.alloc(Loc {
region: loc_expr.region, region: loc_expr.region,
value: Record(fields), value: Record(fields),
@ -444,7 +499,7 @@ pub fn desugar_expr<'a>(
Tuple(fields) => { Tuple(fields) => {
let mut allocated = Vec::with_capacity_in(fields.len(), arena); let mut allocated = Vec::with_capacity_in(fields.len(), arena);
for field in fields.iter() { for field in fields.iter() {
let expr = desugar_expr(arena, field, src, line_info, module_path); let expr = desugar_expr(arena, field, src, line_info, module_path, problems);
allocated.push(expr); allocated.push(expr);
} }
let fields = fields.replace_items(allocated.into_bump_slice()); let fields = fields.replace_items(allocated.into_bump_slice());
@ -456,11 +511,12 @@ pub fn desugar_expr<'a>(
RecordUpdate { fields, update } => { RecordUpdate { fields, update } => {
// NOTE the `update` field is always a `Var { .. }`, we only desugar it to get rid of // NOTE the `update` field is always a `Var { .. }`, we only desugar it to get rid of
// any spaces before/after // any spaces before/after
let new_update = desugar_expr(arena, update, src, line_info, module_path); let new_update = desugar_expr(arena, update, src, line_info, module_path, problems);
let mut allocated = Vec::with_capacity_in(fields.len(), arena); let mut allocated = Vec::with_capacity_in(fields.len(), arena);
for field in fields.iter() { for field in fields.iter() {
let value = desugar_field(arena, &field.value, src, line_info, module_path); let value =
desugar_field(arena, &field.value, src, line_info, module_path, problems);
allocated.push(Loc { allocated.push(Loc {
value, value,
region: field.region, region: field.region,
@ -533,8 +589,8 @@ pub fn desugar_expr<'a>(
Closure(loc_patterns, loc_ret) => arena.alloc(Loc { Closure(loc_patterns, loc_ret) => arena.alloc(Loc {
region: loc_expr.region, region: loc_expr.region,
value: Closure( value: Closure(
desugar_loc_patterns(arena, loc_patterns, src, line_info, module_path), desugar_loc_patterns(arena, loc_patterns, src, line_info, module_path, problems),
desugar_expr(arena, loc_ret, src, line_info, module_path), desugar_expr(arena, loc_ret, src, line_info, module_path, problems),
), ),
}), }),
Backpassing(loc_patterns, loc_body, loc_ret) => { Backpassing(loc_patterns, loc_body, loc_ret) => {
@ -542,12 +598,19 @@ pub fn desugar_expr<'a>(
// //
// loc_ret // loc_ret
// first desugar the body, because it may contain |> let problem_region = Region::span_across(
let desugared_body = desugar_expr(arena, loc_body, src, line_info, module_path); &Region::across_all(loc_patterns.iter().map(|loc_pattern| &loc_pattern.region)),
&loc_body.region,
);
problems.push(Problem::DeprecatedBackpassing(problem_region));
let desugared_ret = desugar_expr(arena, loc_ret, src, line_info, module_path); // first desugar the body, because it may contain |>
let desugared_body =
desugar_expr(arena, loc_body, src, line_info, module_path, problems);
let desugared_ret = desugar_expr(arena, loc_ret, src, line_info, module_path, problems);
let desugared_loc_patterns = let desugared_loc_patterns =
desugar_loc_patterns(arena, loc_patterns, src, line_info, module_path); desugar_loc_patterns(arena, loc_patterns, src, line_info, module_path, problems);
let closure = Expr::Closure(desugared_loc_patterns, desugared_ret); let closure = Expr::Closure(desugared_loc_patterns, desugared_ret);
let loc_closure = Loc::at(loc_expr.region, closure); let loc_closure = Loc::at(loc_expr.region, closure);
@ -583,7 +646,7 @@ pub fn desugar_expr<'a>(
RecordBuilder { mapper, fields } => { RecordBuilder { mapper, fields } => {
// NOTE the `mapper` is always a `Var { .. }`, we only desugar it to get rid of // NOTE the `mapper` is always a `Var { .. }`, we only desugar it to get rid of
// any spaces before/after // any spaces before/after
let new_mapper = desugar_expr(arena, mapper, src, line_info, module_path); let new_mapper = desugar_expr(arena, mapper, src, line_info, module_path, problems);
if fields.is_empty() { if fields.is_empty() {
return arena.alloc(Loc { return arena.alloc(Loc {
@ -607,7 +670,8 @@ pub fn desugar_expr<'a>(
for field in fields.items { for field in fields.items {
let (name, value, ignored) = let (name, value, ignored) =
match desugar_field(arena, &field.value, src, line_info, module_path) { match desugar_field(arena, &field.value, src, line_info, module_path, problems)
{
AssignedField::RequiredValue(loc_name, _, loc_val) => { AssignedField::RequiredValue(loc_name, _, loc_val) => {
(loc_name, loc_val, false) (loc_name, loc_val, false)
} }
@ -845,11 +909,20 @@ pub fn desugar_expr<'a>(
src, src,
line_info, line_info,
module_path, module_path,
problems,
), ),
Defs(defs, loc_ret) => { Defs(defs, loc_ret) => {
let mut defs = (*defs).clone(); let mut defs = (*defs).clone();
desugar_defs_node_values(arena, &mut defs, src, line_info, module_path, false); desugar_defs_node_values(
let loc_ret = desugar_expr(arena, loc_ret, src, line_info, module_path); arena,
&mut defs,
src,
line_info,
module_path,
false,
problems,
);
let loc_ret = desugar_expr(arena, loc_ret, src, line_info, module_path, problems);
arena.alloc(Loc::at(loc_expr.region, Defs(arena.alloc(defs), loc_ret))) arena.alloc(Loc::at(loc_expr.region, Defs(arena.alloc(defs), loc_ret)))
} }
@ -881,14 +954,21 @@ pub fn desugar_expr<'a>(
} }
}; };
desugared_args.push(desugar_expr(arena, arg, src, line_info, module_path)); desugared_args.push(desugar_expr(
arena,
arg,
src,
line_info,
module_path,
problems,
));
} }
let desugared_args = desugared_args.into_bump_slice(); let desugared_args = desugared_args.into_bump_slice();
let mut apply: &Loc<Expr> = arena.alloc(Loc { let mut apply: &Loc<Expr> = arena.alloc(Loc {
value: Apply( value: Apply(
desugar_expr(arena, loc_fn, src, line_info, module_path), desugar_expr(arena, loc_fn, src, line_info, module_path, problems),
desugared_args, desugared_args,
*called_via, *called_via,
), ),
@ -900,7 +980,8 @@ pub fn desugar_expr<'a>(
Some(apply_exprs) => { Some(apply_exprs) => {
for expr in apply_exprs { for expr in apply_exprs {
let desugared_expr = desugar_expr(arena, expr, src, line_info, module_path); let desugared_expr =
desugar_expr(arena, expr, src, line_info, module_path, problems);
let args = std::slice::from_ref(arena.alloc(apply)); let args = std::slice::from_ref(arena.alloc(apply));
@ -921,17 +1002,31 @@ pub fn desugar_expr<'a>(
src, src,
line_info, line_info,
module_path, module_path,
problems,
)); ));
let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena); let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena);
for branch in branches.iter() { for branch in branches.iter() {
let desugared_expr = let desugared_expr =
desugar_expr(arena, &branch.value, src, line_info, module_path); desugar_expr(arena, &branch.value, src, line_info, module_path, problems);
let desugared_patterns = let desugared_patterns = desugar_loc_patterns(
desugar_loc_patterns(arena, branch.patterns, src, line_info, module_path); arena,
branch.patterns,
src,
line_info,
module_path,
problems,
);
let desugared_guard = if let Some(guard) = &branch.guard { let desugared_guard = if let Some(guard) = &branch.guard {
Some(*desugar_expr(arena, guard, src, line_info, module_path)) Some(*desugar_expr(
arena,
guard,
src,
line_info,
module_path,
problems,
))
} else { } else {
None None
}; };
@ -969,8 +1064,14 @@ pub fn desugar_expr<'a>(
}, },
}; };
let loc_fn_var = arena.alloc(Loc { region, value }); let loc_fn_var = arena.alloc(Loc { region, value });
let desugared_args = let desugared_args = arena.alloc([desugar_expr(
arena.alloc([desugar_expr(arena, loc_arg, src, line_info, module_path)]); arena,
loc_arg,
src,
line_info,
module_path,
problems,
)]);
arena.alloc(Loc { arena.alloc(Loc {
value: Apply(loc_fn_var, desugared_args, CalledVia::UnaryOp(op)), value: Apply(loc_fn_var, desugared_args, CalledVia::UnaryOp(op)),
@ -989,6 +1090,7 @@ pub fn desugar_expr<'a>(
src, src,
line_info, line_info,
module_path, module_path,
problems,
) )
} }
ParensAround(expr) => { ParensAround(expr) => {
@ -1001,6 +1103,7 @@ pub fn desugar_expr<'a>(
src, src,
line_info, line_info,
module_path, module_path,
problems,
); );
arena.alloc(Loc { arena.alloc(Loc {
@ -1016,14 +1119,15 @@ pub fn desugar_expr<'a>(
src, src,
line_info, line_info,
module_path, module_path,
problems,
)); ));
let mut desugared_if_thens = Vec::with_capacity_in(if_thens.len(), arena); let mut desugared_if_thens = Vec::with_capacity_in(if_thens.len(), arena);
for (condition, then_branch) in if_thens.iter() { for (condition, then_branch) in if_thens.iter() {
desugared_if_thens.push(( desugared_if_thens.push((
*desugar_expr(arena, condition, src, line_info, module_path), *desugar_expr(arena, condition, src, line_info, module_path, problems),
*desugar_expr(arena, then_branch, src, line_info, module_path), *desugar_expr(arena, then_branch, src, line_info, module_path, problems),
)); ));
} }
@ -1033,14 +1137,21 @@ pub fn desugar_expr<'a>(
}) })
} }
Expect(condition, continuation) => { Expect(condition, continuation) => {
let desugared_condition = let desugared_condition = &*arena.alloc(desugar_expr(
&*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); arena,
condition,
src,
line_info,
module_path,
problems,
));
let desugared_continuation = &*arena.alloc(desugar_expr( let desugared_continuation = &*arena.alloc(desugar_expr(
arena, arena,
continuation, continuation,
src, src,
line_info, line_info,
module_path, module_path,
problems,
)); ));
arena.alloc(Loc { arena.alloc(Loc {
value: Expect(desugared_condition, desugared_continuation), value: Expect(desugared_condition, desugared_continuation),
@ -1056,6 +1167,7 @@ pub fn desugar_expr<'a>(
src, src,
line_info, line_info,
module_path, module_path,
problems,
)); ));
let region = condition.region; let region = condition.region;
@ -1068,8 +1180,14 @@ pub fn desugar_expr<'a>(
value: inspect_fn, value: inspect_fn,
region, region,
}); });
let desugared_inspect_args = let desugared_inspect_args = arena.alloc([desugar_expr(
arena.alloc([desugar_expr(arena, condition, src, line_info, module_path)]); arena,
condition,
src,
line_info,
module_path,
problems,
)]);
let dbg_str = arena.alloc(Loc { let dbg_str = arena.alloc(Loc {
value: Apply(loc_inspect_fn_var, desugared_inspect_args, CalledVia::Space), value: Apply(loc_inspect_fn_var, desugared_inspect_args, CalledVia::Space),
@ -1114,6 +1232,7 @@ fn desugar_str_segments<'a>(
src: &'a str, src: &'a str,
line_info: &mut Option<LineInfo>, line_info: &mut Option<LineInfo>,
module_path: &str, module_path: &str,
problems: &mut std::vec::Vec<Problem>,
) -> &'a [StrSegment<'a>] { ) -> &'a [StrSegment<'a>] {
Vec::from_iter_in( Vec::from_iter_in(
segments.iter().map(|segment| match segment { segments.iter().map(|segment| match segment {
@ -1130,6 +1249,7 @@ fn desugar_str_segments<'a>(
src, src,
line_info, line_info,
module_path, module_path,
problems,
); );
StrSegment::DeprecatedInterpolated(Loc { StrSegment::DeprecatedInterpolated(Loc {
region: loc_desugared.region, region: loc_desugared.region,
@ -1146,6 +1266,7 @@ fn desugar_str_segments<'a>(
src, src,
line_info, line_info,
module_path, module_path,
problems,
); );
StrSegment::Interpolated(Loc { StrSegment::Interpolated(Loc {
region: loc_desugared.region, region: loc_desugared.region,
@ -1164,11 +1285,12 @@ fn desugar_field_collection<'a>(
src: &'a str, src: &'a str,
line_info: &mut Option<LineInfo>, line_info: &mut Option<LineInfo>,
module_path: &str, module_path: &str,
problems: &mut std::vec::Vec<Problem>,
) -> Collection<'a, Loc<AssignedField<'a, Expr<'a>>>> { ) -> Collection<'a, Loc<AssignedField<'a, Expr<'a>>>> {
let mut allocated = Vec::with_capacity_in(fields.len(), arena); let mut allocated = Vec::with_capacity_in(fields.len(), arena);
for field in fields.iter() { for field in fields.iter() {
let value = desugar_field(arena, &field.value, src, line_info, module_path); let value = desugar_field(arena, &field.value, src, line_info, module_path, problems);
allocated.push(Loc::at(field.region, value)); allocated.push(Loc::at(field.region, value));
} }
@ -1182,6 +1304,7 @@ fn desugar_field<'a>(
src: &'a str, src: &'a str,
line_info: &mut Option<LineInfo>, line_info: &mut Option<LineInfo>,
module_path: &str, module_path: &str,
problems: &mut std::vec::Vec<Problem>,
) -> AssignedField<'a, Expr<'a>> { ) -> AssignedField<'a, Expr<'a>> {
use roc_parse::ast::AssignedField::*; use roc_parse::ast::AssignedField::*;
@ -1192,7 +1315,7 @@ fn desugar_field<'a>(
region: loc_str.region, region: loc_str.region,
}, },
spaces, spaces,
desugar_expr(arena, loc_expr, src, line_info, module_path), desugar_expr(arena, loc_expr, src, line_info, module_path, problems),
), ),
OptionalValue(loc_str, spaces, loc_expr) => OptionalValue( OptionalValue(loc_str, spaces, loc_expr) => OptionalValue(
Loc { Loc {
@ -1200,7 +1323,7 @@ fn desugar_field<'a>(
region: loc_str.region, region: loc_str.region,
}, },
spaces, spaces,
desugar_expr(arena, loc_expr, src, line_info, module_path), desugar_expr(arena, loc_expr, src, line_info, module_path, problems),
), ),
IgnoredValue(loc_str, spaces, loc_expr) => IgnoredValue( IgnoredValue(loc_str, spaces, loc_expr) => IgnoredValue(
Loc { Loc {
@ -1208,7 +1331,7 @@ fn desugar_field<'a>(
region: loc_str.region, region: loc_str.region,
}, },
spaces, spaces,
desugar_expr(arena, loc_expr, src, line_info, module_path), desugar_expr(arena, loc_expr, src, line_info, module_path, problems),
), ),
LabelOnly(loc_str) => { LabelOnly(loc_str) => {
// Desugar { x } into { x: x } // Desugar { x } into { x: x }
@ -1226,11 +1349,22 @@ fn desugar_field<'a>(
region: loc_str.region, region: loc_str.region,
}, },
&[], &[],
desugar_expr(arena, arena.alloc(loc_expr), src, line_info, module_path), desugar_expr(
arena,
arena.alloc(loc_expr),
src,
line_info,
module_path,
problems,
),
) )
} }
SpaceBefore(field, _spaces) => desugar_field(arena, field, src, line_info, module_path), SpaceBefore(field, _spaces) => {
SpaceAfter(field, _spaces) => desugar_field(arena, field, src, line_info, module_path), desugar_field(arena, field, src, line_info, module_path, problems)
}
SpaceAfter(field, _spaces) => {
desugar_field(arena, field, src, line_info, module_path, problems)
}
Malformed(string) => Malformed(string), Malformed(string) => Malformed(string),
} }
@ -1242,11 +1376,19 @@ fn desugar_loc_patterns<'a>(
src: &'a str, src: &'a str,
line_info: &mut Option<LineInfo>, line_info: &mut Option<LineInfo>,
module_path: &str, module_path: &str,
problems: &mut std::vec::Vec<Problem>,
) -> &'a [Loc<Pattern<'a>>] { ) -> &'a [Loc<Pattern<'a>>] {
Vec::from_iter_in( Vec::from_iter_in(
loc_patterns.iter().map(|loc_pattern| Loc { loc_patterns.iter().map(|loc_pattern| Loc {
region: loc_pattern.region, region: loc_pattern.region,
value: desugar_pattern(arena, loc_pattern.value, src, line_info, module_path), value: desugar_pattern(
arena,
loc_pattern.value,
src,
line_info,
module_path,
problems,
),
}), }),
arena, arena,
) )
@ -1259,10 +1401,18 @@ fn desugar_loc_pattern<'a>(
src: &'a str, src: &'a str,
line_info: &mut Option<LineInfo>, line_info: &mut Option<LineInfo>,
module_path: &str, module_path: &str,
problems: &mut std::vec::Vec<Problem>,
) -> &'a Loc<Pattern<'a>> { ) -> &'a Loc<Pattern<'a>> {
arena.alloc(Loc { arena.alloc(Loc {
region: loc_pattern.region, region: loc_pattern.region,
value: desugar_pattern(arena, loc_pattern.value, src, line_info, module_path), value: desugar_pattern(
arena,
loc_pattern.value,
src,
line_info,
module_path,
problems,
),
}) })
} }
@ -1272,6 +1422,7 @@ fn desugar_pattern<'a>(
src: &'a str, src: &'a str,
line_info: &mut Option<LineInfo>, line_info: &mut Option<LineInfo>,
module_path: &str, module_path: &str,
problems: &mut std::vec::Vec<Problem>,
) -> Pattern<'a> { ) -> Pattern<'a> {
use roc_parse::ast::Pattern::*; use roc_parse::ast::Pattern::*;
@ -1295,7 +1446,14 @@ fn desugar_pattern<'a>(
let desugared_arg_patterns = Vec::from_iter_in( let desugared_arg_patterns = Vec::from_iter_in(
arg_patterns.iter().map(|arg_pattern| Loc { arg_patterns.iter().map(|arg_pattern| Loc {
region: arg_pattern.region, region: arg_pattern.region,
value: desugar_pattern(arena, arg_pattern.value, src, line_info, module_path), value: desugar_pattern(
arena,
arg_pattern.value,
src,
line_info,
module_path,
problems,
),
}), }),
arena, arena,
) )
@ -1306,8 +1464,14 @@ fn desugar_pattern<'a>(
RecordDestructure(field_patterns) => { RecordDestructure(field_patterns) => {
let mut allocated = Vec::with_capacity_in(field_patterns.len(), arena); let mut allocated = Vec::with_capacity_in(field_patterns.len(), arena);
for field_pattern in field_patterns.iter() { for field_pattern in field_patterns.iter() {
let value = let value = desugar_pattern(
desugar_pattern(arena, field_pattern.value, src, line_info, module_path); arena,
field_pattern.value,
src,
line_info,
module_path,
problems,
);
allocated.push(Loc { allocated.push(Loc {
value, value,
region: field_pattern.region, region: field_pattern.region,
@ -1319,15 +1483,17 @@ fn desugar_pattern<'a>(
} }
RequiredField(name, field_pattern) => RequiredField( RequiredField(name, field_pattern) => RequiredField(
name, name,
desugar_loc_pattern(arena, field_pattern, src, line_info, module_path), desugar_loc_pattern(arena, field_pattern, src, line_info, module_path, problems),
),
OptionalField(name, expr) => OptionalField(
name,
desugar_expr(arena, expr, src, line_info, module_path, problems),
), ),
OptionalField(name, expr) => {
OptionalField(name, desugar_expr(arena, expr, src, line_info, module_path))
}
Tuple(patterns) => { Tuple(patterns) => {
let mut allocated = Vec::with_capacity_in(patterns.len(), arena); let mut allocated = Vec::with_capacity_in(patterns.len(), arena);
for pattern in patterns.iter() { for pattern in patterns.iter() {
let value = desugar_pattern(arena, pattern.value, src, line_info, module_path); let value =
desugar_pattern(arena, pattern.value, src, line_info, module_path, problems);
allocated.push(Loc { allocated.push(Loc {
value, value,
region: pattern.region, region: pattern.region,
@ -1340,7 +1506,8 @@ fn desugar_pattern<'a>(
List(patterns) => { List(patterns) => {
let mut allocated = Vec::with_capacity_in(patterns.len(), arena); let mut allocated = Vec::with_capacity_in(patterns.len(), arena);
for pattern in patterns.iter() { for pattern in patterns.iter() {
let value = desugar_pattern(arena, pattern.value, src, line_info, module_path); let value =
desugar_pattern(arena, pattern.value, src, line_info, module_path, problems);
allocated.push(Loc { allocated.push(Loc {
value, value,
region: pattern.region, region: pattern.region,
@ -1351,14 +1518,14 @@ fn desugar_pattern<'a>(
List(patterns) List(patterns)
} }
As(sub_pattern, symbol) => As( As(sub_pattern, symbol) => As(
desugar_loc_pattern(arena, sub_pattern, src, line_info, module_path), desugar_loc_pattern(arena, sub_pattern, src, line_info, module_path, problems),
symbol, symbol,
), ),
SpaceBefore(sub_pattern, _spaces) => { SpaceBefore(sub_pattern, _spaces) => {
desugar_pattern(arena, *sub_pattern, src, line_info, module_path) desugar_pattern(arena, *sub_pattern, src, line_info, module_path, problems)
} }
SpaceAfter(sub_pattern, _spaces) => { SpaceAfter(sub_pattern, _spaces) => {
desugar_pattern(arena, *sub_pattern, src, line_info, module_path) desugar_pattern(arena, *sub_pattern, src, line_info, module_path, problems)
} }
} }
} }
@ -1479,6 +1646,7 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
} }
} }
#[allow(clippy::too_many_arguments)]
fn desugar_bin_ops<'a>( fn desugar_bin_ops<'a>(
arena: &'a Bump, arena: &'a Bump,
whole_region: Region, whole_region: Region,
@ -1487,19 +1655,27 @@ fn desugar_bin_ops<'a>(
src: &'a str, src: &'a str,
line_info: &mut Option<LineInfo>, line_info: &mut Option<LineInfo>,
module_path: &str, module_path: &str,
problems: &mut std::vec::Vec<Problem>,
) -> &'a Loc<Expr<'a>> { ) -> &'a Loc<Expr<'a>> {
let mut arg_stack: Vec<&'a Loc<Expr>> = Vec::with_capacity_in(lefts.len() + 1, arena); let mut arg_stack: Vec<&'a Loc<Expr>> = Vec::with_capacity_in(lefts.len() + 1, arena);
let mut op_stack: Vec<Loc<BinOp>> = Vec::with_capacity_in(lefts.len(), arena); let mut op_stack: Vec<Loc<BinOp>> = Vec::with_capacity_in(lefts.len(), arena);
for (loc_expr, loc_op) in lefts { for (loc_expr, loc_op) in lefts {
arg_stack.push(desugar_expr(arena, loc_expr, src, line_info, module_path)); arg_stack.push(desugar_expr(
arena,
loc_expr,
src,
line_info,
module_path,
problems,
));
match run_binop_step(arena, whole_region, &mut arg_stack, &mut op_stack, *loc_op) { match run_binop_step(arena, whole_region, &mut arg_stack, &mut op_stack, *loc_op) {
Err(problem) => return problem, Err(problem) => return problem,
Ok(()) => continue, Ok(()) => continue,
} }
} }
let mut expr = desugar_expr(arena, right, src, line_info, module_path); let mut expr = desugar_expr(arena, right, src, line_info, module_path, problems);
for (left, loc_op) in arg_stack.into_iter().zip(op_stack.into_iter()).rev() { for (left, loc_op) in arg_stack.into_iter().zip(op_stack.into_iter()).rev() {
expr = arena.alloc(new_op_call_expr(arena, left, loc_op, expr)); expr = arena.alloc(new_op_call_expr(arena, left, loc_op, expr));

View file

@ -337,7 +337,15 @@ pub fn canonicalize_module_defs<'a>(
// operators, and then again on *their* nested operators, ultimately applying the // operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily. // rules multiple times unnecessarily.
crate::desugar::desugar_defs_node_values(arena, loc_defs, src, &mut None, module_path, true); crate::desugar::desugar_defs_node_values(
arena,
loc_defs,
src,
&mut None,
module_path,
true,
&mut env.problems,
);
let mut rigid_variables = RigidVariables::default(); let mut rigid_variables = RigidVariables::default();

View file

@ -59,6 +59,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
expr_str, expr_str,
&mut None, &mut None,
arena.alloc("TestPath"), arena.alloc("TestPath"),
&mut Default::default(),
); );
let mut scope = Scope::new( let mut scope = Scope::new(

View file

@ -12,7 +12,15 @@ mod suffixed_tests {
($src:expr) => {{ ($src:expr) => {{
let arena = &Bump::new(); let arena = &Bump::new();
let mut defs = parse_defs_with(arena, indoc!($src)).unwrap(); let mut defs = parse_defs_with(arena, indoc!($src)).unwrap();
desugar_defs_node_values(arena, &mut defs, $src, &mut None, "test.roc", true); desugar_defs_node_values(
arena,
&mut defs,
$src,
&mut None,
"test.roc",
true,
&mut Default::default(),
);
let snapshot = format!("{:#?}", &defs); let snapshot = format!("{:#?}", &defs);
println!("{}", snapshot); println!("{}", snapshot);

View file

@ -174,6 +174,7 @@ pub fn can_expr_with<'a>(
expr_str, expr_str,
&mut None, &mut None,
arena.alloc("TestPath"), arena.alloc("TestPath"),
&mut Default::default(),
); );
let mut scope = Scope::new( let mut scope = Scope::new(

View file

@ -4816,7 +4816,7 @@ mod test_reporting {
expression_indentation_end, expression_indentation_end,
indoc!( indoc!(
r" r"
f <- Foo.foo f = Foo.foo
" "
), ),
@r#" @r#"
@ -4827,8 +4827,8 @@ mod test_reporting {
1 app "test" provides [main] to "./platform" 1 app "test" provides [main] to "./platform"
2 2
3 main = 3 main =
4 f <- Foo.foo 4 f = Foo.foo
^ ^
Looks like the indentation ends prematurely here. Did you mean to have Looks like the indentation ends prematurely here. Did you mean to have
another expression after this line? another expression after this line?
@ -6617,34 +6617,6 @@ All branches in an `if` must have the same type!
" "
); );
test_report!(
backpassing_type_error,
indoc!(
r#"
x <- List.map ["a", "b"]
x + 1
"#
),
@r#"
TYPE MISMATCH in /code/proj/Main.roc
This 2nd argument to `map` has an unexpected type:
4> x <- List.map ["a", "b"]
5>
6> x + 1
The argument is an anonymous function of type:
Num * -> Num *
But `map` needs its 2nd argument to be:
Str -> Num *
"#
);
test_report!( test_report!(
expect_expr_type_error, expect_expr_type_error,
indoc!( indoc!(
@ -10198,9 +10170,9 @@ All branches in an `if` must have the same type!
withOpen : (Handle -> Result {} *) -> Result {} * withOpen : (Handle -> Result {} *) -> Result {} *
withOpen = \callback -> withOpen = \callback ->
handle <- await (open {}) await (open {}) \handle ->
{} <- await (callback handle) await (callback handle) \_ ->
close handle close handle
withOpen withOpen
" "
@ -10212,9 +10184,9 @@ All branches in an `if` must have the same type!
10 withOpen : (Handle -> Result {} *) -> Result {} * 10 withOpen : (Handle -> Result {} *) -> Result {} *
11 withOpen = \callback -> 11 withOpen = \callback ->
12> handle <- await (open {}) 12> await (open {}) \handle ->
13> {} <- await (callback handle) 13> await (callback handle) \_ ->
14> close handle 14> close handle
The type annotation on `withOpen` says this `await` call should have the The type annotation on `withOpen` says this `await` call should have the
type: type:
@ -10227,6 +10199,7 @@ All branches in an `if` must have the same type!
Tip: Any connection between types must use a named type variable, not Tip: Any connection between types must use a named type variable, not
a `*`! Maybe the annotation on `withOpen` should have a named type a `*`! Maybe the annotation on `withOpen` should have a named type
variable in place of the `*`? variable in place of the `*`?
" "
); );
@ -10830,7 +10803,7 @@ All branches in an `if` must have the same type!
7 a: <- "a", 7 a: <- "a",
^^^ ^^^
Tip: Remove `<-` to assign the field directly. Tip: Remove <- to assign the field directly.
"# "#
); );
@ -10962,7 +10935,7 @@ All branches in an `if` must have the same type!
6 { xyz <- 6 { xyz <-
^^^ ^^^
Note: Record builders need a mapper function before the `<-` to combine Note: Record builders need a mapper function before the <- to combine
fields together with. fields together with.
"# "#
); );
@ -11844,6 +11817,32 @@ All branches in an `if` must have the same type!
@r" @r"
" "
); );
test_report!(
deprecated_backpassing,
indoc!(
r#"
foo = \bar ->
baz <- Result.try bar
Ok (baz * 3)
foo (Ok 123)
"#
),
@r###"
BACKPASSING DEPRECATED in /code/proj/Main.roc
Backpassing (<-) like this will soon be deprecated:
5 baz <- Result.try bar
^^^^^^^^^^^^^^^^^^^^^
You should use a ! for awaiting tasks or a ? for trying results, and
functions everywhere else.
"###
);
test_report!( test_report!(
unknown_shorthand_no_deps, unknown_shorthand_no_deps,
indoc!( indoc!(
@ -13881,7 +13880,7 @@ All branches in an `if` must have the same type!
"# "#
), ),
@r#" @r#"
DEFINITIONs ONLY USED IN RECURSION in /code/proj/Main.roc DEFINITIONS ONLY USED IN RECURSION in /code/proj/Main.roc
These 2 definitions are only used in mutual recursion with themselves: These 2 definitions are only used in mutual recursion with themselves:
@ -13890,6 +13889,7 @@ All branches in an `if` must have the same type!
If you don't intend to use or export any of them, they should all be If you don't intend to use or export any of them, they should all be
removed! removed!
"# "#
); );
@ -13953,7 +13953,7 @@ All branches in an `if` must have the same type!
"# "#
), ),
@r#" @r#"
DEFINITIONs ONLY USED IN RECURSION in /code/proj/Main.roc DEFINITIONS ONLY USED IN RECURSION in /code/proj/Main.roc
These 2 definitions are only used in mutual recursion with themselves: These 2 definitions are only used in mutual recursion with themselves:
@ -13962,6 +13962,7 @@ All branches in an `if` must have the same type!
If you don't intend to use or export any of them, they should all be If you don't intend to use or export any of them, they should all be
removed! removed!
"# "#
); );

View file

@ -118,6 +118,23 @@ impl std::fmt::Display for UnaryOp {
} }
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Suffix {
/// (!), e.g. (Stdin.line!)
Bang,
/// (?), e.g. (parseData? data)
Question,
}
impl std::fmt::Display for Suffix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Suffix::Bang => write!(f, "!"),
Suffix::Question => write!(f, "?"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinOp { pub enum BinOp {
// highest precedence // highest precedence

View file

@ -54,6 +54,7 @@ pub enum Problem {
new_symbol: Symbol, new_symbol: Symbol,
existing_symbol_region: Region, existing_symbol_region: Region,
}, },
DeprecatedBackpassing(Region),
/// First symbol is the name of the closure with that argument /// First symbol is the name of the closure with that argument
/// Bool is whether the closure is anonymous /// Bool is whether the closure is anonymous
/// Second symbol is the name of the argument that is unused /// Second symbol is the name of the argument that is unused
@ -257,6 +258,7 @@ impl Problem {
Problem::ExplicitBuiltinImport(_, _) => Warning, Problem::ExplicitBuiltinImport(_, _) => Warning,
Problem::ExplicitBuiltinTypeImport(_, _) => Warning, Problem::ExplicitBuiltinTypeImport(_, _) => Warning,
Problem::ImportShadowsSymbol { .. } => RuntimeError, Problem::ImportShadowsSymbol { .. } => RuntimeError,
Problem::DeprecatedBackpassing(_) => Warning,
Problem::ExposedButNotDefined(_) => RuntimeError, Problem::ExposedButNotDefined(_) => RuntimeError,
Problem::UnknownGeneratesWith(_) => RuntimeError, Problem::UnknownGeneratesWith(_) => RuntimeError,
Problem::UnusedArgument(_, _, _, _) => Warning, Problem::UnusedArgument(_, _, _, _) => Warning,
@ -340,6 +342,7 @@ impl Problem {
| Problem::ExplicitBuiltinImport(_, region) | Problem::ExplicitBuiltinImport(_, region)
| Problem::ExplicitBuiltinTypeImport(_, region) | Problem::ExplicitBuiltinTypeImport(_, region)
| Problem::ImportShadowsSymbol { region, .. } | Problem::ImportShadowsSymbol { region, .. }
| Problem::DeprecatedBackpassing(region)
| Problem::UnknownGeneratesWith(Loc { region, .. }) | Problem::UnknownGeneratesWith(Loc { region, .. })
| Problem::UnusedArgument(_, _, _, region) | Problem::UnusedArgument(_, _, _, region)
| Problem::UnusedBranchDef(_, region) | Problem::UnusedBranchDef(_, region)

View file

@ -2479,39 +2479,6 @@ fn expanded_result() {
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn backpassing_result() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
a : Result I64 Str
a = Ok 1
f = \x -> Ok (x + 1)
g = \y -> Ok (y * 2)
main : I64
main =
helper =
x <- Result.try a
y <- Result.try (f x)
z <- Result.try (g y)
Ok z
helper
|> Result.withDefault 0
"#
),
4,
i64
);
}
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = "Shadowing { original_region: @55-56, shadow: @72-73 Ident")] #[should_panic(expected = "Shadowing { original_region: @55-56, shadow: @72-73 Ident")]

View file

@ -3389,8 +3389,8 @@ fn inspect_custom_type() {
myToInspector : HelloWorld -> Inspector f where f implements InspectFormatter myToInspector : HelloWorld -> Inspector f where f implements InspectFormatter
myToInspector = \@HellowWorld {} -> myToInspector = \@HellowWorld {} ->
fmt <- Inspect.custom Inspect.custom \fmt ->
Inspect.apply (Inspect.str "Hello, World!\n") fmt Inspect.apply (Inspect.str "Hello, World!\n") fmt
main = main =
Inspect.inspect (@HelloWorld {}) Inspect.inspect (@HelloWorld {})

View file

@ -1,4 +1,5 @@
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
use roc_module::called_via::Suffix;
use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::DERIVABLE_ABILITIES; use roc_module::symbol::DERIVABLE_ABILITIES;
use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::PrecedenceProblem::BothNonAssociative;
@ -246,6 +247,26 @@ pub fn can_problem<'b>(
title = DUPLICATE_NAME.to_string(); title = DUPLICATE_NAME.to_string();
} }
Problem::DeprecatedBackpassing(region) => {
doc = alloc.stack([
alloc.concat([
alloc.reflow("Backpassing ("),
alloc.backpassing_arrow(),
alloc.reflow(") like this will soon be deprecated:"),
]),
alloc.region(lines.convert_region(region), severity),
alloc.concat([
alloc.reflow("You should use a "),
alloc.suffix(Suffix::Bang),
alloc.reflow(" for awaiting tasks or a "),
alloc.suffix(Suffix::Question),
alloc.reflow(" for trying results, and functions everywhere else."),
]),
]);
title = "BACKPASSING DEPRECATED".to_string();
}
Problem::DefsOnlyUsedInRecursion(1, region) => { Problem::DefsOnlyUsedInRecursion(1, region) => {
doc = alloc.stack([ doc = alloc.stack([
alloc.reflow("This definition is only used in recursion with itself:"), alloc.reflow("This definition is only used in recursion with itself:"),
@ -270,7 +291,7 @@ pub fn can_problem<'b>(
), ),
]); ]);
title = "DEFINITIONs ONLY USED IN RECURSION".to_string(); title = "DEFINITIONS ONLY USED IN RECURSION".to_string();
} }
Problem::ExposedButNotDefined(symbol) => { Problem::ExposedButNotDefined(symbol) => {
doc = alloc.stack([ doc = alloc.stack([

View file

@ -1265,7 +1265,7 @@ fn to_expr_report<'b>(
alloc.concat([ alloc.concat([
alloc.tip(), alloc.tip(),
alloc.reflow("Remove "), alloc.reflow("Remove "),
alloc.keyword("<-"), alloc.backpassing_arrow(),
alloc.reflow(" to assign the field directly.") alloc.reflow(" to assign the field directly.")
]) ])
} }
@ -1273,7 +1273,7 @@ fn to_expr_report<'b>(
alloc.concat([ alloc.concat([
alloc.note(""), alloc.note(""),
alloc.reflow("Record builders need a mapper function before the "), alloc.reflow("Record builders need a mapper function before the "),
alloc.keyword("<-"), alloc.backpassing_arrow(),
alloc.reflow(" to combine fields together with.") alloc.reflow(" to combine fields together with.")
]) ])
} }

View file

@ -509,6 +509,10 @@ impl<'a> RocDocAllocator<'a> {
self.text(name).annotate(Annotation::Shorthand) self.text(name).annotate(Annotation::Shorthand)
} }
pub fn backpassing_arrow(&'a self) -> DocBuilder<'a, Self, Annotation> {
self.text("<-").annotate(Annotation::BinOp)
}
pub fn binop( pub fn binop(
&'a self, &'a self,
content: roc_module::called_via::BinOp, content: roc_module::called_via::BinOp,
@ -523,6 +527,13 @@ impl<'a> RocDocAllocator<'a> {
self.text(content.to_string()).annotate(Annotation::UnaryOp) self.text(content.to_string()).annotate(Annotation::UnaryOp)
} }
pub fn suffix(
&'a self,
content: roc_module::called_via::Suffix,
) -> DocBuilder<'a, Self, Annotation> {
self.text(content.to_string()).annotate(Annotation::UnaryOp)
}
/// Turns off backticks/colors in a block /// Turns off backticks/colors in a block
pub fn type_block( pub fn type_block(
&'a self, &'a self,

View file

@ -55,27 +55,28 @@ addFriend = \@Community { people, friends }, from, to ->
walkFriendNames : Community, state, (state, Str, Set Str -> state) -> state walkFriendNames : Community, state, (state, Str, Set Str -> state) -> state
walkFriendNames = \@Community { people, friends }, s0, nextFn -> walkFriendNames = \@Community { people, friends }, s0, nextFn ->
(out, _) = (out, _) =
(s1, id), friendSet <- List.walk friends (s0, 0) List.walk friends (s0, 0) \(s1, id), friendSet ->
(@Person person) = (@Person person) =
when List.get people id is when List.get people id is
Ok v -> v
Err _ -> crash "Unknown Person"
personName =
person.firstName
|> Str.concat " "
|> Str.concat person.lastName
friendNames =
friendsSet, friendId <- Set.walk friendSet (Set.empty {})
(@Person friend) =
when List.get people friendId is
Ok v -> v Ok v -> v
Err _ -> crash "Unknown Person" Err _ -> crash "Unknown Person"
friendName = personName =
friend.firstName person.firstName
|> Str.concat " " |> Str.concat " "
|> Str.concat friend.lastName |> Str.concat person.lastName
Set.insert friendsSet friendName
friendNames =
Set.walk friendSet (Set.empty {}) \friendsSet, friendId ->
(@Person friend) =
when List.get people friendId is
Ok v -> v
Err _ -> crash "Unknown Person"
friendName =
friend.firstName
|> Str.concat " "
|> Str.concat friend.lastName
Set.insert friendsSet friendName
(nextFn s1 personName friendNames, id + 1)
(nextFn s1 personName friendNames, id + 1)
out out

View file

@ -61,18 +61,18 @@ toStr = \{ scopes, stack, state, vars } ->
with : Str, (Context -> Task {} a) -> Task {} a with : Str, (Context -> Task {} a) -> Task {} a
with = \path, callback -> with = \path, callback ->
handle <- File.withOpen path File.withOpen path \handle ->
# I cant define scope here and put it in the list in callback. It breaks alias anaysis. # I cant define scope here and put it in the list in callback. It breaks alias anaysis.
# Instead I have to inline this. # Instead I have to inline this.
# root_scope = { data: Some handle, index: 0, buf: [], whileInfo: None } # root_scope = { data: Some handle, index: 0, buf: [], whileInfo: None }
callback { scopes: [{ data: Some handle, index: 0, buf: [], whileInfo: None }], state: Executing, stack: [], vars: List.repeat (Number 0) Variable.totalCount } callback { scopes: [{ data: Some handle, index: 0, buf: [], whileInfo: None }], state: Executing, stack: [], vars: List.repeat (Number 0) Variable.totalCount }
# I am pretty sure there is a syntax to destructure and keep a reference to the whole, but Im not sure what it is. # I am pretty sure there is a syntax to destructure and keep a reference to the whole, but Im not sure what it is.
getChar : Context -> Task [T U8 Context] [EndOfData, NoScope] getChar : Context -> Task [T U8 Context] [EndOfData, NoScope]
getChar = \ctx -> getChar = \ctx ->
when List.last ctx.scopes is when List.last ctx.scopes is
Ok scope -> Ok scope ->
(T val newScope) <- Task.await (getCharScope scope) (T val newScope) = getCharScope! scope
Task.succeed (T val { ctx & scopes: List.set ctx.scopes (List.len ctx.scopes - 1) newScope }) Task.succeed (T val { ctx & scopes: List.set ctx.scopes (List.len ctx.scopes - 1) newScope })
Err ListWasEmpty -> Err ListWasEmpty ->
@ -87,7 +87,7 @@ getCharScope = \scope ->
Err OutOfBounds -> Err OutOfBounds ->
when scope.data is when scope.data is
Some h -> Some h ->
bytes <- Task.await (File.chunk h) bytes = File.chunk! h
when List.first bytes is when List.first bytes is
Ok val -> Ok val ->
# This starts at 1 because the first character is already being returned. # This starts at 1 because the first character is already being returned.

View file

@ -28,47 +28,47 @@ main = \filename ->
interpretFile : Str -> Task {} [StringErr Str] interpretFile : Str -> Task {} [StringErr Str]
interpretFile = \filename -> interpretFile = \filename ->
ctx <- Context.with filename Context.with filename \ctx ->
result <- Task.attempt (interpretCtx ctx) result = interpretCtx ctx |> Task.result!
when result is when result is
Ok _ -> Ok _ ->
Task.succeed {} Task.succeed {}
Err BadUtf8 -> Err BadUtf8 ->
Task.fail (StringErr "Failed to convert string from Utf8 bytes") Task.fail (StringErr "Failed to convert string from Utf8 bytes")
Err DivByZero -> Err DivByZero ->
Task.fail (StringErr "Division by zero") Task.fail (StringErr "Division by zero")
Err EmptyStack -> Err EmptyStack ->
Task.fail (StringErr "Tried to pop a value off of the stack when it was empty") Task.fail (StringErr "Tried to pop a value off of the stack when it was empty")
Err InvalidBooleanValue -> Err InvalidBooleanValue ->
Task.fail (StringErr "Ran into an invalid boolean that was neither false (0) or true (-1)") Task.fail (StringErr "Ran into an invalid boolean that was neither false (0) or true (-1)")
Err (InvalidChar char) -> Err (InvalidChar char) ->
Task.fail (StringErr "Ran into an invalid character with ascii code: $(char)") Task.fail (StringErr "Ran into an invalid character with ascii code: $(char)")
Err MaxInputNumber -> Err MaxInputNumber ->
Task.fail (StringErr "Like the original false compiler, the max input number is 320,000") Task.fail (StringErr "Like the original false compiler, the max input number is 320,000")
Err NoLambdaOnStack -> Err NoLambdaOnStack ->
Task.fail (StringErr "Tried to run a lambda when no lambda was on the stack") Task.fail (StringErr "Tried to run a lambda when no lambda was on the stack")
Err NoNumberOnStack -> Err NoNumberOnStack ->
Task.fail (StringErr "Tried to run a number when no number was on the stack") Task.fail (StringErr "Tried to run a number when no number was on the stack")
Err NoVariableOnStack -> Err NoVariableOnStack ->
Task.fail (StringErr "Tried to load a variable when no variable was on the stack") Task.fail (StringErr "Tried to load a variable when no variable was on the stack")
Err NoScope -> Err NoScope ->
Task.fail (StringErr "Tried to run code when not in any scope") Task.fail (StringErr "Tried to run code when not in any scope")
Err OutOfBounds -> Err OutOfBounds ->
Task.fail (StringErr "Tried to load from an offset that was outside of the stack") Task.fail (StringErr "Tried to load from an offset that was outside of the stack")
Err UnexpectedEndOfData -> Err UnexpectedEndOfData ->
Task.fail (StringErr "Hit end of data while still parsing something") Task.fail (StringErr "Hit end of data while still parsing something")
isDigit : U8 -> Bool isDigit : U8 -> Bool
isDigit = \char -> isDigit = \char ->
@ -129,11 +129,11 @@ interpretCtxLoop = \ctx ->
Task.fail NoScope Task.fail NoScope
Executing -> Executing ->
# {} <- Task.await (Stdout.line (Context.toStr ctx)) # Stdout.line! (Context.toStr ctx)
result <- Task.attempt (Context.getChar ctx) result = Context.getChar ctx |> Task.result!
when result is when result is
Ok (T val newCtx) -> Ok (T val newCtx) ->
execCtx <- Task.await (stepExecCtx newCtx val) execCtx = stepExecCtx! newCtx val
Task.succeed (Step execCtx) Task.succeed (Step execCtx)
Err NoScope -> Err NoScope ->
@ -151,7 +151,7 @@ interpretCtxLoop = \ctx ->
Task.succeed (Step dropCtx) Task.succeed (Step dropCtx)
InComment -> InComment ->
result <- Task.attempt (Context.getChar ctx) result = Context.getChar ctx |> Task.result!
when result is when result is
Ok (T val newCtx) -> Ok (T val newCtx) ->
if val == 0x7D then if val == 0x7D then
@ -167,7 +167,7 @@ interpretCtxLoop = \ctx ->
Task.fail UnexpectedEndOfData Task.fail UnexpectedEndOfData
InNumber accum -> InNumber accum ->
result <- Task.attempt (Context.getChar ctx) result = Context.getChar ctx |> Task.result!
when result is when result is
Ok (T val newCtx) -> Ok (T val newCtx) ->
if isDigit val then if isDigit val then
@ -182,7 +182,7 @@ interpretCtxLoop = \ctx ->
# outside of number now, this needs to be executed. # outside of number now, this needs to be executed.
pushCtx = Context.pushStack newCtx (Number accum) pushCtx = Context.pushStack newCtx (Number accum)
execCtx <- Task.await (stepExecCtx { pushCtx & state: Executing } val) execCtx = stepExecCtx! { pushCtx & state: Executing } val
Task.succeed (Step execCtx) Task.succeed (Step execCtx)
Err NoScope -> Err NoScope ->
@ -192,14 +192,14 @@ interpretCtxLoop = \ctx ->
Task.fail UnexpectedEndOfData Task.fail UnexpectedEndOfData
InString bytes -> InString bytes ->
result <- Task.attempt (Context.getChar ctx) result = Context.getChar ctx |> Task.result!
when result is when result is
Ok (T val newCtx) -> Ok (T val newCtx) ->
if val == 0x22 then if val == 0x22 then
# `"` end of string # `"` end of string
when Str.fromUtf8 bytes is when Str.fromUtf8 bytes is
Ok str -> Ok str ->
{} <- Task.await (Stdout.raw str) Stdout.raw! str
Task.succeed (Step { newCtx & state: Executing }) Task.succeed (Step { newCtx & state: Executing })
Err _ -> Err _ ->
@ -214,7 +214,7 @@ interpretCtxLoop = \ctx ->
Task.fail UnexpectedEndOfData Task.fail UnexpectedEndOfData
InLambda depth bytes -> InLambda depth bytes ->
result <- Task.attempt (Context.getChar ctx) result = Context.getChar ctx |> Task.result!
when result is when result is
Ok (T val newCtx) -> Ok (T val newCtx) ->
if val == 0x5B then if val == 0x5B then
@ -238,7 +238,7 @@ interpretCtxLoop = \ctx ->
Task.fail UnexpectedEndOfData Task.fail UnexpectedEndOfData
InSpecialChar -> InSpecialChar ->
result <- Task.attempt (Context.getChar { ctx & state: Executing }) result = Context.getChar { ctx & state: Executing } |> Task.result!
when result is when result is
Ok (T 0xB8 newCtx) -> Ok (T 0xB8 newCtx) ->
result2 = result2 =
@ -273,7 +273,7 @@ interpretCtxLoop = \ctx ->
Task.fail UnexpectedEndOfData Task.fail UnexpectedEndOfData
LoadChar -> LoadChar ->
result <- Task.attempt (Context.getChar { ctx & state: Executing }) result = Context.getChar { ctx & state: Executing } |> Task.result!
when result is when result is
Ok (T x newCtx) -> Ok (T x newCtx) ->
Task.succeed (Step (Context.pushStack newCtx (Number (Num.intCast x)))) Task.succeed (Step (Context.pushStack newCtx (Number (Num.intCast x))))
@ -472,7 +472,7 @@ stepExecCtx = \ctx, char ->
Ok (T popCtx num) -> Ok (T popCtx num) ->
when Str.fromUtf8 [Num.intCast num] is when Str.fromUtf8 [Num.intCast num] is
Ok str -> Ok str ->
{} <- Task.await (Stdout.raw str) Stdout.raw! str
Task.succeed popCtx Task.succeed popCtx
Err _ -> Err _ ->
@ -485,7 +485,7 @@ stepExecCtx = \ctx, char ->
# `.` write int # `.` write int
when popNumber ctx is when popNumber ctx is
Ok (T popCtx num) -> Ok (T popCtx num) ->
{} <- Task.await (Stdout.raw (Num.toStr (Num.intCast num))) Stdout.raw! (Num.toStr (Num.intCast num))
Task.succeed popCtx Task.succeed popCtx
Err e -> Err e ->
@ -493,7 +493,7 @@ stepExecCtx = \ctx, char ->
0x5E -> 0x5E ->
# `^` read char as int # `^` read char as int
in <- Task.await Stdin.char in = Stdin.char!
if in == 255 then if in == 255 then
# max char sent on EOF. Change to -1 # max char sent on EOF. Change to -1
Task.succeed (Context.pushStack ctx (Number -1)) Task.succeed (Context.pushStack ctx (Number -1))

View file

@ -22,7 +22,8 @@ close = \@Handle handle -> Effect.after (Effect.closeFile handle) Task.succeed
withOpen : Str, (Handle -> Task {} a) -> Task {} a withOpen : Str, (Handle -> Task {} a) -> Task {} a
withOpen = \path, callback -> withOpen = \path, callback ->
handle <- Task.await (open path) handle = open! path
result <- Task.attempt (callback handle) result = callback handle |> Task.result!
{} <- Task.await (close handle) close! handle
Task.fromResult result Task.fromResult result

View file

@ -1,4 +1,15 @@
module [Task, succeed, fail, await, map, onFail, attempt, fromResult, loop] module [
Task,
succeed,
fail,
await,
map,
onFail,
attempt,
result,
fromResult,
loop,
]
import pf.Effect import pf.Effect
@ -12,7 +23,7 @@ loop = \state, step ->
\res -> \res ->
when res is when res is
Ok (Step newState) -> Step newState Ok (Step newState) -> Step newState
Ok (Done result) -> Done (Ok result) Ok (Done res2) -> Done (Ok res2)
Err e -> Done (Err e) Err e -> Done (Err e)
Effect.loop state looper Effect.loop state looper
@ -26,8 +37,8 @@ fail = \val ->
Effect.always (Err val) Effect.always (Err val)
fromResult : Result a e -> Task a e fromResult : Result a e -> Task a e
fromResult = \result -> fromResult = \res ->
when result is when res is
Ok a -> succeed a Ok a -> succeed a
Err e -> fail e Err e -> fail e
@ -35,8 +46,8 @@ attempt : Task a b, (Result a b -> Task c d) -> Task c d
attempt = \effect, transform -> attempt = \effect, transform ->
Effect.after Effect.after
effect effect
\result -> \res ->
when result is when res is
Ok ok -> transform (Ok ok) Ok ok -> transform (Ok ok)
Err err -> transform (Err err) Err err -> transform (Err err)
@ -44,8 +55,8 @@ await : Task a err, (a -> Task b err) -> Task b err
await = \effect, transform -> await = \effect, transform ->
Effect.after Effect.after
effect effect
\result -> \res ->
when result is when res is
Ok a -> transform a Ok a -> transform a
Err err -> Task.fail err Err err -> Task.fail err
@ -53,8 +64,8 @@ onFail : Task ok a, (a -> Task ok b) -> Task ok b
onFail = \effect, transform -> onFail = \effect, transform ->
Effect.after Effect.after
effect effect
\result -> \res ->
when result is when res is
Ok a -> Task.succeed a Ok a -> Task.succeed a
Err err -> transform err Err err -> transform err
@ -62,7 +73,13 @@ map : Task a err, (a -> b) -> Task b err
map = \effect, transform -> map = \effect, transform ->
Effect.after Effect.after
effect effect
\result -> \res ->
when result is when res is
Ok a -> Task.succeed (transform a) Ok a -> Task.succeed (transform a)
Err err -> Task.fail err Err err -> Task.fail err
result : Task ok err -> Task (Result ok err) *
result = \effect ->
Effect.after
effect
\res -> Task.succeed res

View file

@ -88,13 +88,12 @@ initClientApp = \json, app ->
initClientAppHelp json app initClientAppHelp json app
# Call out to JS to patch the DOM, attaching the event listeners # Call out to JS to patch the DOM, attaching the event listeners
_ <- applyPatches patches |> Effect.after Effect.after (applyPatches patches) \_ ->
Effect.always {
Effect.always { app,
app, state,
state, rendered,
rendered, }
}
# Testable helper function to initialise the app # Testable helper function to initialise the app
initClientAppHelp : List U8, App state initData -> { state, rendered : RenderedTree state, patches : List Patch } where initData implements Decoding initClientAppHelp : List U8, App state initData -> { state, rendered : RenderedTree state, patches : List Patch } where initData implements Decoding
@ -222,16 +221,16 @@ dispatchEvent = \platformState, eventData, handlerId ->
{ rendered: newRendered, patches } = { rendered: newRendered, patches } =
diff { rendered, patches: [] } newViewUnrendered diff { rendered, patches: [] } newViewUnrendered
_ <- applyPatches patches |> Effect.after Effect.after (applyPatches patches) \_ ->
Effect.always { Effect.always {
platformState: { platformState: {
app, app,
state: newState, state: newState,
rendered: newRendered, rendered: newRendered,
}, },
stopPropagation, stopPropagation,
preventDefault, preventDefault,
} }
None -> None ->
Effect.always { platformState, stopPropagation, preventDefault } Effect.always { platformState, stopPropagation, preventDefault }

View file

@ -540,32 +540,35 @@ expect
decodeTuple = \initialState, stepElem, finalizer -> Decode.custom \initialBytes, @Json {} -> decodeTuple = \initialState, stepElem, finalizer -> Decode.custom \initialBytes, @Json {} ->
# NB: the stepper function must be passed explicitly until #2894 is resolved. # NB: the stepper function must be passed explicitly until #2894 is resolved.
decodeElems = \stepper, state, index, bytes -> decodeElems = \stepper, state, index, bytes ->
{ val: newState, rest: beforeCommaOrBreak } <- tryDecode (
( when stepper state index is
when stepper state index is TooLong ->
TooLong -> bytes
{ rest: beforeCommaOrBreak } <- bytes |> anything |> tryDecode |> anything
{ result: Ok state, rest: beforeCommaOrBreak } |> tryDecode \{ rest: beforeCommaOrBreak } ->
{ result: Ok state, rest: beforeCommaOrBreak }
Next decoder -> Next decoder ->
Decode.decodeWith bytes decoder json Decode.decodeWith bytes decoder json
) )
|> tryDecode \{ val: newState, rest: beforeCommaOrBreak } ->
{ result: commaResult, rest: nextBytes } = comma beforeCommaOrBreak
{ result: commaResult, rest: nextBytes } = comma beforeCommaOrBreak when commaResult is
Ok {} -> decodeElems stepElem newState (index + 1) nextBytes
Err _ -> { result: Ok newState, rest: nextBytes }
when commaResult is initialBytes
Ok {} -> decodeElems stepElem newState (index + 1) nextBytes |> openBracket
Err _ -> { result: Ok newState, rest: nextBytes } |> tryDecode \{ rest: afterBracketBytes } ->
decodeElems stepElem initialState 0 afterBracketBytes
{ rest: afterBracketBytes } <- initialBytes |> openBracket |> tryDecode |> tryDecode \{ val: endStateResult, rest: beforeClosingBracketBytes } ->
beforeClosingBracketBytes
{ val: endStateResult, rest: beforeClosingBracketBytes } <- decodeElems stepElem initialState 0 afterBracketBytes |> tryDecode |> closingBracket
|> tryDecode \{ rest: afterTupleBytes } ->
{ rest: afterTupleBytes } <- beforeClosingBracketBytes |> closingBracket |> tryDecode when finalizer endStateResult is
Ok val -> { result: Ok val, rest: afterTupleBytes }
when finalizer endStateResult is Err e -> { result: Err e, rest: afterTupleBytes }
Ok val -> { result: Ok val, rest: afterTupleBytes }
Err e -> { result: Err e, rest: afterTupleBytes }
# Test decode of tuple # Test decode of tuple
expect expect
@ -1225,44 +1228,42 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
Ok objectName -> Ok objectName ->
# Decode the json value # Decode the json value
{ val: updatedRecord, rest: bytesAfterValue } <- (
( fieldName = objectName
fieldName = objectName
# Retrieve value decoder for the current field # Retrieve value decoder for the current field
when stepField recordState fieldName is when stepField recordState fieldName is
Skip -> Skip ->
# TODO This doesn't seem right, shouldn't we eat # TODO This doesn't seem right, shouldn't we eat
# the remaining json object value bytes if we are skipping this # the remaining json object value bytes if we are skipping this
# field? # field?
{ result: Ok recordState, rest: valueBytes } { result: Ok recordState, rest: valueBytes }
Keep valueDecoder -> Keep valueDecoder ->
# Decode the value using the decoder from the recordState # Decode the value using the decoder from the recordState
# Note we need to pass json config options recursively here # Note we need to pass json config options recursively here
Decode.decodeWith valueBytes valueDecoder (@Json {}) Decode.decodeWith valueBytes valueDecoder (@Json {})
) )
|> tryDecode |> tryDecode \{ val: updatedRecord, rest: bytesAfterValue } ->
# Check if another field or '}' for end of object
when List.walkUntil bytesAfterValue (AfterObjectValue 0) objectHelp is
ObjectFieldNameStart n ->
rest = List.dropFirst bytesAfterValue n
# Check if another field or '}' for end of object # Decode the next field and value
when List.walkUntil bytesAfterValue (AfterObjectValue 0) objectHelp is decodeFields updatedRecord rest
ObjectFieldNameStart n ->
rest = List.dropFirst bytesAfterValue n
# Decode the next field and value AfterClosingBrace n ->
decodeFields updatedRecord rest rest = List.dropFirst bytesAfterValue n
AfterClosingBrace n -> # Build final record from decoded fields and values
rest = List.dropFirst bytesAfterValue n when finalizer updatedRecord json is
Ok val -> { result: Ok val, rest }
Err e -> { result: Err e, rest }
# Build final record from decoded fields and values _ ->
when finalizer updatedRecord json is # Invalid object
Ok val -> { result: Ok val, rest } { result: Err TooShort, rest: bytesAfterValue }
Err e -> { result: Err e, rest }
_ ->
# Invalid object
{ result: Err TooShort, rest: bytesAfterValue }
countBytesBeforeFirstField = countBytesBeforeFirstField =
when List.walkUntil bytes (BeforeOpeningBrace 0) objectHelp is when List.walkUntil bytes (BeforeOpeningBrace 0) objectHelp is