From 50f6e1142373c8004234a2b99206ec4c4f956f55 Mon Sep 17 00:00:00 2001 From: Sam Mohr Date: Fri, 16 Aug 2024 22:36:04 -0700 Subject: [PATCH 1/3] Deprecate backpassing to prepare for eventual removal --- crates/cli/tests/benchmarks/Base64/Decode.roc | 49 ++- crates/cli/tests/benchmarks/cFold.roc | 9 +- crates/cli/tests/benchmarks/deriv.roc | 20 +- crates/cli/tests/benchmarks/nQueens.roc | 9 +- crates/cli/tests/benchmarks/platform/Task.roc | 35 +- crates/cli/tests/benchmarks/quicksortApp.roc | 10 +- crates/cli/tests/benchmarks/rBTreeCk.roc | 9 +- crates/cli/tests/benchmarks/rBTreeDel.roc | 11 +- crates/cli/tests/cli/countdown.roc | 12 +- crates/cli/tests/cli/echo.roc | 2 +- crates/cli/tests/cli/parse-args.roc | 22 +- crates/cli/tests/cli/parser-letter-counts.roc | 21 +- crates/compiler/builtins/roc/Dict.roc | 24 +- crates/compiler/builtins/roc/Inspect.roc | 244 +++++++------- crates/compiler/builtins/roc/Set.roc | 4 +- crates/compiler/can/src/desugar.rs | 312 ++++++++++++++---- crates/compiler/can/src/module.rs | 10 +- crates/compiler/can/tests/helpers/mod.rs | 1 + crates/compiler/can/tests/test_suffixed.rs | 10 +- crates/compiler/load/tests/helpers/mod.rs | 1 + crates/compiler/load/tests/test_reporting.rs | 83 ++--- crates/compiler/module/src/called_via.rs | 17 + crates/compiler/problem/src/can.rs | 3 + crates/compiler/test_mono/src/tests.rs | 4 +- crates/reporting/src/error/canonicalize.rs | 23 +- crates/reporting/src/error/type.rs | 4 +- crates/reporting/src/report.rs | 11 + examples/Community.roc | 39 +-- examples/cli/false-interpreter/Context.roc | 14 +- examples/cli/false-interpreter/False.roc | 86 ++--- .../cli/false-interpreter/platform/File.roc | 7 +- .../cli/false-interpreter/platform/Task.roc | 18 +- .../platform/Html/Internal/Client.roc | 33 +- examples/virtual-dom-wip/platform/Json.roc | 111 ++++--- 34 files changed, 778 insertions(+), 490 deletions(-) diff --git a/crates/cli/tests/benchmarks/Base64/Decode.roc b/crates/cli/tests/benchmarks/Base64/Decode.roc index ebc2068e41..4b32bff95a 100644 --- a/crates/cli/tests/benchmarks/Base64/Decode.roc +++ b/crates/cli/tests/benchmarks/Base64/Decode.roc @@ -1,4 +1,4 @@ -interface Base64.Decode exposes [fromBytes] imports [] +module [fromBytes] 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, string } -> 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 - 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 - - Loop { - remaining: remaining - 3, - string: Str.concat string (bitsToChars combined 0), - } + Loop { + remaining: remaining - 3, + string: Str.concat string (bitsToChars combined 0), + } else if remaining == 0 then Bytes.Decode.succeed (Done string) 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 = Num.intCast x - b : U32 - b = Num.intCast y - combined = Num.bitwiseOr (Num.shiftLeftBy a 16) (Num.shiftLeftBy b 8) + a : U32 + a = Num.intCast x + b : U32 + b = Num.intCast y + 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 # remaining = 1 - x <- Bytes.Decode.map Bytes.Decode.u8 + Bytes.Decode.map Bytes.Decode.u8 \x -> - a : U32 - a = Num.intCast x + a : U32 + 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 = \bits, missing -> diff --git a/crates/cli/tests/benchmarks/cFold.roc b/crates/cli/tests/benchmarks/cFold.roc index e4b1e2423d..d9c5791ece 100644 --- a/crates/cli/tests/benchmarks/cFold.roc +++ b/crates/cli/tests/benchmarks/cFold.roc @@ -1,12 +1,11 @@ -app "cfold" - packages { pf: "platform/main.roc" } - imports [pf.Task] - provides [main] to pf +app [main] { pf: platform "platform/main.roc" } + +import pf.Task # adapted from https://github.com/koka-lang/koka/blob/master/test/bench/haskell/cfold.hs main : Task.Task {} [] main = - inputResult <- Task.attempt Task.getInt + inputResult = Task.getInt |> Task.result! when inputResult is Ok n -> diff --git a/crates/cli/tests/benchmarks/deriv.roc b/crates/cli/tests/benchmarks/deriv.roc index 7e4d22a6c2..2add592670 100644 --- a/crates/cli/tests/benchmarks/deriv.roc +++ b/crates/cli/tests/benchmarks/deriv.roc @@ -1,14 +1,13 @@ -app "deriv" - packages { pf: "platform/main.roc" } - imports [pf.Task] - provides [main] to pf +app [main] { pf: platform "platform/main.roc" } + +import pf.Task # based on: https://github.com/koka-lang/koka/blob/master/test/bench/haskell/deriv.hs IO a : Task.Task a [] main : Task.Task {} [] main = - inputResult <- Task.attempt Task.getInt + inputResult = Task.getInt |> Task.result! when inputResult is Ok n -> @@ -25,11 +24,12 @@ main = Task.putLine "Error: Failed to get Integer from stdin." nestHelp : I64, (I64, Expr -> IO Expr), I64, Expr -> IO Expr -nestHelp = \s, f, m, x -> when m is - 0 -> Task.succeed x - _ -> - w <- Task.after (f (s - m) x) - nestHelp s f (m - 1) w +nestHelp = \s, f, m, x -> + when m is + 0 -> Task.succeed x + _ -> + Task.after (f (s - m) x) \w -> + nestHelp s f (m - 1) w nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr nest = \f, n, e -> nestHelp n f n e diff --git a/crates/cli/tests/benchmarks/nQueens.roc b/crates/cli/tests/benchmarks/nQueens.roc index 15593e3721..82e331ef47 100644 --- a/crates/cli/tests/benchmarks/nQueens.roc +++ b/crates/cli/tests/benchmarks/nQueens.roc @@ -1,11 +1,10 @@ -app "nqueens" - packages { pf: "platform/main.roc" } - imports [pf.Task] - provides [main] to pf +app [main] { pf: platform "platform/main.roc" } + +import pf.Task main : Task.Task {} [] main = - inputResult <- Task.attempt Task.getInt + inputResult = Task.getInt |> Task.result! when inputResult is Ok n -> diff --git a/crates/cli/tests/benchmarks/platform/Task.roc b/crates/cli/tests/benchmarks/platform/Task.roc index 16da2d1eb7..d948e7d798 100644 --- a/crates/cli/tests/benchmarks/platform/Task.roc +++ b/crates/cli/tests/benchmarks/platform/Task.roc @@ -1,6 +1,20 @@ -interface Task - exposes [Task, succeed, fail, after, map, putLine, putInt, getInt, forever, loop, attempt] - imports [pf.Effect] +module [ + Task, + await, + succeed, + fail, + after, + map, + result, + putLine, + putInt, + getInt, + forever, + loop, + attempt, +] + +import pf.Effect Task ok err : Effect.Effect (Result ok err) @@ -46,6 +60,15 @@ after = \effect, transform -> 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 + \result -> + when result is + Ok a -> transform a + Err err -> Task.fail err + attempt : Task a b, (Result a b -> Task c d) -> Task c d attempt = \task, transform -> Effect.after @@ -64,6 +87,12 @@ map = \effect, transform -> Ok a -> Ok (transform a) Err err -> Err err +result : Task ok err -> Task (Result ok err) * +result = \effect -> + Effect.after + effect + \result -> Task.succeed result + putLine : Str -> Task {} * putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) diff --git a/crates/cli/tests/benchmarks/quicksortApp.roc b/crates/cli/tests/benchmarks/quicksortApp.roc index 67766bc982..66b39ab2bf 100644 --- a/crates/cli/tests/benchmarks/quicksortApp.roc +++ b/crates/cli/tests/benchmarks/quicksortApp.roc @@ -1,11 +1,11 @@ -app "quicksortapp" - packages { pf: "platform/main.roc" } - imports [pf.Task, Quicksort] - provides [main] to pf +app [main] { pf: platform "platform/main.roc" } + +import pf.Task +import Quicksort main : Task.Task {} [] main = - inputResult <- Task.attempt Task.getInt + inputResult = Task.getInt |> Task.result! when inputResult is Ok n -> diff --git a/crates/cli/tests/benchmarks/rBTreeCk.roc b/crates/cli/tests/benchmarks/rBTreeCk.roc index 5c685b195e..0d7f1c5232 100644 --- a/crates/cli/tests/benchmarks/rBTreeCk.roc +++ b/crates/cli/tests/benchmarks/rBTreeCk.roc @@ -1,7 +1,6 @@ -app "rbtree-ck" - packages { pf: "platform/main.roc" } - imports [pf.Task] - provides [main] to pf +app [main] { pf: platform "platform/main.roc" } + +import pf.Task Color : [Red, Black] @@ -40,7 +39,7 @@ fold = \f, tree, b -> main : Task.Task {} [] main = - inputResult <- Task.attempt Task.getInt + inputResult = Task.getInt |> Task.result! when inputResult is Ok n -> diff --git a/crates/cli/tests/benchmarks/rBTreeDel.roc b/crates/cli/tests/benchmarks/rBTreeDel.roc index 5617d6eb3f..82b333c488 100644 --- a/crates/cli/tests/benchmarks/rBTreeDel.roc +++ b/crates/cli/tests/benchmarks/rBTreeDel.roc @@ -1,7 +1,6 @@ -app "rbtree-del" - packages { pf: "platform/main.roc" } - imports [pf.Task] - provides [main] to pf +app [main] { pf: platform "platform/main.roc" } + +import pf.Task Color : [Red, Black] @@ -13,7 +12,7 @@ ConsList a : [Nil, Cons a (ConsList a)] main : Task.Task {} [] main = - inputResult <- Task.attempt Task.getInt + inputResult = Task.getInt |> Task.result! when inputResult is Ok n -> @@ -248,4 +247,4 @@ del = \t, k -> rebalanceLeft cx lx ky vy ry Delmin (Del ry Bool.false) ky vy -> - Del (Node cx lx ky vy ry) Bool.false \ No newline at end of file + Del (Node cx lx ky vy ry) Bool.false diff --git a/crates/cli/tests/cli/countdown.roc b/crates/cli/tests/cli/countdown.roc index bea034af1e..26b1ce386a 100644 --- a/crates/cli/tests/cli/countdown.roc +++ b/crates/cli/tests/cli/countdown.roc @@ -2,18 +2,18 @@ app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/downlo import pf.Stdin import pf.Stdout -import pf.Task exposing [await, loop] +import pf.Task exposing [Task, loop] main = - _ <- await (Stdout.line "\nLet's count down from 3 together - all you have to do is press .") - _ <- await Stdin.line + Stdout.line! "\nLet's count down from 3 together - all you have to do is press ." + _ = Stdin.line! loop 3 tick tick = \n -> if n == 0 then - _ <- await (Stdout.line "🎉 SURPRISE! Happy Birthday! 🎂") + Stdout.line! "🎉 SURPRISE! Happy Birthday! 🎂" Task.ok (Done {}) else - _ <- await (n |> Num.toStr |> \s -> "$(s)..." |> Stdout.line) - _ <- await Stdin.line + Stdout.line! (n |> Num.toStr |> \s -> "$(s)...") + _ = Stdin.line! Task.ok (Step (n - 1)) diff --git a/crates/cli/tests/cli/echo.roc b/crates/cli/tests/cli/echo.roc index dd6572a957..5c77c7514d 100644 --- a/crates/cli/tests/cli/echo.roc +++ b/crates/cli/tests/cli/echo.roc @@ -5,7 +5,7 @@ import pf.Stdout import pf.Task exposing [Task] 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 diff --git a/crates/cli/tests/cli/parse-args.roc b/crates/cli/tests/cli/parse-args.roc index 145a2e36e4..983d297ca5 100644 --- a/crates/cli/tests/cli/parse-args.roc +++ b/crates/cli/tests/cli/parse-args.roc @@ -55,23 +55,23 @@ numParam = \{ name } -> { params: [param], parser } cliMap : ArgParser a, (a -> b) -> ArgParser b -cliMap = \{ params, parser }, mapper -> { - params, - parser: \args -> - (data, afterData) <- parser args - |> Result.try +cliMap = \{ params, parser }, mapper -> + mappedParser = \args -> + (data, afterData) = parser? args - Ok (mapper data, afterData), -} + Ok (mapper data, afterData) + + { + params, + parser: mappedParser, + } cliBuild : ArgParser a, ArgParser b, (a, b -> c) -> ArgParser c cliBuild = \firstWeaver, secondWeaver, combine -> allParams = List.concat firstWeaver.params secondWeaver.params combinedParser = \args -> - (firstValue, afterFirst) <- firstWeaver.parser args - |> Result.try - (secondValue, afterSecond) <- secondWeaver.parser afterFirst - |> Result.try + (firstValue, afterFirst) = firstWeaver.parser? args + (secondValue, afterSecond) = secondWeaver.parser? afterFirst Ok (combine firstValue secondValue, afterSecond) diff --git a/crates/cli/tests/cli/parser-letter-counts.roc b/crates/cli/tests/cli/parser-letter-counts.roc index ba9780e94d..4e1a749cf8 100644 --- a/crates/cli/tests/cli/parser-letter-counts.roc +++ b/crates/cli/tests/cli/parser-letter-counts.roc @@ -26,18 +26,17 @@ Letter : [A, B, C, Other] letterParser : Parser (List U8) Letter 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 = - when input is - [] -> 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 } + valResult + |> Result.map \val -> { val, input: List.dropFirst input 1 } expect input = "B" diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc index f6d17e2879..31c95b7764 100644 --- a/crates/compiler/builtins/roc/Dict.roc +++ b/crates/compiler/builtins/roc/Dict.roc @@ -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 -> - fmt <- Inspect.custom - Inspect.apply (Inspect.dict dict walk Inspect.toInspector Inspect.toInspector) fmt + Inspect.custom \fmt -> + Inspect.apply (Inspect.dict dict walk Inspect.toInspector Inspect.toInspector) fmt ## Return an empty dictionary. ## ```roc @@ -894,9 +894,9 @@ calcNumBuckets = \shifts -> maxBucketCount fillBucketsFromData = \buckets0, data, shifts -> - buckets1, (key, _), dataIndex <- List.walkWithIndex data buckets0 - (bucketIndex, distAndFingerprint) = nextWhileLess buckets1 key shifts - placeAndShiftUp buckets1 { distAndFingerprint, dataIndex: Num.toU32 dataIndex } bucketIndex + List.walkWithIndex data buckets0 \buckets1, (key, _), dataIndex -> + (bucketIndex, distAndFingerprint) = nextWhileLess buckets1 key shifts + placeAndShiftUp buckets1 { distAndFingerprint, dataIndex: Num.toU32 dataIndex } bucketIndex nextWhileLess : List Bucket, k, U8 -> (U64, U32) where k implements Hash & Eq nextWhileLess = \buckets, key, shifts -> @@ -1213,15 +1213,15 @@ expect ] dict = - acc, k <- List.walk badKeys (Dict.empty {}) - Dict.update acc k \val -> - when val is - Present p -> Present (p |> Num.addWrap 1) - Missing -> Present 0 + List.walk badKeys (Dict.empty {}) \acc, k -> + Dict.update acc k \val -> + when val is + Present p -> Present (p |> Num.addWrap 1) + Missing -> Present 0 allInsertedCorrectly = - acc, k <- List.walk badKeys Bool.true - acc && Dict.contains dict k + List.walk badKeys Bool.true \acc, k -> + acc && Dict.contains dict k allInsertedCorrectly diff --git a/crates/compiler/builtins/roc/Inspect.roc b/crates/compiler/builtins/roc/Inspect.roc index 0f9c08a213..ed7db5369e 100644 --- a/crates/compiler/builtins/roc/Inspect.roc +++ b/crates/compiler/builtins/roc/Inspect.roc @@ -138,203 +138,203 @@ dbgInit = \{} -> @DbgFormatter { data: "" } dbgList : list, ElemWalker (DbgFormatter, Bool) list elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter dbgList = \content, walkFn, toDbgInspector -> - f0 <- custom - dbgWrite f0 "[" - |> \f1 -> - (f2, prependSep), elem <- walkFn content (f1, Bool.false) - f3 = - if prependSep then - dbgWrite f2 ", " - else - f2 + custom \f0 -> + dbgWrite f0 "[" + |> \f1 -> + walkFn content (f1, Bool.false) \(f2, prependSep), elem -> + f3 = + if prependSep then + dbgWrite f2 ", " + else + f2 - elem - |> toDbgInspector - |> apply f3 - |> \f4 -> (f4, Bool.true) - |> .0 - |> dbgWrite "]" + elem + |> toDbgInspector + |> apply f3 + |> \f4 -> (f4, Bool.true) + |> .0 + |> dbgWrite "]" dbgSet : set, ElemWalker (DbgFormatter, Bool) set elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter dbgSet = \content, walkFn, toDbgInspector -> - f0 <- custom - dbgWrite f0 "{" - |> \f1 -> - (f2, prependSep), elem <- walkFn content (f1, Bool.false) - f3 = - if prependSep then - dbgWrite f2 ", " - else - f2 + custom \f0 -> + dbgWrite f0 "{" + |> \f1 -> + walkFn content (f1, Bool.false) \(f2, prependSep), elem -> + f3 = + if prependSep then + dbgWrite f2 ", " + else + f2 - elem - |> toDbgInspector - |> apply f3 - |> \f4 -> (f4, Bool.true) - |> .0 - |> dbgWrite "}" + elem + |> toDbgInspector + |> apply f3 + |> \f4 -> (f4, Bool.true) + |> .0 + |> dbgWrite "}" dbgDict : dict, KeyValWalker (DbgFormatter, Bool) dict key value, (key -> Inspector DbgFormatter), (value -> Inspector DbgFormatter) -> Inspector DbgFormatter dbgDict = \d, walkFn, keyToInspector, valueToInspector -> - f0 <- custom - dbgWrite f0 "{" - |> \f1 -> - (f2, prependSep), key, value <- walkFn d (f1, Bool.false) - f3 = - if prependSep then - dbgWrite f2 ", " - else - f2 + custom \f0 -> + dbgWrite f0 "{" + |> \f1 -> + walkFn d (f1, Bool.false) \(f2, prependSep), key, value -> + f3 = + if prependSep then + dbgWrite f2 ", " + else + f2 - apply (keyToInspector key) f3 - |> dbgWrite ": " - |> \x -> apply (valueToInspector value) x - |> \f4 -> (f4, Bool.true) - |> .0 - |> dbgWrite "}" + apply (keyToInspector key) f3 + |> dbgWrite ": " + |> \x -> apply (valueToInspector value) x + |> \f4 -> (f4, Bool.true) + |> .0 + |> dbgWrite "}" dbgTag : Str, List (Inspector DbgFormatter) -> Inspector DbgFormatter dbgTag = \name, fields -> if List.isEmpty fields then - f0 <- custom - dbgWrite f0 name + custom \f0 -> + dbgWrite f0 name else - f0 <- custom - dbgWrite f0 "(" - |> dbgWrite name - |> \f1 -> - f2, inspector <- List.walk fields f1 - dbgWrite f2 " " - |> \x -> apply inspector x - |> dbgWrite ")" + custom \f0 -> + dbgWrite f0 "(" + |> dbgWrite name + |> \f1 -> + List.walk fields f1 \f2, inspector -> + dbgWrite f2 " " + |> \x -> apply inspector x + |> dbgWrite ")" dbgTuple : List (Inspector DbgFormatter) -> Inspector DbgFormatter dbgTuple = \fields -> - f0 <- custom - dbgWrite f0 "(" - |> \f1 -> - (f2, prependSep), inspector <- List.walk fields (f1, Bool.false) - f3 = - if prependSep then - dbgWrite f2 ", " - else - f2 + custom \f0 -> + dbgWrite f0 "(" + |> \f1 -> + List.walk fields (f1, Bool.false) \(f2, prependSep), inspector -> + f3 = + if prependSep then + dbgWrite f2 ", " + else + f2 - apply inspector f3 - |> \f4 -> (f4, Bool.true) - |> .0 - |> dbgWrite ")" + apply inspector f3 + |> \f4 -> (f4, Bool.true) + |> .0 + |> dbgWrite ")" dbgRecord : List { key : Str, value : Inspector DbgFormatter } -> Inspector DbgFormatter dbgRecord = \fields -> - f0 <- custom - dbgWrite f0 "{" - |> \f1 -> - (f2, prependSep), { key, value } <- List.walk fields (f1, Bool.false) - f3 = - if prependSep then - dbgWrite f2 ", " - else - f2 + custom \f0 -> + dbgWrite f0 "{" + |> \f1 -> + List.walk fields (f1, Bool.false) \(f2, prependSep), { key, value } -> + f3 = + if prependSep then + dbgWrite f2 ", " + else + f2 - dbgWrite f3 key - |> dbgWrite ": " - |> \x -> apply value x - |> \f4 -> (f4, Bool.true) - |> .0 - |> dbgWrite "}" + dbgWrite f3 key + |> dbgWrite ": " + |> \x -> apply value x + |> \f4 -> (f4, Bool.true) + |> .0 + |> dbgWrite "}" dbgBool : Bool -> Inspector DbgFormatter dbgBool = \b -> if b then - f0 <- custom - dbgWrite f0 "Bool.true" + custom \f0 -> + dbgWrite f0 "Bool.true" else - f0 <- custom - dbgWrite f0 "Bool.false" + custom \f0 -> + dbgWrite f0 "Bool.false" dbgStr : Str -> Inspector DbgFormatter dbgStr = \s -> - f0 <- custom - f0 - |> dbgWrite "\"" - |> dbgWrite s # TODO: Should we be escaping strings for dbg/logging? - |> dbgWrite "\"" + custom \f0 -> + f0 + |> dbgWrite "\"" + |> dbgWrite s # TODO: Should we be escaping strings for dbg/logging? + |> dbgWrite "\"" dbgOpaque : * -> Inspector DbgFormatter dbgOpaque = \_ -> - f0 <- custom - dbgWrite f0 "" + custom \f0 -> + dbgWrite f0 "" dbgFunction : * -> Inspector DbgFormatter dbgFunction = \_ -> - f0 <- custom - dbgWrite f0 "" + custom \f0 -> + dbgWrite f0 "" dbgU8 : U8 -> Inspector DbgFormatter dbgU8 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgI8 : I8 -> Inspector DbgFormatter dbgI8 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgU16 : U16 -> Inspector DbgFormatter dbgU16 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgI16 : I16 -> Inspector DbgFormatter dbgI16 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgU32 : U32 -> Inspector DbgFormatter dbgU32 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgI32 : I32 -> Inspector DbgFormatter dbgI32 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgU64 : U64 -> Inspector DbgFormatter dbgU64 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgI64 : I64 -> Inspector DbgFormatter dbgI64 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgU128 : U128 -> Inspector DbgFormatter dbgU128 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgI128 : I128 -> Inspector DbgFormatter dbgI128 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgF32 : F32 -> Inspector DbgFormatter dbgF32 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgF64 : F64 -> Inspector DbgFormatter dbgF64 = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgDec : Dec -> Inspector DbgFormatter dbgDec = \num -> - f0 <- custom - dbgWrite f0 (num |> Num.toStr) + custom \f0 -> + dbgWrite f0 (num |> Num.toStr) dbgWrite : DbgFormatter, Str -> DbgFormatter dbgWrite = \@DbgFormatter { data }, added -> diff --git a/crates/compiler/builtins/roc/Set.roc b/crates/compiler/builtins/roc/Set.roc index c68ae1f2fd..4d0c4a7332 100644 --- a/crates/compiler/builtins/roc/Set.roc +++ b/crates/compiler/builtins/roc/Set.roc @@ -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 -> - fmt <- Inspect.custom - Inspect.apply (Inspect.set set walk Inspect.toInspector) fmt + Inspect.custom \fmt -> + Inspect.apply (Inspect.set set walk Inspect.toInspector) fmt ## Creates a new empty `Set`. ## ```roc diff --git a/crates/compiler/can/src/desugar.rs b/crates/compiler/can/src/desugar.rs index 75cc844a77..13a00cc043 100644 --- a/crates/compiler/can/src/desugar.rs +++ b/crates/compiler/can/src/desugar.rs @@ -12,6 +12,7 @@ use roc_parse::ast::{ AssignedField, Collection, ModuleImportParams, OldRecordBuilderField, Pattern, StrLiteral, StrSegment, TypeAnnotation, ValueDef, WhenBranch, }; +use roc_problem::can::Problem; use roc_region::all::{LineInfo, Loc, Region}; // BinOp precedence logic adapted from Gluon by Markus Westerlind @@ -74,13 +75,14 @@ fn desugar_value_def<'a>( src: &'a str, line_info: &mut Option, module_path: &str, + problems: &mut std::vec::Vec, ) -> ValueDef<'a> { use ValueDef::*; match def { Body(loc_pattern, loc_expr) => Body( - desugar_loc_pattern(arena, loc_pattern, src, line_info, module_path), - desugar_expr(arena, loc_expr, 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, problems), ), ann @ Annotation(_, _) => *ann, AnnotatedBody { @@ -93,16 +95,29 @@ fn desugar_value_def<'a>( ann_pattern, ann_type, lines_between, - body_pattern: desugar_loc_pattern(arena, body_pattern, src, line_info, module_path), - body_expr: desugar_expr(arena, body_expr, src, line_info, module_path), + body_pattern: desugar_loc_pattern( + arena, + body_pattern, + src, + line_info, + module_path, + problems, + ), + body_expr: desugar_expr(arena, body_expr, src, line_info, module_path, problems), }, Dbg { condition, preceding_comment, } => { - let desugared_condition = - &*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); + let desugared_condition = &*arena.alloc(desugar_expr( + arena, + condition, + src, + line_info, + module_path, + problems, + )); Dbg { condition: desugared_condition, preceding_comment: *preceding_comment, @@ -112,8 +127,14 @@ fn desugar_value_def<'a>( condition, preceding_comment, } => { - let desugared_condition = - &*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); + let desugared_condition = &*arena.alloc(desugar_expr( + arena, + condition, + src, + line_info, + module_path, + problems, + )); Expect { condition: desugared_condition, preceding_comment: *preceding_comment, @@ -123,8 +144,14 @@ fn desugar_value_def<'a>( condition, preceding_comment, } => { - let desugared_condition = - &*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); + let desugared_condition = &*arena.alloc(desugar_expr( + arena, + condition, + src, + line_info, + module_path, + problems, + )); ExpectFx { condition: desugared_condition, preceding_comment: *preceding_comment, @@ -141,7 +168,14 @@ fn desugar_value_def<'a>( params.map(|ModuleImportParams { before, params }| ModuleImportParams { before, 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: &[], 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, module_path: &str, top_level_def: bool, + problems: &mut std::vec::Vec, ) { 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` @@ -312,6 +354,7 @@ pub fn desugar_expr<'a>( src: &'a str, line_info: &mut Option, module_path: &str, + problems: &mut std::vec::Vec, ) -> &'a Loc> { match &loc_expr.value { Float(..) @@ -344,6 +387,7 @@ pub fn desugar_expr<'a>( src, line_info, module_path, + problems, ))); arena.alloc(Loc { region, value }) @@ -352,7 +396,7 @@ pub fn desugar_expr<'a>( let region = loc_expr.region; let new_lines = Vec::from_iter_in( 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, ); @@ -375,6 +419,7 @@ pub fn desugar_expr<'a>( src, line_info, module_path, + problems, ) .value, paths, @@ -389,7 +434,8 @@ pub fn desugar_expr<'a>( target, } => { 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); arena.alloc(Loc::at( @@ -413,6 +459,7 @@ pub fn desugar_expr<'a>( src, line_info, module_path, + problems, ) .value, paths, @@ -424,7 +471,14 @@ pub fn desugar_expr<'a>( let mut new_items = Vec::with_capacity_in(items.len(), arena); 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 value: Expr<'a> = List(items.replace_items(new_items)); @@ -435,7 +489,8 @@ pub fn desugar_expr<'a>( }) } 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 { region: loc_expr.region, value: Record(fields), @@ -444,7 +499,7 @@ pub fn desugar_expr<'a>( Tuple(fields) => { let mut allocated = Vec::with_capacity_in(fields.len(), arena); 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); } let fields = fields.replace_items(allocated.into_bump_slice()); @@ -456,11 +511,12 @@ pub fn desugar_expr<'a>( RecordUpdate { fields, update } => { // NOTE the `update` field is always a `Var { .. }`, we only desugar it to get rid of // 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); 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 { value, region: field.region, @@ -479,8 +535,8 @@ pub fn desugar_expr<'a>( Closure(loc_patterns, loc_ret) => arena.alloc(Loc { region: loc_expr.region, value: Closure( - desugar_loc_patterns(arena, loc_patterns, src, line_info, module_path), - desugar_expr(arena, loc_ret, 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, problems), ), }), Backpassing(loc_patterns, loc_body, loc_ret) => { @@ -488,12 +544,19 @@ pub fn desugar_expr<'a>( // // loc_ret - // first desugar the body, because it may contain |> - let desugared_body = desugar_expr(arena, loc_body, src, line_info, module_path); + let problem_region = Region::span_across( + &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 = - 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 loc_closure = Loc::at(loc_expr.region, closure); @@ -529,7 +592,7 @@ pub fn desugar_expr<'a>( RecordBuilder { mapper, fields } => { // NOTE the `mapper` is always a `Var { .. }`, we only desugar it to get rid of // 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() { return arena.alloc(Loc { @@ -553,7 +616,8 @@ pub fn desugar_expr<'a>( for field in fields.items { 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) => { (loc_name, loc_val, false) } @@ -791,11 +855,20 @@ pub fn desugar_expr<'a>( src, line_info, module_path, + problems, ), Defs(defs, loc_ret) => { let mut defs = (*defs).clone(); - desugar_defs_node_values(arena, &mut defs, src, line_info, module_path, false); - let loc_ret = desugar_expr(arena, loc_ret, src, line_info, module_path); + desugar_defs_node_values( + 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))) } @@ -827,14 +900,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 mut apply: &Loc = arena.alloc(Loc { 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, *called_via, ), @@ -846,7 +926,8 @@ pub fn desugar_expr<'a>( Some(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)); @@ -867,17 +948,31 @@ pub fn desugar_expr<'a>( src, line_info, module_path, + problems, )); let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena); for branch in branches.iter() { let desugared_expr = - desugar_expr(arena, &branch.value, src, line_info, module_path); - let desugared_patterns = - desugar_loc_patterns(arena, branch.patterns, src, line_info, module_path); + desugar_expr(arena, &branch.value, src, line_info, module_path, problems); + let desugared_patterns = desugar_loc_patterns( + arena, + branch.patterns, + src, + line_info, + module_path, + problems, + ); 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 { None }; @@ -915,8 +1010,14 @@ pub fn desugar_expr<'a>( }, }; let loc_fn_var = arena.alloc(Loc { region, value }); - let desugared_args = - arena.alloc([desugar_expr(arena, loc_arg, src, line_info, module_path)]); + let desugared_args = arena.alloc([desugar_expr( + arena, + loc_arg, + src, + line_info, + module_path, + problems, + )]); arena.alloc(Loc { value: Apply(loc_fn_var, desugared_args, CalledVia::UnaryOp(op)), @@ -935,6 +1036,7 @@ pub fn desugar_expr<'a>( src, line_info, module_path, + problems, ) } ParensAround(expr) => { @@ -947,6 +1049,7 @@ pub fn desugar_expr<'a>( src, line_info, module_path, + problems, ); arena.alloc(Loc { @@ -962,14 +1065,15 @@ pub fn desugar_expr<'a>( src, line_info, module_path, + problems, )); let mut desugared_if_thens = Vec::with_capacity_in(if_thens.len(), arena); for (condition, then_branch) in if_thens.iter() { desugared_if_thens.push(( - *desugar_expr(arena, condition, src, line_info, module_path), - *desugar_expr(arena, then_branch, 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, problems), )); } @@ -979,14 +1083,21 @@ pub fn desugar_expr<'a>( }) } Expect(condition, continuation) => { - let desugared_condition = - &*arena.alloc(desugar_expr(arena, condition, src, line_info, module_path)); + let desugared_condition = &*arena.alloc(desugar_expr( + arena, + condition, + src, + line_info, + module_path, + problems, + )); let desugared_continuation = &*arena.alloc(desugar_expr( arena, continuation, src, line_info, module_path, + problems, )); arena.alloc(Loc { value: Expect(desugared_condition, desugared_continuation), @@ -1002,6 +1113,7 @@ pub fn desugar_expr<'a>( src, line_info, module_path, + problems, )); let region = condition.region; @@ -1014,8 +1126,14 @@ pub fn desugar_expr<'a>( value: inspect_fn, region, }); - let desugared_inspect_args = - arena.alloc([desugar_expr(arena, condition, src, line_info, module_path)]); + let desugared_inspect_args = arena.alloc([desugar_expr( + arena, + condition, + src, + line_info, + module_path, + problems, + )]); let dbg_str = arena.alloc(Loc { value: Apply(loc_inspect_fn_var, desugared_inspect_args, CalledVia::Space), @@ -1060,6 +1178,7 @@ fn desugar_str_segments<'a>( src: &'a str, line_info: &mut Option, module_path: &str, + problems: &mut std::vec::Vec, ) -> &'a [StrSegment<'a>] { Vec::from_iter_in( segments.iter().map(|segment| match segment { @@ -1076,6 +1195,7 @@ fn desugar_str_segments<'a>( src, line_info, module_path, + problems, ); StrSegment::DeprecatedInterpolated(Loc { region: loc_desugared.region, @@ -1092,6 +1212,7 @@ fn desugar_str_segments<'a>( src, line_info, module_path, + problems, ); StrSegment::Interpolated(Loc { region: loc_desugared.region, @@ -1110,11 +1231,12 @@ fn desugar_field_collection<'a>( src: &'a str, line_info: &mut Option, module_path: &str, + problems: &mut std::vec::Vec, ) -> Collection<'a, Loc>>> { let mut allocated = Vec::with_capacity_in(fields.len(), arena); for field in fields.iter() { - let value = desugar_field(arena, &field.value, src, line_info, module_path); + let value = desugar_field(arena, &field.value, src, line_info, module_path, problems); allocated.push(Loc::at(field.region, value)); } @@ -1128,6 +1250,7 @@ fn desugar_field<'a>( src: &'a str, line_info: &mut Option, module_path: &str, + problems: &mut std::vec::Vec, ) -> AssignedField<'a, Expr<'a>> { use roc_parse::ast::AssignedField::*; @@ -1138,7 +1261,7 @@ fn desugar_field<'a>( region: loc_str.region, }, 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( Loc { @@ -1146,7 +1269,7 @@ fn desugar_field<'a>( region: loc_str.region, }, 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( Loc { @@ -1154,7 +1277,7 @@ fn desugar_field<'a>( region: loc_str.region, }, 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) => { // Desugar { x } into { x: x } @@ -1172,11 +1295,22 @@ fn desugar_field<'a>( 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), - SpaceAfter(field, _spaces) => desugar_field(arena, field, src, line_info, module_path), + SpaceBefore(field, _spaces) => { + 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), } @@ -1188,11 +1322,19 @@ fn desugar_loc_patterns<'a>( src: &'a str, line_info: &mut Option, module_path: &str, + problems: &mut std::vec::Vec, ) -> &'a [Loc>] { Vec::from_iter_in( loc_patterns.iter().map(|loc_pattern| Loc { 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, ) @@ -1205,10 +1347,18 @@ fn desugar_loc_pattern<'a>( src: &'a str, line_info: &mut Option, module_path: &str, + problems: &mut std::vec::Vec, ) -> &'a Loc> { arena.alloc(Loc { 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, + ), }) } @@ -1218,6 +1368,7 @@ fn desugar_pattern<'a>( src: &'a str, line_info: &mut Option, module_path: &str, + problems: &mut std::vec::Vec, ) -> Pattern<'a> { use roc_parse::ast::Pattern::*; @@ -1241,7 +1392,14 @@ fn desugar_pattern<'a>( let desugared_arg_patterns = Vec::from_iter_in( arg_patterns.iter().map(|arg_pattern| Loc { 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, ) @@ -1252,8 +1410,14 @@ fn desugar_pattern<'a>( RecordDestructure(field_patterns) => { let mut allocated = Vec::with_capacity_in(field_patterns.len(), arena); for field_pattern in field_patterns.iter() { - let value = - desugar_pattern(arena, field_pattern.value, src, line_info, module_path); + let value = desugar_pattern( + arena, + field_pattern.value, + src, + line_info, + module_path, + problems, + ); allocated.push(Loc { value, region: field_pattern.region, @@ -1265,15 +1429,17 @@ fn desugar_pattern<'a>( } RequiredField(name, field_pattern) => RequiredField( 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) => { let mut allocated = Vec::with_capacity_in(patterns.len(), arena); 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 { value, region: pattern.region, @@ -1286,7 +1452,8 @@ fn desugar_pattern<'a>( List(patterns) => { let mut allocated = Vec::with_capacity_in(patterns.len(), arena); 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 { value, region: pattern.region, @@ -1297,14 +1464,14 @@ fn desugar_pattern<'a>( List(patterns) } 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, ), 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) => { - desugar_pattern(arena, *sub_pattern, src, line_info, module_path) + desugar_pattern(arena, *sub_pattern, src, line_info, module_path, problems) } } } @@ -1425,6 +1592,7 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { } } +#[allow(clippy::too_many_arguments)] fn desugar_bin_ops<'a>( arena: &'a Bump, whole_region: Region, @@ -1433,19 +1601,27 @@ fn desugar_bin_ops<'a>( src: &'a str, line_info: &mut Option, module_path: &str, + problems: &mut std::vec::Vec, ) -> &'a Loc> { let mut arg_stack: Vec<&'a Loc> = Vec::with_capacity_in(lefts.len() + 1, arena); let mut op_stack: Vec> = Vec::with_capacity_in(lefts.len(), arena); 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) { Err(problem) => return problem, 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() { expr = arena.alloc(new_op_call_expr(arena, left, loc_op, expr)); diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs index ddcbcf4361..6a13626e30 100644 --- a/crates/compiler/can/src/module.rs +++ b/crates/compiler/can/src/module.rs @@ -337,7 +337,15 @@ pub fn canonicalize_module_defs<'a>( // operators, and then again on *their* nested operators, ultimately applying the // rules multiple times unnecessarily. - crate::desugar::desugar_defs_node_values(arena, loc_defs, src, &mut None, module_path, 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(); diff --git a/crates/compiler/can/tests/helpers/mod.rs b/crates/compiler/can/tests/helpers/mod.rs index 508d84ea84..c1e144c7d2 100644 --- a/crates/compiler/can/tests/helpers/mod.rs +++ b/crates/compiler/can/tests/helpers/mod.rs @@ -59,6 +59,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut expr_str, &mut None, arena.alloc("TestPath"), + &mut Default::default(), ); let mut scope = Scope::new( diff --git a/crates/compiler/can/tests/test_suffixed.rs b/crates/compiler/can/tests/test_suffixed.rs index 48ac9fb91c..f82d857e5f 100644 --- a/crates/compiler/can/tests/test_suffixed.rs +++ b/crates/compiler/can/tests/test_suffixed.rs @@ -12,7 +12,15 @@ mod suffixed_tests { ($src:expr) => {{ let arena = &Bump::new(); 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); println!("{}", snapshot); diff --git a/crates/compiler/load/tests/helpers/mod.rs b/crates/compiler/load/tests/helpers/mod.rs index 63679c9820..4c86b54c8a 100644 --- a/crates/compiler/load/tests/helpers/mod.rs +++ b/crates/compiler/load/tests/helpers/mod.rs @@ -174,6 +174,7 @@ pub fn can_expr_with<'a>( expr_str, &mut None, arena.alloc("TestPath"), + &mut Default::default(), ); let mut scope = Scope::new( diff --git a/crates/compiler/load/tests/test_reporting.rs b/crates/compiler/load/tests/test_reporting.rs index 96fd1aa33c..ac4bbc391e 100644 --- a/crates/compiler/load/tests/test_reporting.rs +++ b/crates/compiler/load/tests/test_reporting.rs @@ -4816,7 +4816,7 @@ mod test_reporting { expression_indentation_end, indoc!( r" - f <- Foo.foo + f = Foo.foo " ), @r#" @@ -4827,8 +4827,8 @@ mod test_reporting { 1│ app "test" provides [main] to "./platform" 2│ 3│ main = - 4│ f <- Foo.foo - ^ + 4│ f = Foo.foo + ^ Looks like the indentation ends prematurely here. Did you mean to have 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!( expect_expr_type_error, indoc!( @@ -10198,9 +10170,9 @@ All branches in an `if` must have the same type! withOpen : (Handle -> Result {} *) -> Result {} * withOpen = \callback -> - handle <- await (open {}) - {} <- await (callback handle) - close handle + await (open {}) \handle -> + await (callback handle) \_ -> + close handle withOpen " @@ -10212,9 +10184,9 @@ All branches in an `if` must have the same type! 10│ withOpen : (Handle -> Result {} *) -> Result {} * 11│ withOpen = \callback -> - 12│> handle <- await (open {}) - 13│> {} <- await (callback handle) - 14│> close handle + 12│> await (open {}) \handle -> + 13│> await (callback handle) \_ -> + 14│> close handle The type annotation on `withOpen` says this `await` call should have the 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 a `*`! Maybe the annotation on `withOpen` should have a named type variable in place of the `*`? + " ); @@ -10830,7 +10803,7 @@ All branches in an `if` must have the same type! 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 <- ^^^ - 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. "# ); @@ -11844,6 +11817,32 @@ All branches in an `if` must have the same type! @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!( unknown_shorthand_no_deps, indoc!( @@ -13881,7 +13880,7 @@ All branches in an `if` must have the same type! "# ), @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: @@ -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 removed! + "# ); @@ -13953,7 +13953,7 @@ All branches in an `if` must have the same type! "# ), @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: @@ -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 removed! + "# ); diff --git a/crates/compiler/module/src/called_via.rs b/crates/compiler/module/src/called_via.rs index cfac8d5f2a..18c274020a 100644 --- a/crates/compiler/module/src/called_via.rs +++ b/crates/compiler/module/src/called_via.rs @@ -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)] pub enum BinOp { // highest precedence diff --git a/crates/compiler/problem/src/can.rs b/crates/compiler/problem/src/can.rs index b253dcab3d..5f2463dea8 100644 --- a/crates/compiler/problem/src/can.rs +++ b/crates/compiler/problem/src/can.rs @@ -54,6 +54,7 @@ pub enum Problem { new_symbol: Symbol, existing_symbol_region: Region, }, + DeprecatedBackpassing(Region), /// First symbol is the name of the closure with that argument /// Bool is whether the closure is anonymous /// Second symbol is the name of the argument that is unused @@ -257,6 +258,7 @@ impl Problem { Problem::ExplicitBuiltinImport(_, _) => Warning, Problem::ExplicitBuiltinTypeImport(_, _) => Warning, Problem::ImportShadowsSymbol { .. } => RuntimeError, + Problem::DeprecatedBackpassing(_) => Warning, Problem::ExposedButNotDefined(_) => RuntimeError, Problem::UnknownGeneratesWith(_) => RuntimeError, Problem::UnusedArgument(_, _, _, _) => Warning, @@ -340,6 +342,7 @@ impl Problem { | Problem::ExplicitBuiltinImport(_, region) | Problem::ExplicitBuiltinTypeImport(_, region) | Problem::ImportShadowsSymbol { region, .. } + | Problem::DeprecatedBackpassing(region) | Problem::UnknownGeneratesWith(Loc { region, .. }) | Problem::UnusedArgument(_, _, _, region) | Problem::UnusedBranchDef(_, region) diff --git a/crates/compiler/test_mono/src/tests.rs b/crates/compiler/test_mono/src/tests.rs index 9a0a4f2846..5e8e78ed0a 100644 --- a/crates/compiler/test_mono/src/tests.rs +++ b/crates/compiler/test_mono/src/tests.rs @@ -3389,8 +3389,8 @@ fn inspect_custom_type() { myToInspector : HelloWorld -> Inspector f where f implements InspectFormatter myToInspector = \@HellowWorld {} -> - fmt <- Inspect.custom - Inspect.apply (Inspect.str "Hello, World!\n") fmt + Inspect.custom \fmt -> + Inspect.apply (Inspect.str "Hello, World!\n") fmt main = Inspect.inspect (@HelloWorld {}) diff --git a/crates/reporting/src/error/canonicalize.rs b/crates/reporting/src/error/canonicalize.rs index 01d04a44be..b42e022b7e 100644 --- a/crates/reporting/src/error/canonicalize.rs +++ b/crates/reporting/src/error/canonicalize.rs @@ -1,4 +1,5 @@ use roc_collections::all::MutSet; +use roc_module::called_via::Suffix; use roc_module::ident::{Ident, Lowercase, ModuleName}; use roc_module::symbol::DERIVABLE_ABILITIES; use roc_problem::can::PrecedenceProblem::BothNonAssociative; @@ -246,6 +247,26 @@ pub fn can_problem<'b>( 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) => { doc = alloc.stack([ 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) => { doc = alloc.stack([ diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index e3c18b5bf3..1c35eccb90 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -1265,7 +1265,7 @@ fn to_expr_report<'b>( alloc.concat([ alloc.tip(), alloc.reflow("Remove "), - alloc.keyword("<-"), + alloc.backpassing_arrow(), alloc.reflow(" to assign the field directly.") ]) } @@ -1273,7 +1273,7 @@ fn to_expr_report<'b>( alloc.concat([ alloc.note(""), alloc.reflow("Record builders need a mapper function before the "), - alloc.keyword("<-"), + alloc.backpassing_arrow(), alloc.reflow(" to combine fields together with.") ]) } diff --git a/crates/reporting/src/report.rs b/crates/reporting/src/report.rs index 0e9e2d968d..e914fccce4 100644 --- a/crates/reporting/src/report.rs +++ b/crates/reporting/src/report.rs @@ -509,6 +509,10 @@ impl<'a> RocDocAllocator<'a> { self.text(name).annotate(Annotation::Shorthand) } + pub fn backpassing_arrow(&'a self) -> DocBuilder<'a, Self, Annotation> { + self.text("<-").annotate(Annotation::BinOp) + } + pub fn binop( &'a self, content: roc_module::called_via::BinOp, @@ -523,6 +527,13 @@ impl<'a> RocDocAllocator<'a> { 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 pub fn type_block( &'a self, diff --git a/examples/Community.roc b/examples/Community.roc index f413f133e5..e19906a8f6 100644 --- a/examples/Community.roc +++ b/examples/Community.roc @@ -55,27 +55,28 @@ addFriend = \@Community { people, friends }, from, to -> walkFriendNames : Community, state, (state, Str, Set Str -> state) -> state walkFriendNames = \@Community { people, friends }, s0, nextFn -> (out, _) = - (s1, id), friendSet <- List.walk friends (s0, 0) - (@Person person) = - 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 + List.walk friends (s0, 0) \(s1, id), friendSet -> + (@Person person) = + when List.get people id is Ok v -> v Err _ -> crash "Unknown Person" - friendName = - friend.firstName + personName = + person.firstName |> Str.concat " " - |> Str.concat friend.lastName - Set.insert friendsSet friendName + |> Str.concat person.lastName + + 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 diff --git a/examples/cli/false-interpreter/Context.roc b/examples/cli/false-interpreter/Context.roc index 747124a334..1b97242f69 100644 --- a/examples/cli/false-interpreter/Context.roc +++ b/examples/cli/false-interpreter/Context.roc @@ -61,18 +61,18 @@ toStr = \{ scopes, stack, state, vars } -> with : Str, (Context -> Task {} a) -> Task {} a with = \path, callback -> - handle <- File.withOpen path - # I cant define scope here and put it in the list in callback. It breaks alias anaysis. - # Instead I have to inline this. - # 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 } + File.withOpen path \handle -> + # I cant define scope here and put it in the list in callback. It breaks alias anaysis. + # Instead I have to inline this. + # 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 } # 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 = \ctx -> when List.last ctx.scopes is 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 }) Err ListWasEmpty -> @@ -87,7 +87,7 @@ getCharScope = \scope -> Err OutOfBounds -> when scope.data is Some h -> - bytes <- Task.await (File.chunk h) + bytes = File.chunk! h when List.first bytes is Ok val -> # This starts at 1 because the first character is already being returned. diff --git a/examples/cli/false-interpreter/False.roc b/examples/cli/false-interpreter/False.roc index 7300abff72..96fc3bb625 100644 --- a/examples/cli/false-interpreter/False.roc +++ b/examples/cli/false-interpreter/False.roc @@ -28,47 +28,47 @@ main = \filename -> interpretFile : Str -> Task {} [StringErr Str] interpretFile = \filename -> - ctx <- Context.with filename - result <- Task.attempt (interpretCtx ctx) - when result is - Ok _ -> - Task.succeed {} + Context.with filename \ctx -> + result = interpretCtx ctx |> Task.result! + when result is + Ok _ -> + Task.succeed {} - Err BadUtf8 -> - Task.fail (StringErr "Failed to convert string from Utf8 bytes") + Err BadUtf8 -> + Task.fail (StringErr "Failed to convert string from Utf8 bytes") - Err DivByZero -> - Task.fail (StringErr "Division by zero") + Err DivByZero -> + Task.fail (StringErr "Division by zero") - Err EmptyStack -> - Task.fail (StringErr "Tried to pop a value off of the stack when it was empty") + Err EmptyStack -> + Task.fail (StringErr "Tried to pop a value off of the stack when it was empty") - Err InvalidBooleanValue -> - Task.fail (StringErr "Ran into an invalid boolean that was neither false (0) or true (-1)") + Err InvalidBooleanValue -> + Task.fail (StringErr "Ran into an invalid boolean that was neither false (0) or true (-1)") - Err (InvalidChar char) -> - Task.fail (StringErr "Ran into an invalid character with ascii code: $(char)") + Err (InvalidChar char) -> + Task.fail (StringErr "Ran into an invalid character with ascii code: $(char)") - Err MaxInputNumber -> - Task.fail (StringErr "Like the original false compiler, the max input number is 320,000") + Err MaxInputNumber -> + Task.fail (StringErr "Like the original false compiler, the max input number is 320,000") - Err NoLambdaOnStack -> - Task.fail (StringErr "Tried to run a lambda when no lambda was on the stack") + Err NoLambdaOnStack -> + Task.fail (StringErr "Tried to run a lambda when no lambda was on the stack") - Err NoNumberOnStack -> - Task.fail (StringErr "Tried to run a number when no number was on the stack") + Err NoNumberOnStack -> + Task.fail (StringErr "Tried to run a number when no number was on the stack") - Err NoVariableOnStack -> - Task.fail (StringErr "Tried to load a variable when no variable was on the stack") + Err NoVariableOnStack -> + Task.fail (StringErr "Tried to load a variable when no variable was on the stack") - Err NoScope -> - Task.fail (StringErr "Tried to run code when not in any scope") + Err NoScope -> + Task.fail (StringErr "Tried to run code when not in any scope") - Err OutOfBounds -> - Task.fail (StringErr "Tried to load from an offset that was outside of the stack") + Err OutOfBounds -> + Task.fail (StringErr "Tried to load from an offset that was outside of the stack") - Err UnexpectedEndOfData -> - Task.fail (StringErr "Hit end of data while still parsing something") + Err UnexpectedEndOfData -> + Task.fail (StringErr "Hit end of data while still parsing something") isDigit : U8 -> Bool isDigit = \char -> @@ -129,11 +129,11 @@ interpretCtxLoop = \ctx -> Task.fail NoScope Executing -> - # {} <- Task.await (Stdout.line (Context.toStr ctx)) - result <- Task.attempt (Context.getChar ctx) + # Stdout.line! (Context.toStr ctx) + result = Context.getChar ctx |> Task.result! when result is Ok (T val newCtx) -> - execCtx <- Task.await (stepExecCtx newCtx val) + execCtx = stepExecCtx! newCtx val Task.succeed (Step execCtx) Err NoScope -> @@ -151,7 +151,7 @@ interpretCtxLoop = \ctx -> Task.succeed (Step dropCtx) InComment -> - result <- Task.attempt (Context.getChar ctx) + result = Context.getChar ctx |> Task.result! when result is Ok (T val newCtx) -> if val == 0x7D then @@ -167,7 +167,7 @@ interpretCtxLoop = \ctx -> Task.fail UnexpectedEndOfData InNumber accum -> - result <- Task.attempt (Context.getChar ctx) + result = Context.getChar ctx |> Task.result! when result is Ok (T val newCtx) -> if isDigit val then @@ -182,7 +182,7 @@ interpretCtxLoop = \ctx -> # outside of number now, this needs to be executed. pushCtx = Context.pushStack newCtx (Number accum) - execCtx <- Task.await (stepExecCtx { pushCtx & state: Executing } val) + execCtx = stepExecCtx! { pushCtx & state: Executing } val Task.succeed (Step execCtx) Err NoScope -> @@ -192,14 +192,14 @@ interpretCtxLoop = \ctx -> Task.fail UnexpectedEndOfData InString bytes -> - result <- Task.attempt (Context.getChar ctx) + result = Context.getChar ctx |> Task.result! when result is Ok (T val newCtx) -> if val == 0x22 then # `"` end of string when Str.fromUtf8 bytes is Ok str -> - {} <- Task.await (Stdout.raw str) + Stdout.raw! str Task.succeed (Step { newCtx & state: Executing }) Err _ -> @@ -214,7 +214,7 @@ interpretCtxLoop = \ctx -> Task.fail UnexpectedEndOfData InLambda depth bytes -> - result <- Task.attempt (Context.getChar ctx) + result = Context.getChar ctx |> Task.result! when result is Ok (T val newCtx) -> if val == 0x5B then @@ -238,7 +238,7 @@ interpretCtxLoop = \ctx -> Task.fail UnexpectedEndOfData InSpecialChar -> - result <- Task.attempt (Context.getChar { ctx & state: Executing }) + result = Context.getChar { ctx & state: Executing } |> Task.result! when result is Ok (T 0xB8 newCtx) -> result2 = @@ -273,7 +273,7 @@ interpretCtxLoop = \ctx -> Task.fail UnexpectedEndOfData LoadChar -> - result <- Task.attempt (Context.getChar { ctx & state: Executing }) + result = Context.getChar { ctx & state: Executing } |> Task.result! when result is Ok (T x newCtx) -> Task.succeed (Step (Context.pushStack newCtx (Number (Num.intCast x)))) @@ -472,7 +472,7 @@ stepExecCtx = \ctx, char -> Ok (T popCtx num) -> when Str.fromUtf8 [Num.intCast num] is Ok str -> - {} <- Task.await (Stdout.raw str) + Stdout.raw! str Task.succeed popCtx Err _ -> @@ -485,7 +485,7 @@ stepExecCtx = \ctx, char -> # `.` write int when popNumber ctx is Ok (T popCtx num) -> - {} <- Task.await (Stdout.raw (Num.toStr (Num.intCast num))) + Stdout.raw! (Num.toStr (Num.intCast num)) Task.succeed popCtx Err e -> @@ -493,7 +493,7 @@ stepExecCtx = \ctx, char -> 0x5E -> # `^` read char as int - in <- Task.await Stdin.char + in = Stdin.char! if in == 255 then # max char sent on EOF. Change to -1 Task.succeed (Context.pushStack ctx (Number -1)) diff --git a/examples/cli/false-interpreter/platform/File.roc b/examples/cli/false-interpreter/platform/File.roc index 9d6e2e8677..d9496bc455 100644 --- a/examples/cli/false-interpreter/platform/File.roc +++ b/examples/cli/false-interpreter/platform/File.roc @@ -22,7 +22,8 @@ close = \@Handle handle -> Effect.after (Effect.closeFile handle) Task.succeed withOpen : Str, (Handle -> Task {} a) -> Task {} a withOpen = \path, callback -> - handle <- Task.await (open path) - result <- Task.attempt (callback handle) - {} <- Task.await (close handle) + handle = open! path + result = callback handle |> Task.result! + close! handle + Task.fromResult result diff --git a/examples/cli/false-interpreter/platform/Task.roc b/examples/cli/false-interpreter/platform/Task.roc index d5f212da47..bdc608f279 100644 --- a/examples/cli/false-interpreter/platform/Task.roc +++ b/examples/cli/false-interpreter/platform/Task.roc @@ -1,4 +1,14 @@ -module [Task, succeed, fail, await, map, onFail, attempt, fromResult, loop] +module [ + Task, + succeed, + fail, + await, + map, + onFail, + attempt, + fromResult, + loop, +] import pf.Effect @@ -66,3 +76,9 @@ map = \effect, transform -> when result is Ok a -> Task.succeed (transform a) Err err -> Task.fail err + +result : Task ok err -> Task (Result ok err) * +result = \effect -> + Effect.after + effect + \result -> Task.succeed result diff --git a/examples/virtual-dom-wip/platform/Html/Internal/Client.roc b/examples/virtual-dom-wip/platform/Html/Internal/Client.roc index 00e42d1fd4..46ca2549c4 100644 --- a/examples/virtual-dom-wip/platform/Html/Internal/Client.roc +++ b/examples/virtual-dom-wip/platform/Html/Internal/Client.roc @@ -88,13 +88,12 @@ initClientApp = \json, app -> initClientAppHelp json app # Call out to JS to patch the DOM, attaching the event listeners - _ <- applyPatches patches |> Effect.after - - Effect.always { - app, - state, - rendered, - } + Effect.after (applyPatches patches) \_ -> + Effect.always { + app, + state, + rendered, + } # Testable helper function to initialise the app 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 } = diff { rendered, patches: [] } newViewUnrendered - _ <- applyPatches patches |> Effect.after - Effect.always { - platformState: { - app, - state: newState, - rendered: newRendered, - }, - stopPropagation, - preventDefault, - } + Effect.after (applyPatches patches) \_ -> + Effect.always { + platformState: { + app, + state: newState, + rendered: newRendered, + }, + stopPropagation, + preventDefault, + } None -> Effect.always { platformState, stopPropagation, preventDefault } diff --git a/examples/virtual-dom-wip/platform/Json.roc b/examples/virtual-dom-wip/platform/Json.roc index d28c2e925b..1b1c456694 100644 --- a/examples/virtual-dom-wip/platform/Json.roc +++ b/examples/virtual-dom-wip/platform/Json.roc @@ -540,32 +540,35 @@ expect decodeTuple = \initialState, stepElem, finalizer -> Decode.custom \initialBytes, @Json {} -> # NB: the stepper function must be passed explicitly until #2894 is resolved. decodeElems = \stepper, state, index, bytes -> - { val: newState, rest: beforeCommaOrBreak } <- tryDecode - ( - when stepper state index is - TooLong -> - { rest: beforeCommaOrBreak } <- bytes |> anything |> tryDecode - { result: Ok state, rest: beforeCommaOrBreak } + ( + when stepper state index is + TooLong -> + bytes + |> anything + |> tryDecode \{ rest: beforeCommaOrBreak } -> + { result: Ok state, rest: beforeCommaOrBreak } - Next decoder -> - Decode.decodeWith bytes decoder json - ) + Next decoder -> + 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 - Ok {} -> decodeElems stepElem newState (index + 1) nextBytes - Err _ -> { result: Ok newState, rest: nextBytes } - - { rest: afterBracketBytes } <- initialBytes |> openBracket |> tryDecode - - { val: endStateResult, rest: beforeClosingBracketBytes } <- decodeElems stepElem initialState 0 afterBracketBytes |> tryDecode - - { rest: afterTupleBytes } <- beforeClosingBracketBytes |> closingBracket |> tryDecode - - when finalizer endStateResult is - Ok val -> { result: Ok val, rest: afterTupleBytes } - Err e -> { result: Err e, rest: afterTupleBytes } + initialBytes + |> openBracket + |> tryDecode \{ rest: afterBracketBytes } -> + decodeElems stepElem initialState 0 afterBracketBytes + |> tryDecode \{ val: endStateResult, rest: beforeClosingBracketBytes } -> + beforeClosingBracketBytes + |> closingBracket + |> tryDecode \{ rest: afterTupleBytes } -> + when finalizer endStateResult is + Ok val -> { result: Ok val, rest: afterTupleBytes } + Err e -> { result: Err e, rest: afterTupleBytes } # Test decode of tuple expect @@ -1225,44 +1228,42 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso Ok objectName -> # Decode the json value - { val: updatedRecord, rest: bytesAfterValue } <- - ( - fieldName = objectName + ( + fieldName = objectName - # Retrieve value decoder for the current field - when stepField recordState fieldName is - Skip -> - # TODO This doesn't seem right, shouldn't we eat - # the remaining json object value bytes if we are skipping this - # field? - { result: Ok recordState, rest: valueBytes } + # Retrieve value decoder for the current field + when stepField recordState fieldName is + Skip -> + # TODO This doesn't seem right, shouldn't we eat + # the remaining json object value bytes if we are skipping this + # field? + { result: Ok recordState, rest: valueBytes } - Keep valueDecoder -> - # Decode the value using the decoder from the recordState - # Note we need to pass json config options recursively here - Decode.decodeWith valueBytes valueDecoder (@Json {}) - ) - |> tryDecode + Keep valueDecoder -> + # Decode the value using the decoder from the recordState + # Note we need to pass json config options recursively here + Decode.decodeWith valueBytes valueDecoder (@Json {}) + ) + |> 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 - when List.walkUntil bytesAfterValue (AfterObjectValue 0) objectHelp is - ObjectFieldNameStart n -> - rest = List.dropFirst bytesAfterValue n + # Decode the next field and value + decodeFields updatedRecord rest - # Decode the next field and value - decodeFields updatedRecord rest + AfterClosingBrace n -> + rest = List.dropFirst bytesAfterValue n - AfterClosingBrace n -> - rest = List.dropFirst bytesAfterValue n + # Build final record from decoded fields and values + 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 - Ok val -> { result: Ok val, rest } - Err e -> { result: Err e, rest } - - _ -> - # Invalid object - { result: Err TooShort, rest: bytesAfterValue } + _ -> + # Invalid object + { result: Err TooShort, rest: bytesAfterValue } countBytesBeforeFirstField = when List.walkUntil bytes (BeforeOpeningBrace 0) objectHelp is From f9008c3af0e992f20b60f706e34b4ae518df5993 Mon Sep 17 00:00:00 2001 From: Sam Mohr Date: Fri, 16 Aug 2024 23:37:31 -0700 Subject: [PATCH 2/3] Fix broken CLI tests --- crates/cli/tests/benchmarks/platform/Task.roc | 20 +++++++------- crates/cli/tests/cli_run.rs | 27 ++++++++++++------- crates/cli/tests/known_bad/TypeError.roc | 9 +++---- .../cli/false-interpreter/platform/Task.roc | 25 ++++++++--------- 4 files changed, 44 insertions(+), 37 deletions(-) diff --git a/crates/cli/tests/benchmarks/platform/Task.roc b/crates/cli/tests/benchmarks/platform/Task.roc index d948e7d798..090710395a 100644 --- a/crates/cli/tests/benchmarks/platform/Task.roc +++ b/crates/cli/tests/benchmarks/platform/Task.roc @@ -38,7 +38,7 @@ loop = \state, step -> \res -> when res is Ok (Step newState) -> Step newState - Ok (Done result) -> Done (Ok result) + Ok (Done res2) -> Done (Ok res2) Err e -> Done (Err e) Effect.loop state looper @@ -55,8 +55,8 @@ after : Task a err, (a -> Task b err) -> Task b err after = \effect, transform -> Effect.after effect - \result -> - when result is + \res -> + when res is Ok a -> transform a Err err -> Task.fail err @@ -64,8 +64,8 @@ await : Task a err, (a -> Task b err) -> Task b err await = \effect, transform -> Effect.after effect - \result -> - when result is + \res -> + when res is Ok a -> transform a Err err -> Task.fail err @@ -73,8 +73,8 @@ attempt : Task a b, (Result a b -> Task c d) -> Task c d attempt = \task, transform -> Effect.after task - \result -> - when result is + \res -> + when res is Ok ok -> transform (Ok ok) Err err -> transform (Err err) @@ -82,8 +82,8 @@ map : Task a err, (a -> b) -> Task b err map = \effect, transform -> Effect.map effect - \result -> - when result is + \res -> + when res is Ok a -> Ok (transform a) Err err -> Err err @@ -91,7 +91,7 @@ result : Task ok err -> Task (Result ok err) * result = \effect -> Effect.after effect - \result -> Task.succeed result + \res -> Task.succeed res putLine : Str -> Task {} * putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) diff --git a/crates/cli/tests/cli_run.rs b/crates/cli/tests/cli_run.rs index 9904498c1e..62f77a7a05 100644 --- a/crates/cli/tests/cli_run.rs +++ b/crates/cli/tests/cli_run.rs @@ -23,6 +23,7 @@ mod cli_run { use serial_test::serial; use std::iter; use std::path::Path; + use std::process::ExitStatus; #[cfg(all(unix, not(target_os = "macos")))] const ALLOW_VALGRIND: bool = true; @@ -106,6 +107,11 @@ mod cli_run { 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) { let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[], &[]); @@ -803,7 +809,7 @@ mod cli_run { fn check_virtual_dom_server() { let path = file_path_from_root("examples/virtual-dom-wip", "example-server.roc"); 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 @@ -812,7 +818,7 @@ mod cli_run { fn check_virtual_dom_client() { let path = file_path_from_root("examples/virtual-dom-wip", "example-client.roc"); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); - assert!(out.status.success()); + assert_valid_roc_check_status(out.status); } #[test] @@ -821,7 +827,7 @@ mod cli_run { fn cli_countdown_check() { let path = file_path_from_root("crates/cli/tests/cli", "countdown.roc"); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); - assert!(out.status.success()); + assert_valid_roc_check_status(out.status); } #[test] @@ -830,7 +836,7 @@ mod cli_run { fn cli_echo_check() { let path = file_path_from_root("crates/cli/tests/cli", "echo.roc"); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); - assert!(out.status.success()); + assert_valid_roc_check_status(out.status); } #[test] @@ -839,7 +845,7 @@ mod cli_run { fn cli_file_check() { let path = file_path_from_root("crates/cli/tests/cli", "fileBROKEN.roc"); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); - assert!(out.status.success()); + assert_valid_roc_check_status(out.status); } #[test] @@ -848,7 +854,8 @@ mod cli_run { fn cli_form_check() { let path = file_path_from_root("crates/cli/tests/cli", "form.roc"); 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] @@ -857,7 +864,7 @@ mod cli_run { fn cli_http_get_check() { let path = file_path_from_root("crates/cli/tests/cli", "http-get.roc"); let out = run_roc([CMD_CHECK, path.to_str().unwrap()], &[], &[]); - assert!(out.status.success()); + assert_valid_roc_check_status(out.status); } #[test] @@ -1482,9 +1489,9 @@ mod cli_run { Something is off with the body of the main definition: - 6│ main : Str -> Task {} [] - 7│ main = /_ -> - 8│ "this is a string, not a Task {} [] function like the platform expects." + 5│ main : Str -> Task {} [] + 6│ main = /_ -> + 7│ "this is a string, not a Task {} [] function like the platform expects." ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The body is a string of type: diff --git a/crates/cli/tests/known_bad/TypeError.roc b/crates/cli/tests/known_bad/TypeError.roc index 070aca541a..d7509f9e85 100644 --- a/crates/cli/tests/known_bad/TypeError.roc +++ b/crates/cli/tests/known_bad/TypeError.roc @@ -1,8 +1,7 @@ -app "type-error" - packages { pf: "../../../../examples/cli/false-interpreter/platform/main.roc" } - imports [pf.Task.{ Task }] - provides [main] to pf +app [main] { pf: platform "../../../../examples/cli/false-interpreter/platform/main.roc" } + +import pf.Task exposing [Task] main : Str -> Task {} [] main = \_ -> - "this is a string, not a Task {} [] function like the platform expects." \ No newline at end of file + "this is a string, not a Task {} [] function like the platform expects." diff --git a/examples/cli/false-interpreter/platform/Task.roc b/examples/cli/false-interpreter/platform/Task.roc index bdc608f279..033ce78e41 100644 --- a/examples/cli/false-interpreter/platform/Task.roc +++ b/examples/cli/false-interpreter/platform/Task.roc @@ -6,6 +6,7 @@ module [ map, onFail, attempt, + result, fromResult, loop, ] @@ -22,7 +23,7 @@ loop = \state, step -> \res -> when res is Ok (Step newState) -> Step newState - Ok (Done result) -> Done (Ok result) + Ok (Done res2) -> Done (Ok res2) Err e -> Done (Err e) Effect.loop state looper @@ -36,8 +37,8 @@ fail = \val -> Effect.always (Err val) fromResult : Result a e -> Task a e -fromResult = \result -> - when result is +fromResult = \res -> + when res is Ok a -> succeed a Err e -> fail e @@ -45,8 +46,8 @@ attempt : Task a b, (Result a b -> Task c d) -> Task c d attempt = \effect, transform -> Effect.after effect - \result -> - when result is + \res -> + when res is Ok ok -> transform (Ok ok) Err err -> transform (Err err) @@ -54,8 +55,8 @@ await : Task a err, (a -> Task b err) -> Task b err await = \effect, transform -> Effect.after effect - \result -> - when result is + \res -> + when res is Ok a -> transform a Err err -> Task.fail err @@ -63,8 +64,8 @@ onFail : Task ok a, (a -> Task ok b) -> Task ok b onFail = \effect, transform -> Effect.after effect - \result -> - when result is + \res -> + when res is Ok a -> Task.succeed a Err err -> transform err @@ -72,8 +73,8 @@ map : Task a err, (a -> b) -> Task b err map = \effect, transform -> Effect.after effect - \result -> - when result is + \res -> + when res is Ok a -> Task.succeed (transform a) Err err -> Task.fail err @@ -81,4 +82,4 @@ result : Task ok err -> Task (Result ok err) * result = \effect -> Effect.after effect - \result -> Task.succeed result + \res -> Task.succeed res From 7c90a3a9003eac88ca53a184eebab013bba57da6 Mon Sep 17 00:00:00 2001 From: Sam Mohr Date: Fri, 16 Aug 2024 23:51:07 -0700 Subject: [PATCH 3/3] Remove vestigial backpassing test --- .../compiler/test_gen/src/gen_primitives.rs | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index 60bdf275f8..fcbd4c66b9 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -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] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[should_panic(expected = "Shadowing { original_region: @55-56, shadow: @72-73 Ident")]