diff --git a/.github/workflows/test_nightly_many_os.yml b/.github/workflows/test_nightly_many_os.yml index b2fd38e70e..a4b667a815 100644 --- a/.github/workflows/test_nightly_many_os.yml +++ b/.github/workflows/test_nightly_many_os.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ macos-12, macos-13, ubuntu-20.04, ubuntu-22.04 ] + os: [ macos-12, macos-13, ubuntu-20.04, ubuntu-22.04, ubuntu-24.04] runs-on: ${{ matrix.os }} timeout-minutes: 90 steps: diff --git a/Cargo.lock b/Cargo.lock index eb5c398edc..f4299c78b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2771,6 +2771,7 @@ dependencies = [ "roc_derive_key", "roc_error_macros", "roc_late_solve", + "roc_lower_params", "roc_module", "roc_mono", "roc_packaging", @@ -2791,6 +2792,19 @@ dependencies = [ "ven_pretty", ] +[[package]] +name = "roc_lower_params" +version = "0.0.1" +dependencies = [ + "bumpalo", + "roc_can", + "roc_collections", + "roc_module", + "roc_region", + "roc_solve_problem", + "roc_types", +] + [[package]] name = "roc_module" version = "0.0.1" diff --git a/crates/cli/src/format.rs b/crates/cli/src/format.rs index 46282a273e..a57f8eeab8 100644 --- a/crates/cli/src/format.rs +++ b/crates/cli/src/format.rs @@ -263,7 +263,7 @@ mod tests { use std::io::Write; use tempfile::{tempdir, TempDir}; - const FORMATTED_ROC: &str = r#"app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } + const FORMATTED_ROC: &str = r#"app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout import pf.Task @@ -271,7 +271,7 @@ import pf.Task main = Stdout.line! "I'm a Roc application!""#; - const UNFORMATTED_ROC: &str = r#"app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } + const UNFORMATTED_ROC: &str = r#"app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 0804d3a31b..0433651e5d 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1295,8 +1295,8 @@ fn roc_dev_native( break if libc::WIFEXITED(status) { libc::WEXITSTATUS(status) } else { - // we don't have an exit code, so we probably shouldn't make one up - 0 + // we don't have an exit code, but something went wrong if we're in this else + 1 }; } ChildProcessMsg::Expect => { diff --git a/crates/cli/tests/benchmarks/AStar.roc b/crates/cli/tests/benchmarks/AStar.roc index acfaa26e07..29b8a9d974 100644 --- a/crates/cli/tests/benchmarks/AStar.roc +++ b/crates/cli/tests/benchmarks/AStar.roc @@ -1,6 +1,6 @@ -interface AStar - exposes [findPath, Model, initialModel, cheapestOpen, reconstructPath] - imports [Quicksort] +module [findPath, Model, initialModel, cheapestOpen, reconstructPath] + +import Quicksort findPath = \costFn, moveFn, start, end -> astar costFn moveFn end (initialModel start) diff --git a/crates/cli/tests/benchmarks/Base64.roc b/crates/cli/tests/benchmarks/Base64.roc index 6b0698231a..bdf23ea2ec 100644 --- a/crates/cli/tests/benchmarks/Base64.roc +++ b/crates/cli/tests/benchmarks/Base64.roc @@ -1,4 +1,4 @@ -interface Base64 exposes [fromBytes, fromStr, toBytes, toStr] imports [] +module [fromBytes, fromStr, toBytes, toStr] import Base64.Decode import Base64.Encode diff --git a/crates/cli/tests/benchmarks/Base64/Encode.roc b/crates/cli/tests/benchmarks/Base64/Encode.roc index 7f0114b40c..66e195509b 100644 --- a/crates/cli/tests/benchmarks/Base64/Encode.roc +++ b/crates/cli/tests/benchmarks/Base64/Encode.roc @@ -1,7 +1,4 @@ -interface Base64.Encode - exposes [toBytes] - imports [] - +module [toBytes] import Bytes.Encode exposing [ByteEncoder] diff --git a/crates/cli/tests/benchmarks/Bytes/Decode.roc b/crates/cli/tests/benchmarks/Bytes/Decode.roc index 705e9f49a2..79d96828c6 100644 --- a/crates/cli/tests/benchmarks/Bytes/Decode.roc +++ b/crates/cli/tests/benchmarks/Bytes/Decode.roc @@ -1,4 +1,4 @@ -interface Bytes.Decode exposes [ByteDecoder, decode, map, map2, u8, loop, Step, succeed, DecodeProblem, after, map3] imports [] +module [ByteDecoder, decode, map, map2, u8, loop, Step, succeed, DecodeProblem, after, map3] State : { bytes : List U8, cursor : U64 } diff --git a/crates/cli/tests/benchmarks/Bytes/Encode.roc b/crates/cli/tests/benchmarks/Bytes/Encode.roc index c44192e529..2c70aff7fa 100644 --- a/crates/cli/tests/benchmarks/Bytes/Encode.roc +++ b/crates/cli/tests/benchmarks/Bytes/Encode.roc @@ -1,4 +1,4 @@ -interface Bytes.Encode exposes [ByteEncoder, sequence, u8, u16, bytes, empty, encode] imports [] +module [ByteEncoder, sequence, u8, u16, bytes, empty, encode] Endianness : [BE, LE] diff --git a/crates/cli/tests/benchmarks/Issue2279Help.roc b/crates/cli/tests/benchmarks/Issue2279Help.roc index 47e2308fc3..3f33b2ac86 100644 --- a/crates/cli/tests/benchmarks/Issue2279Help.roc +++ b/crates/cli/tests/benchmarks/Issue2279Help.roc @@ -1,6 +1,4 @@ -interface Issue2279Help - exposes [text, asText] - imports [] +module [text, asText] text = "Hello, world!" diff --git a/crates/cli/tests/benchmarks/Quicksort.roc b/crates/cli/tests/benchmarks/Quicksort.roc index f75a523021..5f36d00f92 100644 --- a/crates/cli/tests/benchmarks/Quicksort.roc +++ b/crates/cli/tests/benchmarks/Quicksort.roc @@ -1,4 +1,4 @@ -interface Quicksort exposes [sortBy, sortWith, show] imports [] +module [sortBy, sortWith, show] show : List I64 -> Str show = \list -> diff --git a/crates/cli/tests/benchmarks/cFold.roc b/crates/cli/tests/benchmarks/cFold.roc index d9c5791ece..685827a07c 100644 --- a/crates/cli/tests/benchmarks/cFold.roc +++ b/crates/cli/tests/benchmarks/cFold.roc @@ -1,11 +1,16 @@ app [main] { pf: platform "platform/main.roc" } -import pf.Task +import pf.PlatformTasks # adapted from https://github.com/koka-lang/koka/blob/master/test/bench/haskell/cfold.hs -main : Task.Task {} [] +main : Task {} [] main = - inputResult = Task.getInt |> Task.result! + { value, isError } = PlatformTasks.getInt! + inputResult = + if isError then + Err GetIntError + else + Ok value when inputResult is Ok n -> @@ -17,10 +22,10 @@ main = |> Num.toStr |> Str.concat " & " |> Str.concat (Num.toStr optimized) - |> Task.putLine + |> PlatformTasks.putLine Err GetIntError -> - Task.putLine "Error: Failed to get Integer from stdin." + PlatformTasks.putLine "Error: Failed to get Integer from stdin." Expr : [ Add Expr Expr, diff --git a/crates/cli/tests/benchmarks/closure.roc b/crates/cli/tests/benchmarks/closure.roc index 088a61aa1b..27d98d3d6d 100644 --- a/crates/cli/tests/benchmarks/closure.roc +++ b/crates/cli/tests/benchmarks/closure.roc @@ -1,18 +1,15 @@ -app "closure" - packages { pf: "platform/main.roc" } - imports [pf.Task] - provides [main] to pf +app [main] { pf: platform "platform/main.roc" } # see https://github.com/roc-lang/roc/issues/985 -main : Task.Task {} [] +main : Task {} [] main = closure1 {} # |> Task.after (\_ -> closure2 {}) # |> Task.after (\_ -> closure3 {}) # |> Task.after (\_ -> closure4 {}) # --- -closure1 : {} -> Task.Task {} [] +closure1 : {} -> Task {} [] closure1 = \_ -> - Task.succeed (foo toUnitBorrowed "a long string such that it's malloced") + Task.ok (foo toUnitBorrowed "a long string such that it's malloced") |> Task.map \_ -> {} toUnitBorrowed = \x -> Str.countUtf8Bytes x diff --git a/crates/cli/tests/benchmarks/deriv.roc b/crates/cli/tests/benchmarks/deriv.roc index 2add592670..25806ac9d6 100644 --- a/crates/cli/tests/benchmarks/deriv.roc +++ b/crates/cli/tests/benchmarks/deriv.roc @@ -1,13 +1,18 @@ app [main] { pf: platform "platform/main.roc" } -import pf.Task +import pf.PlatformTasks # based on: https://github.com/koka-lang/koka/blob/master/test/bench/haskell/deriv.hs -IO a : Task.Task a [] +IO a : Task a [] -main : Task.Task {} [] +main : Task {} [] main = - inputResult = Task.getInt |> Task.result! + { value, isError } = PlatformTasks.getInt! + inputResult = + if isError then + Err GetIntError + else + Ok value when inputResult is Ok n -> @@ -21,15 +26,15 @@ main = |> Task.map \_ -> {} Err GetIntError -> - Task.putLine "Error: Failed to get Integer from stdin." + PlatformTasks.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 + 0 -> Task.ok x _ -> - Task.after (f (s - m) x) \w -> - nestHelp s f (m - 1) w + w = f! (s - m) x + nestHelp s f (m - 1) w nest : (I64, Expr -> IO Expr), I64, Expr -> IO Expr nest = \f, n, e -> nestHelp n f n e @@ -162,6 +167,5 @@ deriv = \i, f -> Num.toStr (i + 1) |> Str.concat " count: " |> Str.concat (Num.toStr (count fprime)) - - Task.putLine line - |> Task.after \_ -> Task.succeed fprime + PlatformTasks.putLine! line + Task.ok fprime diff --git a/crates/cli/tests/benchmarks/issue2279.roc b/crates/cli/tests/benchmarks/issue2279.roc index c6d8be7523..1ec76f507a 100644 --- a/crates/cli/tests/benchmarks/issue2279.roc +++ b/crates/cli/tests/benchmarks/issue2279.roc @@ -1,7 +1,7 @@ -app "issue2279" - packages { pf: "platform/main.roc" } - imports [Issue2279Help, pf.Task] - provides [main] to pf +app [main] { pf: platform "platform/main.roc" } + +import Issue2279Help +import pf.PlatformTasks main = text = @@ -10,4 +10,4 @@ main = else Issue2279Help.asText 42 - Task.putLine text + PlatformTasks.putLine text diff --git a/crates/cli/tests/benchmarks/nQueens.roc b/crates/cli/tests/benchmarks/nQueens.roc index 82e331ef47..09eb39fab6 100644 --- a/crates/cli/tests/benchmarks/nQueens.roc +++ b/crates/cli/tests/benchmarks/nQueens.roc @@ -1,19 +1,24 @@ app [main] { pf: platform "platform/main.roc" } -import pf.Task +import pf.PlatformTasks -main : Task.Task {} [] +main : Task {} [] main = - inputResult = Task.getInt |> Task.result! + { value, isError } = PlatformTasks.getInt! + inputResult = + if isError then + Err GetIntError + else + Ok value when inputResult is Ok n -> queens n # original koka 13 |> Num.toStr - |> Task.putLine + |> PlatformTasks.putLine Err GetIntError -> - Task.putLine "Error: Failed to get Integer from stdin." + PlatformTasks.putLine "Error: Failed to get Integer from stdin." ConsList a : [Nil, Cons a (ConsList a)] diff --git a/crates/cli/tests/benchmarks/platform/Effect.roc b/crates/cli/tests/benchmarks/platform/Effect.roc deleted file mode 100644 index a7f749717c..0000000000 --- a/crates/cli/tests/benchmarks/platform/Effect.roc +++ /dev/null @@ -1,10 +0,0 @@ -hosted Effect - exposes [Effect, after, map, always, forever, loop, putLine, putInt, getInt] - imports [] - generates Effect with [after, map, always, forever, loop] - -putLine : Str -> Effect {} - -putInt : I64 -> Effect {} - -getInt : Effect { value : I64, isError : Bool } diff --git a/crates/cli/tests/benchmarks/platform/PlatformTasks.roc b/crates/cli/tests/benchmarks/platform/PlatformTasks.roc new file mode 100644 index 0000000000..81b7acf111 --- /dev/null +++ b/crates/cli/tests/benchmarks/platform/PlatformTasks.roc @@ -0,0 +1,9 @@ +hosted PlatformTasks + exposes [putLine, putInt, getInt] + imports [] + +putLine : Str -> Task {} * + +putInt : I64 -> Task {} * + +getInt : Task { value : I64, isError : Bool } * diff --git a/crates/cli/tests/benchmarks/platform/Task.roc b/crates/cli/tests/benchmarks/platform/Task.roc deleted file mode 100644 index 090710395a..0000000000 --- a/crates/cli/tests/benchmarks/platform/Task.roc +++ /dev/null @@ -1,117 +0,0 @@ -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) - -forever : Task val err -> Task * err -forever = \task -> - looper = \{} -> - task - |> Effect.map - \res -> - when res is - Ok _ -> Step {} - Err e -> Done (Err e) - - Effect.loop {} looper - -loop : state, (state -> Task [Step state, Done done] err) -> Task done err -loop = \state, step -> - looper = \current -> - step current - |> Effect.map - \res -> - when res is - Ok (Step newState) -> Step newState - Ok (Done res2) -> Done (Ok res2) - Err e -> Done (Err e) - - Effect.loop state looper - -succeed : val -> Task val * -succeed = \val -> - Effect.always (Ok val) - -fail : err -> Task * err -fail = \val -> - Effect.always (Err val) - -after : Task a err, (a -> Task b err) -> Task b err -after = \effect, transform -> - Effect.after - effect - \res -> - 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 - Err err -> Task.fail err - -attempt : Task a b, (Result a b -> Task c d) -> Task c d -attempt = \task, transform -> - Effect.after - task - \res -> - when res is - Ok ok -> transform (Ok ok) - Err err -> transform (Err err) - -map : Task a err, (a -> b) -> Task b err -map = \effect, transform -> - Effect.map - effect - \res -> - when res is - Ok a -> Ok (transform a) - 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 = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) - -putInt : I64 -> Task {} * -putInt = \line -> Effect.map (Effect.putInt line) (\_ -> Ok {}) - -getInt : Task I64 [GetIntError] -getInt = - Effect.after - Effect.getInt - \{ isError, value } -> - if - isError - then - # TODO - # when errorCode is - # # A -> Task.fail InvalidCharacter - # # B -> Task.fail IOError - # _ -> - Task.fail GetIntError - else - Task.succeed value diff --git a/crates/cli/tests/benchmarks/platform/main.roc b/crates/cli/tests/benchmarks/platform/main.roc index e4802e2503..a4ac08d384 100644 --- a/crates/cli/tests/benchmarks/platform/main.roc +++ b/crates/cli/tests/benchmarks/platform/main.roc @@ -2,7 +2,7 @@ platform "benchmarks" requires {} { main : Task {} [] } exposes [] packages {} - imports [Task.{ Task }] + imports [] provides [mainForHost] mainForHost : Task {} [] diff --git a/crates/cli/tests/benchmarks/quicksortApp.roc b/crates/cli/tests/benchmarks/quicksortApp.roc index 66b39ab2bf..b94b73ad10 100644 --- a/crates/cli/tests/benchmarks/quicksortApp.roc +++ b/crates/cli/tests/benchmarks/quicksortApp.roc @@ -1,11 +1,16 @@ app [main] { pf: platform "platform/main.roc" } -import pf.Task +import pf.PlatformTasks import Quicksort main : Task.Task {} [] main = - inputResult = Task.getInt |> Task.result! + { value, isError } = PlatformTasks.getInt! + inputResult = + if isError then + Err GetIntError + else + Ok value when inputResult is Ok n -> @@ -19,10 +24,10 @@ main = sort unsortedList |> Quicksort.show - |> Task.putLine + |> PlatformTasks.putLine Err GetIntError -> - Task.putLine "Error: Failed to get Integer from stdin." + PlatformTasks.putLine "Error: Failed to get Integer from stdin." sort : List I64 -> List I64 sort = \list -> diff --git a/crates/cli/tests/benchmarks/rBTreeCk.roc b/crates/cli/tests/benchmarks/rBTreeCk.roc index 0d7f1c5232..8a8076690d 100644 --- a/crates/cli/tests/benchmarks/rBTreeCk.roc +++ b/crates/cli/tests/benchmarks/rBTreeCk.roc @@ -1,6 +1,6 @@ app [main] { pf: platform "platform/main.roc" } -import pf.Task +import pf.PlatformTasks Color : [Red, Black] @@ -37,9 +37,14 @@ fold = \f, tree, b -> Leaf -> b Node _ l k v r -> fold f r (f k v (fold f l b)) -main : Task.Task {} [] +main : Task {} [] main = - inputResult = Task.getInt |> Task.result! + { value, isError } = PlatformTasks.getInt! + inputResult = + if isError then + Err GetIntError + else + Ok value when inputResult is Ok n -> @@ -53,13 +58,13 @@ main = val |> Num.toStr - |> Task.putLine + |> PlatformTasks.putLine Nil -> - Task.putLine "fail" + PlatformTasks.putLine "fail" Err GetIntError -> - Task.putLine "Error: Failed to get Integer from stdin." + PlatformTasks.putLine "Error: Failed to get Integer from stdin." insert : Tree (Num k) v, Num k, v -> Tree (Num k) v insert = \t, k, v -> if isRed t then setBlack (ins t k v) else ins t k v diff --git a/crates/cli/tests/benchmarks/rBTreeDel.roc b/crates/cli/tests/benchmarks/rBTreeDel.roc index 82b333c488..9a55fcf03c 100644 --- a/crates/cli/tests/benchmarks/rBTreeDel.roc +++ b/crates/cli/tests/benchmarks/rBTreeDel.roc @@ -1,6 +1,6 @@ app [main] { pf: platform "platform/main.roc" } -import pf.Task +import pf.PlatformTasks Color : [Red, Black] @@ -10,9 +10,14 @@ Map : Tree I64 Bool ConsList a : [Nil, Cons a (ConsList a)] -main : Task.Task {} [] +main : Task {} [] main = - inputResult = Task.getInt |> Task.result! + { value, isError } = PlatformTasks.getInt! + inputResult = + if isError then + Err GetIntError + else + Ok value when inputResult is Ok n -> @@ -21,10 +26,10 @@ main = val |> Num.toStr - |> Task.putLine + |> PlatformTasks.putLine Err GetIntError -> - Task.putLine "Error: Failed to get Integer from stdin." + PlatformTasks.putLine "Error: Failed to get Integer from stdin." boom : Str -> a boom = \_ -> boom "" diff --git a/crates/cli/tests/benchmarks/rBTreeInsert.roc b/crates/cli/tests/benchmarks/rBTreeInsert.roc index 1a506b5c49..e419d8ff94 100644 --- a/crates/cli/tests/benchmarks/rBTreeInsert.roc +++ b/crates/cli/tests/benchmarks/rBTreeInsert.roc @@ -1,16 +1,15 @@ -app "rbtree-insert" - packages { pf: "platform/main.roc" } - imports [pf.Task] - provides [main] to pf +app [main] { pf: platform "platform/main.roc" } -main : Task.Task {} [] +import pf.PlatformTasks + +main : Task {} [] main = tree : RedBlackTree I64 {} tree = insert 0 {} Empty tree |> show - |> Task.putLine + |> PlatformTasks.putLine show : RedBlackTree I64 {} -> Str show = \tree -> showRBTree tree Num.toStr (\{} -> "{}") diff --git a/crates/cli/tests/benchmarks/testAStar.roc b/crates/cli/tests/benchmarks/testAStar.roc index 98ab543c5c..c6ab5b962f 100644 --- a/crates/cli/tests/benchmarks/testAStar.roc +++ b/crates/cli/tests/benchmarks/testAStar.roc @@ -1,20 +1,12 @@ -app "test-astar" - packages { pf: "platform/main.roc" } - imports [pf.Task, AStar] - provides [main] to pf +app [main] { pf: platform "platform/main.roc" } -main : Task.Task {} [] +import pf.PlatformTasks +import AStar + +#main : Task {} * main = - Task.putLine (showBool test1) + PlatformTasks.putLine! (showBool test1) -# Task.after Task.getInt \n -> -# when n is -# 1 -> -# Task.putLine (showBool test1) -# -# _ -> -# ns = Num.toStr n -# Task.putLine "No test $(ns)" showBool : Bool -> Str showBool = \b -> if diff --git a/crates/cli/tests/benchmarks/testBase64.roc b/crates/cli/tests/benchmarks/testBase64.roc index 9916451731..c93ed7748d 100644 --- a/crates/cli/tests/benchmarks/testBase64.roc +++ b/crates/cli/tests/benchmarks/testBase64.roc @@ -1,18 +1,17 @@ -app "test-base64" - packages { pf: "platform/main.roc" } - imports [pf.Task, Base64] - provides [main] to pf +app [main] { pf: platform "platform/main.roc" } -IO a : Task.Task a [] +import Base64 +import pf.PlatformTasks + +IO a : Task a [] main : IO {} main = when Base64.fromBytes (Str.toUtf8 "Hello World") is - Err _ -> Task.putLine "sadness" + Err _ -> PlatformTasks.putLine "sadness" Ok encoded -> - Task.after - (Task.putLine (Str.concat "encoded: " encoded)) - \_ -> - when Base64.toStr encoded is - Ok decoded -> Task.putLine (Str.concat "decoded: " decoded) - Err _ -> Task.putLine "sadness" + PlatformTasks.putLine! (Str.concat "encoded: " encoded) + + when Base64.toStr encoded is + Ok decoded -> PlatformTasks.putLine (Str.concat "decoded: " decoded) + Err _ -> PlatformTasks.putLine "sadness" diff --git a/crates/cli/tests/cli/combine-tasks.roc b/crates/cli/tests/cli/combine-tasks.roc index 2d3e46a3ea..623c88b1f9 100644 --- a/crates/cli/tests/cli/combine-tasks.roc +++ b/crates/cli/tests/cli/combine-tasks.roc @@ -1,7 +1,6 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout -import pf.Task exposing [Task] main = multipleIn = diff --git a/crates/cli/tests/cli/countdown.roc b/crates/cli/tests/cli/countdown.roc index cd4a5b9000..be8cd47a02 100644 --- a/crates/cli/tests/cli/countdown.roc +++ b/crates/cli/tests/cli/countdown.roc @@ -1,13 +1,12 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdin import pf.Stdout -import pf.Task exposing [Task, loop] main = Stdout.line! "\nLet's count down from 3 together - all you have to do is press ." _ = Stdin.line! - loop 3 tick + Task.loop 3 tick tick = \n -> if n == 0 then diff --git a/crates/cli/tests/cli/echo.roc b/crates/cli/tests/cli/echo.roc index f30f2df3e3..bb5d77174c 100644 --- a/crates/cli/tests/cli/echo.roc +++ b/crates/cli/tests/cli/echo.roc @@ -1,8 +1,7 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdin import pf.Stdout -import pf.Task exposing [Task] main = Stdout.line! "πŸ—£ Shout into this cave and hear the echo! πŸ‘‚πŸ‘‚πŸ‘‚" diff --git a/crates/cli/tests/cli/env.roc b/crates/cli/tests/cli/env.roc index b99e774565..d90b31f99a 100644 --- a/crates/cli/tests/cli/env.roc +++ b/crates/cli/tests/cli/env.roc @@ -1,9 +1,8 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout import pf.Stderr import pf.Env -import pf.Task exposing [Task] main = task = diff --git a/crates/cli/tests/cli/fileBROKEN.roc b/crates/cli/tests/cli/fileBROKEN.roc index 8d006cc3b0..d4e1971275 100644 --- a/crates/cli/tests/cli/fileBROKEN.roc +++ b/crates/cli/tests/cli/fileBROKEN.roc @@ -1,7 +1,6 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout -import pf.Task exposing [Task] import pf.File import pf.Path import pf.Env diff --git a/crates/cli/tests/cli/form.roc b/crates/cli/tests/cli/form.roc index 11b020af68..0ef2b1d769 100644 --- a/crates/cli/tests/cli/form.roc +++ b/crates/cli/tests/cli/form.roc @@ -1,8 +1,7 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdin import pf.Stdout -import pf.Task exposing [await, Task] main = Stdout.line! "What's your first name?" diff --git a/crates/cli/tests/cli/http-get.roc b/crates/cli/tests/cli/http-get.roc index 615f327ed5..3a0501974d 100644 --- a/crates/cli/tests/cli/http-get.roc +++ b/crates/cli/tests/cli/http-get.roc @@ -1,7 +1,6 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Http -import pf.Task exposing [Task] import pf.Stdout main = diff --git a/crates/cli/tests/cli/ingested-file-bytes-no-ann.roc b/crates/cli/tests/cli/ingested-file-bytes-no-ann.roc index f4b23a22a1..e670c6e47d 100644 --- a/crates/cli/tests/cli/ingested-file-bytes-no-ann.roc +++ b/crates/cli/tests/cli/ingested-file-bytes-no-ann.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout import "test-file.txt" as testFile diff --git a/crates/cli/tests/cli/ingested-file-bytes.roc b/crates/cli/tests/cli/ingested-file-bytes.roc index dbbdec680d..c29097bef8 100644 --- a/crates/cli/tests/cli/ingested-file-bytes.roc +++ b/crates/cli/tests/cli/ingested-file-bytes.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout import "test-file.txt" as testFile : _ # the _ is optional diff --git a/crates/cli/tests/cli/ingested-file.roc b/crates/cli/tests/cli/ingested-file.roc index cd0b947154..09d00354f0 100644 --- a/crates/cli/tests/cli/ingested-file.roc +++ b/crates/cli/tests/cli/ingested-file.roc @@ -1,4 +1,4 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout import "ingested-file.roc" as ownCode : Str diff --git a/crates/cli/tests/cli/parse-args.roc b/crates/cli/tests/cli/parse-args.roc index a1ffe4c868..92bc914ee6 100644 --- a/crates/cli/tests/cli/parse-args.roc +++ b/crates/cli/tests/cli/parse-args.roc @@ -1,9 +1,8 @@ app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br", + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", } import pf.Stdout -import pf.Task exposing [Task] main = file = strParam { name: "file" } diff --git a/crates/cli/tests/cli/parser-letter-counts.roc b/crates/cli/tests/cli/parser-letter-counts.roc index aa40b5d358..d6925fa7a1 100644 --- a/crates/cli/tests/cli/parser-letter-counts.roc +++ b/crates/cli/tests/cli/parser-letter-counts.roc @@ -1,5 +1,5 @@ app [main] { - cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br", + cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.5.2/9VrPjwfQQ1QeSL3CfmWr2Pr9DESdDIXy97pwpuq84Ck.tar.br", } diff --git a/crates/cli/tests/cli/parser-movies-csv.roc b/crates/cli/tests/cli/parser-movies-csv.roc index ffd7f4e5da..12a011c647 100644 --- a/crates/cli/tests/cli/parser-movies-csv.roc +++ b/crates/cli/tests/cli/parser-movies-csv.roc @@ -1,11 +1,10 @@ app [main] { - pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br", + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", parser: "https://github.com/lukewilliamboswell/roc-parser/releases/download/0.5.2/9VrPjwfQQ1QeSL3CfmWr2Pr9DESdDIXy97pwpuq84Ck.tar.br", } import pf.Stdout import pf.Stderr -import pf.Task exposing [Task] import parser.Core exposing [map, keep] import parser.String exposing [strFromUtf8] import parser.CSV diff --git a/crates/cli/tests/cli_run.rs b/crates/cli/tests/cli_run.rs index 62f77a7a05..1544a88887 100644 --- a/crates/cli/tests/cli_run.rs +++ b/crates/cli/tests/cli_run.rs @@ -10,8 +10,8 @@ extern crate roc_module; #[cfg(test)] mod cli_run { use cli_utils::helpers::{ - extract_valgrind_errors, file_path_from_root, fixture_file, fixtures_dir, has_error, - known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError, + cli_testing_dir, extract_valgrind_errors, file_path_from_root, fixture_file, fixtures_dir, + has_error, known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError, ValgrindErrorXWhat, }; use const_format::concatcp; @@ -86,11 +86,11 @@ mod cli_run { } fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { - let compile_out = run_roc( - [CMD_CHECK, file.to_str().unwrap()].iter().chain(flags), - &[], - &[], - ); + check_compile_error_with(CMD_CHECK, file, flags, expected); + } + + fn check_compile_error_with(cmd: &str, file: &Path, flags: &[&str], expected: &str) { + let compile_out = run_roc([cmd, file.to_str().unwrap()].iter().chain(flags), &[], &[]); let err = compile_out.stdout.trim(); let err = strip_colors(err); @@ -728,6 +728,200 @@ mod cli_run { ) } + #[test] + #[cfg_attr(windows, ignore)] + fn module_params() { + test_roc_app( + "crates/cli/tests/module_params", + "app.roc", + &[], + &[], + &[], + indoc!( + r#" + App1.baseUrl: https://api.example.com/one + App2.baseUrl: http://api.example.com/two + App3.baseUrl: https://api.example.com/three + App1.getUser 1: https://api.example.com/one/users/1 + App2.getUser 2: http://api.example.com/two/users/2 + App3.getUser 3: https://api.example.com/three/users/3 + App1.getPost 1: https://api.example.com/one/posts/1 + App2.getPost 2: http://api.example.com/two/posts/2 + App3.getPost 3: https://api.example.com/three/posts/3 + App1.getPosts [1, 2]: ["https://api.example.com/one/posts/1", "https://api.example.com/one/posts/2"] + App2.getPosts [3, 4]: ["http://api.example.com/two/posts/3", "http://api.example.com/two/posts/4"] + App2.getPosts [5, 6]: ["http://api.example.com/two/posts/5", "http://api.example.com/two/posts/6"] + App1.getPostComments 1: https://api.example.com/one/posts/1/comments + App2.getPostComments 2: http://api.example.com/two/posts/2/comments + App2.getPostComments 3: http://api.example.com/two/posts/3/comments + App1.getCompanies [1, 2]: ["https://api.example.com/one/companies/1", "https://api.example.com/one/companies/2"] + App2.getCompanies [3, 4]: ["http://api.example.com/two/companies/3", "http://api.example.com/two/companies/4"] + App2.getCompanies [5, 6]: ["http://api.example.com/two/companies/5", "http://api.example.com/two/companies/6"] + App1.getPostAliased 1: https://api.example.com/one/posts/1 + App2.getPostAliased 2: http://api.example.com/two/posts/2 + App3.getPostAliased 3: https://api.example.com/three/posts/3 + App1.baseUrlAliased: https://api.example.com/one + App2.baseUrlAliased: http://api.example.com/two + App3.baseUrlAliased: https://api.example.com/three + App1.getUserSafe 1: https://api.example.com/one/users/1 + Prod.getUserSafe 2: http://api.example.com/prod_1/users/2?safe=true + usersApp1: ["https://api.example.com/one/users/1", "https://api.example.com/one/users/2", "https://api.example.com/one/users/3"] + getUserApp3Nested 3: https://api.example.com/three/users/3 + usersApp3Passed: ["https://api.example.com/three/users/1", "https://api.example.com/three/users/2", "https://api.example.com/three/users/3"] + "# + ), + UseValgrind::No, + TestCliCommands::Run, + ); + } + + #[test] + #[cfg_attr(windows, ignore)] + fn module_params_arity_mismatch() { + check_compile_error_with( + CMD_DEV, + &cli_testing_dir("/module_params/arity_mismatch.roc"), + &[], + indoc!( + r#" + ── TOO MANY ARGS in tests/module_params/arity_mismatch.roc ───────────────────── + + The getUser function expects 1 argument, but it got 2 instead: + + 12β”‚ $(Api.getUser 1 2) + ^^^^^^^^^^^ + + Are there any missing commas? Or missing parentheses? + + + ── TOO MANY ARGS in tests/module_params/arity_mismatch.roc ───────────────────── + + This value is not a function, but it was given 1 argument: + + 13β”‚ $(Api.baseUrl 1) + ^^^^^^^^^^^ + + Are there any missing commas? Or missing parentheses? + + + ── TOO FEW ARGS in tests/module_params/arity_mismatch.roc ────────────────────── + + The getPostComment function expects 2 arguments, but it got only 1: + + 16β”‚ $(Api.getPostComment 1) + ^^^^^^^^^^^^^^^^^^ + + Roc does not allow functions to be partially applied. Use a closure to + make partial application explicit. + + ──────────────────────────────────────────────────────────────────────────────── + + 3 errors and 0 warnings found in ms."# + ), + ); + } + + #[test] + #[cfg_attr(windows, ignore)] + fn module_params_unexpected_fn() { + check_compile_error_with( + CMD_DEV, + &cli_testing_dir("/module_params/unexpected_fn.roc"), + &[], + indoc!( + r#" + ── TYPE MISMATCH in tests/module_params/unexpected_fn.roc ────────────────────── + + This argument to this string interpolation has an unexpected type: + + 11β”‚ $(Api.getPost) + ^^^^^^^^^^^ + + The argument is an anonymous function of type: + + U32 -> Str + + But this string interpolation needs its argument to be: + + Str + + ──────────────────────────────────────────────────────────────────────────────── + + 1 error and 0 warnings found in ms."# + ), + ) + } + + #[test] + #[cfg_attr(windows, ignore)] + fn module_params_bad_ann() { + check_compile_error_with( + CMD_DEV, + &cli_testing_dir("/module_params/bad_ann.roc"), + &[], + indoc!( + r#" + ── TYPE MISMATCH in tests/module_params/BadAnn.roc ───────────────────────────── + + Something is off with the body of the fnAnnotatedAsValue definition: + + 3β”‚ fnAnnotatedAsValue : Str + 4β”‚> fnAnnotatedAsValue = /postId, commentId -> + 5β”‚> "/posts/$(postId)/comments/$(Num.toStr commentId)" + + The body is an anonymous function of type: + + Str, Num * -> Str + + But the type annotation on fnAnnotatedAsValue says it should be: + + Str + + + ── TYPE MISMATCH in tests/module_params/BadAnn.roc ───────────────────────────── + + Something is off with the body of the missingArg definition: + + 7β”‚ missingArg : Str -> Str + 8β”‚> missingArg = /postId, _ -> + 9β”‚> "/posts/$(postId)/comments" + + The body is an anonymous function of type: + + (Str, ? -> Str) + + But the type annotation on missingArg says it should be: + + (Str -> Str) + + Tip: It looks like it takes too many arguments. I'm seeing 1 extra. + + ──────────────────────────────────────────────────────────────────────────────── + + 2 errors and 1 warning found in ms."# + ), + ); + } + + #[test] + #[cfg_attr(windows, ignore)] + fn module_params_pass_task() { + test_roc_app( + "crates/cli/tests/module_params", + "pass_task.roc", + &[], + &[], + &[], + indoc!( + r#" + Hi, Agus! + "# + ), + UseValgrind::No, + TestCliCommands::Run, + ); + } + #[test] #[cfg_attr(windows, ignore)] fn transitive_expects() { @@ -898,8 +1092,8 @@ mod cli_run { ) } + #[ignore = "likely broken because of alias analysis: https://github.com/roc-lang/roc/issues/6544"] #[test] - #[cfg_attr(target_os = "windows", ignore = "Segfault")] fn false_interpreter() { test_roc_app( "examples/cli/false-interpreter", @@ -1489,9 +1683,9 @@ mod cli_run { Something is off with the body of the main definition: - 5β”‚ main : Str -> Task {} [] - 6β”‚ main = /_ -> - 7β”‚ "this is a string, not a Task {} [] function like the platform expects." + 3β”‚ main : Str -> Task {} [] + 4β”‚ main = /_ -> + 5β”‚ "this is a string, not a Task {} [] function like the platform expects." ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The body is a string of type: @@ -1500,7 +1694,7 @@ mod cli_run { But the type annotation on main says it should be: - Effect.Effect (Result {} []) + Task {} [] Tip: Add type annotations to functions or values to help you figure this out. @@ -1579,30 +1773,6 @@ mod cli_run { ); } - #[test] - fn unknown_generates_with() { - check_compile_error( - &known_bad_file("UnknownGeneratesWith.roc"), - &[], - indoc!( - r#" - ── UNKNOWN GENERATES FUNCTION in tests/known_bad/UnknownGeneratesWith.roc ────── - - I don't know how to generate the foobar function. - - 4β”‚ generates Effect with [after, map, always, foobar] - ^^^^^^ - - Only specific functions like `after` and `map` can be generated.Learn - more about hosted modules at TODO. - - ──────────────────────────────────────────────────────────────────────────────── - - 1 error and 0 warnings found in ms."# - ), - ); - } - #[test] fn format_check_good() { check_format_check_as_expected(&fixture_file("format", "Formatted.roc"), true); diff --git a/crates/cli/tests/known_bad/TypeError.roc b/crates/cli/tests/known_bad/TypeError.roc index d7509f9e85..168a1aa77b 100644 --- a/crates/cli/tests/known_bad/TypeError.roc +++ b/crates/cli/tests/known_bad/TypeError.roc @@ -1,7 +1,5 @@ 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." diff --git a/crates/cli/tests/known_bad/UnknownGeneratesWith.roc b/crates/cli/tests/known_bad/UnknownGeneratesWith.roc deleted file mode 100644 index aeab642de2..0000000000 --- a/crates/cli/tests/known_bad/UnknownGeneratesWith.roc +++ /dev/null @@ -1,4 +0,0 @@ -hosted UnknownGeneratesWith - exposes [Effect, after, map, always] - imports [] - generates Effect with [after, map, always, foobar] diff --git a/crates/cli/tests/module_params/Api.roc b/crates/cli/tests/module_params/Api.roc new file mode 100644 index 0000000000..cd3a7792d0 --- /dev/null +++ b/crates/cli/tests/module_params/Api.roc @@ -0,0 +1,69 @@ +module { appId, protocol } -> [ + baseUrl, + getUser, + getPost, + getPosts, + getPostComments, + getCompanies, + baseUrlAliased, + getPostAliased, + getUserSafe, + getPostComment, +] + +## value def referencing params +baseUrl : Str +baseUrl = + protocol "api.example.com/$(appId)" + +## function def referencing params +getUser : U32 -> Str +getUser = \userId -> + # purposefully not using baseUrl to test top-level fn referencing param + protocol "api.example.com/$(appId)/users/$(Num.toStr userId)" + +## function def referencing top-level value +getPost : U32 -> Str +getPost = \postId -> + "$(baseUrl)/posts/$(Num.toStr postId)" + +## function def passing top-level function +getPosts : List U32 -> List Str +getPosts = \ids -> + List.map ids getPost + +## function def calling top-level function +getPostComments : U32 -> Str +getPostComments = \postId -> + "$(getPost postId)/comments" + +## function def passing nested function +getCompanies : List U32 -> List Str +getCompanies = \ids -> + getCompany = \id -> + protocol "api.example.com/$(appId)/companies/$(Num.toStr id)" + + List.map ids getCompany + +## aliasing top-level value +baseUrlAliased : Str +baseUrlAliased = + baseUrl + +## aliasing top-level fn +getPostAliased : U32 -> Str +getPostAliased = + getPost + +## top-level value returning functions +getUserSafe : U32 -> Str +getUserSafe = + if Str.startsWith appId "prod_" then + \id -> "$(getUser id)?safe=true" + else + getUser + +## two-argument function +getPostComment : U32, U32 -> Str +getPostComment = \postId, commentId -> + "$(getPost postId)/comments/$(Num.toStr commentId)" diff --git a/crates/cli/tests/module_params/BadAnn.roc b/crates/cli/tests/module_params/BadAnn.roc new file mode 100644 index 0000000000..2ccfce872e --- /dev/null +++ b/crates/cli/tests/module_params/BadAnn.roc @@ -0,0 +1,9 @@ +module { appId } -> [fnAnnotatedAsValue, missingArg] + +fnAnnotatedAsValue : Str +fnAnnotatedAsValue = \postId, commentId -> + "/posts/$(postId)/comments/$(Num.toStr commentId)" + +missingArg : Str -> Str +missingArg = \postId, _ -> + "/posts/$(postId)/comments" diff --git a/crates/cli/tests/module_params/Menu.roc b/crates/cli/tests/module_params/Menu.roc new file mode 100644 index 0000000000..e16090caf5 --- /dev/null +++ b/crates/cli/tests/module_params/Menu.roc @@ -0,0 +1,7 @@ +module { echo } -> [menu] + +menu = \name -> + indirect name + +indirect = \name -> + echo "Hi, $(name)!" diff --git a/crates/cli/tests/module_params/app.roc b/crates/cli/tests/module_params/app.roc new file mode 100644 index 0000000000..982fe44d08 --- /dev/null +++ b/crates/cli/tests/module_params/app.roc @@ -0,0 +1,59 @@ +app [main] { + pf: platform "../fixtures/multi-dep-str/platform/main.roc", +} + +import Api { appId: "one", protocol: https } as App1 +import Api { appId: "two", protocol: http } as App2 +import Api { appId: "prod_1", protocol: http } as Prod + +https = \url -> "https://$(url)" +http = \url -> "http://$(url)" + +usersApp1 = + # pass top-level fn in a module with params + List.map [1, 2, 3] App1.getUser + +main = + app3Id = "three" + + import Api { appId: app3Id, protocol: https } as App3 + + getUserApp3Nested = \userId -> + # use captured params def + App3.getUser userId + + usersApp3Passed = + # pass top-level fn in a nested def + List.map [1, 2, 3] App3.getUser + + """ + App1.baseUrl: $(App1.baseUrl) + App2.baseUrl: $(App2.baseUrl) + App3.baseUrl: $(App3.baseUrl) + App1.getUser 1: $(App1.getUser 1) + App2.getUser 2: $(App2.getUser 2) + App3.getUser 3: $(App3.getUser 3) + App1.getPost 1: $(App1.getPost 1) + App2.getPost 2: $(App2.getPost 2) + App3.getPost 3: $(App3.getPost 3) + App1.getPosts [1, 2]: $(Inspect.toStr (App1.getPosts [1, 2])) + App2.getPosts [3, 4]: $(Inspect.toStr (App2.getPosts [3, 4])) + App2.getPosts [5, 6]: $(Inspect.toStr (App2.getPosts [5, 6])) + App1.getPostComments 1: $(App1.getPostComments 1) + App2.getPostComments 2: $(App2.getPostComments 2) + App2.getPostComments 3: $(App2.getPostComments 3) + App1.getCompanies [1, 2]: $(Inspect.toStr (App1.getCompanies [1, 2])) + App2.getCompanies [3, 4]: $(Inspect.toStr (App2.getCompanies [3, 4])) + App2.getCompanies [5, 6]: $(Inspect.toStr (App2.getCompanies [5, 6])) + App1.getPostAliased 1: $(App1.getPostAliased 1) + App2.getPostAliased 2: $(App2.getPostAliased 2) + App3.getPostAliased 3: $(App3.getPostAliased 3) + App1.baseUrlAliased: $(App1.baseUrlAliased) + App2.baseUrlAliased: $(App2.baseUrlAliased) + App3.baseUrlAliased: $(App3.baseUrlAliased) + App1.getUserSafe 1: $(App1.getUserSafe 1) + Prod.getUserSafe 2: $(Prod.getUserSafe 2) + usersApp1: $(Inspect.toStr usersApp1) + getUserApp3Nested 3: $(getUserApp3Nested 3) + usersApp3Passed: $(Inspect.toStr usersApp3Passed) + """ diff --git a/crates/cli/tests/module_params/arity_mismatch.roc b/crates/cli/tests/module_params/arity_mismatch.roc new file mode 100644 index 0000000000..de2fe17965 --- /dev/null +++ b/crates/cli/tests/module_params/arity_mismatch.roc @@ -0,0 +1,17 @@ +app [main] { + pf: platform "../fixtures/multi-dep-str/platform/main.roc", +} + +import Api { appId: "one", protocol: https } + +https = \url -> "https://$(url)" + +main = + """ + # too many args + $(Api.getUser 1 2) + $(Api.baseUrl 1) + + # too few args + $(Api.getPostComment 1) + """ diff --git a/crates/cli/tests/module_params/bad_ann.roc b/crates/cli/tests/module_params/bad_ann.roc new file mode 100644 index 0000000000..fc549e362a --- /dev/null +++ b/crates/cli/tests/module_params/bad_ann.roc @@ -0,0 +1,8 @@ +app [main] { + pf: platform "../fixtures/multi-dep-str/platform/main.roc", +} + +import BadAnn { appId: "one" } + +main = + "" diff --git a/crates/cli/tests/module_params/pass_task.roc b/crates/cli/tests/module_params/pass_task.roc new file mode 100644 index 0000000000..7140af55e8 --- /dev/null +++ b/crates/cli/tests/module_params/pass_task.roc @@ -0,0 +1,7 @@ +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } + +import pf.Stdout +import Menu { echo: Stdout.line } + +main = + Menu.menu "Agus" diff --git a/crates/cli/tests/module_params/unexpected_fn.roc b/crates/cli/tests/module_params/unexpected_fn.roc new file mode 100644 index 0000000000..f296eed5de --- /dev/null +++ b/crates/cli/tests/module_params/unexpected_fn.roc @@ -0,0 +1,12 @@ +app [main] { + pf: platform "../fixtures/multi-dep-str/platform/main.roc", +} + +import Api { appId: "one", protocol: https } + +https = \url -> "https://$(url)" + +main = + """ + $(Api.getPost) + """ diff --git a/crates/compiler/builtins/roc/Task.roc b/crates/compiler/builtins/roc/Task.roc new file mode 100644 index 0000000000..4a904c6a7d --- /dev/null +++ b/crates/compiler/builtins/roc/Task.roc @@ -0,0 +1,265 @@ +module [ + Task, + ok, + err, + await, + map, + mapErr, + onErr, + attempt, + forever, + loop, + fromResult, + batch, + sequence, + forEach, + result, +] + +import List +import Result exposing [Result] + +## A Task represents an effect; an interaction with state outside your Roc +## program, such as the terminal's standard output, or a file. +Task ok err := {} -> Result ok err + +## Run a task repeatedly, until it fails with `err`. Note that this task does not return a success value. +forever : Task a err -> Task * err +forever = \@Task task -> + looper = \{} -> + when task {} is + Err e -> Err e + Ok _ -> looper {} + + @Task \{} -> looper {} + +## Run a task repeatedly, until it fails with `err` or completes with `done`. +## +## ``` +## sum = +## Task.loop! 0 \total -> +## numResult = +## Stdin.line +## |> Task.result! +## |> Result.try Str.toU64 +## +## when numResult is +## Ok num -> Task.ok (Step (total + num)) +## Err (StdinErr EndOfFile) -> Task.ok (Done total) +## Err InvalidNumStr -> Task.err NonNumberGiven +## ``` +loop : state, (state -> Task [Step state, Done done] err) -> Task done err +loop = \state, step -> + looper = \current -> + (@Task next) = step current + when next {} is + Err e -> Err e + Ok (Done newResult) -> Ok newResult + Ok (Step newState) -> looper (newState) + + @Task \{} -> looper state + +## Create a task that always succeeds with the value provided. +## +## ``` +## # Always succeeds with "Louis" +## getName : Task.Task Str * +## getName = Task.ok "Louis" +## ``` +## +ok : a -> Task a * +ok = \a -> @Task \{} -> Ok a + +## Create a task that always fails with the error provided. +## +## ``` +## # Always fails with the tag `CustomError Str` +## customError : Str -> Task.Task {} [CustomError Str] +## customError = \err -> Task.err (CustomError err) +## ``` +## +err : a -> Task * a +err = \a -> @Task \{} -> Err a + +## Transform a given Task with a function that handles the success or error case +## and returns another task based on that. This is useful for chaining tasks +## together or performing error handling and recovery. +## +## Consider the following task: +## +## `canFail : Task {} [Failure, AnotherFail, YetAnotherFail]` +## +## We can use [attempt] to handle the failure cases using the following: +## +## ``` +## Task.attempt canFail \result -> +## when result is +## Ok Success -> Stdout.line "Success!" +## Err Failure -> Stdout.line "Oops, failed!" +## Err AnotherFail -> Stdout.line "Ooooops, another failure!" +## Err YetAnotherFail -> Stdout.line "Really big oooooops, yet again!" +## ``` +## +## Here we know that the `canFail` task may fail, and so we use +## `Task.attempt` to convert the task to a `Result` and then use pattern +## matching to handle the success and possible failure cases. +attempt : Task a b, (Result a b -> Task c d) -> Task c d +attempt = \@Task task, transform -> + @Task \{} -> + (@Task transformed) = transform (task {}) + + transformed {} + +## Take the success value from a given [Task] and use that to generate a new [Task]. +## +## We can [await] Task results with callbacks: +## +## ``` +## Task.await (Stdin.line "What's your name?") \name -> +## Stdout.line "Your name is: $(name)" +## ``` +## +## Or we can more succinctly use the `!` bang operator, which desugars to [await]: +## +## ``` +## name = Stdin.line! "What's your name?" +## Stdout.line "Your name is: $(name)" +## ``` +await : Task a b, (a -> Task c b) -> Task c b +await = \@Task task, transform -> + @Task \{} -> + when task {} is + Ok a -> + (@Task transformed) = transform a + transformed {} + + Err b -> + Err b + +## Take the error value from a given [Task] and use that to generate a new [Task]. +## +## ``` +## # Prints "Something went wrong!" to standard error if `canFail` fails. +## canFail +## |> Task.onErr \_ -> Stderr.line "Something went wrong!" +## ``` +onErr : Task a b, (b -> Task a c) -> Task a c +onErr = \@Task task, transform -> + @Task \{} -> + when task {} is + Ok a -> + Ok a + + Err b -> + (@Task transformed) = transform b + transformed {} + +## Transform the success value of a given [Task] with a given function. +## +## ``` +## # Succeeds with a value of "Bonjour Louis!" +## Task.ok "Louis" +## |> Task.map (\name -> "Bonjour $(name)!") +## ``` +map : Task a c, (a -> b) -> Task b c +map = \@Task task, transform -> + @Task \{} -> + when task {} is + Ok a -> Ok (transform a) + Err b -> Err b + +## Transform the error value of a given [Task] with a given function. +## +## ``` +## # Ignore the fail value, and map it to the tag `CustomError` +## canFail +## |> Task.mapErr \_ -> CustomError +## ``` +mapErr : Task c a, (a -> b) -> Task c b +mapErr = \@Task task, transform -> + @Task \{} -> + when task {} is + Ok a -> Ok a + Err b -> Err (transform b) + +## Use a Result among other Tasks by converting it into a [Task]. +fromResult : Result a b -> Task a b +fromResult = \res -> + @Task \{} -> res + +## Apply a task to another task applicatively. This can be used with +## [ok] to build a [Task] that returns a record. +## +## The following example returns a Record with two fields, `apples` and +## `oranges`, each of which is a `List Str`. If it fails it returns the tag +## `NoFruitAvailable`. +## +## ``` +## getFruitBasket : Task { apples : List Str, oranges : List Str } [NoFruitAvailable] +## getFruitBasket = Task.ok { +## apples: <- getFruit Apples |> Task.batch, +## oranges: <- getFruit Oranges |> Task.batch, +## } +## ``` +batch : Task a c -> (Task (a -> b) c -> Task b c) +batch = \current -> + \next -> + await next \f -> + map current f + +## Apply each task in a list sequentially, and return a list of the resulting values. +## Each task will be awaited before beginning the next task. +## +## ``` +## fetchAuthorTasks : List (Task Author [DbError]) +## +## getAuthors : Task (List Author) [DbError] +## getAuthors = Task.sequence fetchAuthorTasks +## ``` +## +sequence : List (Task ok err) -> Task (List ok) err +sequence = \taskList -> + Task.loop (taskList, List.withCapacity (List.len taskList)) \(tasks, values) -> + when tasks is + [task, .. as rest] -> + value = task! + Task.ok (Step (rest, List.append values value)) + + [] -> + Task.ok (Done values) + +## Apply a task repeatedly for each item in a list +## +## ``` +## authors : List Author +## saveAuthor : Author -> Task {} [DbError] +## +## saveAuthors : Task (List Author) [DbError] +## saveAuthors = Task.forEach authors saveAuthor +## ``` +## +forEach : List a, (a -> Task {} b) -> Task {} b +forEach = \items, fn -> + List.walk items (ok {}) \state, item -> + state |> await \_ -> fn item + +## Transform a task that can either succeed with `ok`, or fail with `err`, into +## a task that succeeds with `Result ok err`. +## +## This is useful when chaining tasks using the `!` suffix. For example: +## +## ``` +## # Path.roc +## checkFile : Str -> Task [Good, Bad] [IOError] +## +## # main.roc +## when checkFile "/usr/local/bin/roc" |> Task.result! is +## Ok Good -> "..." +## Ok Bad -> "..." +## Err IOError -> "..." +## ``` +## +result : Task ok err -> Task (Result ok err) * +result = \@Task task -> + @Task \{} -> + Ok (task {}) diff --git a/crates/compiler/builtins/roc/main.roc b/crates/compiler/builtins/roc/main.roc index e884058468..36edf4ed49 100644 --- a/crates/compiler/builtins/roc/main.roc +++ b/crates/compiler/builtins/roc/main.roc @@ -11,4 +11,5 @@ package [ Hash, Box, Inspect, + Task, ] {} diff --git a/crates/compiler/builtins/src/roc.rs b/crates/compiler/builtins/src/roc.rs index d69f73f914..2093c8e4cb 100644 --- a/crates/compiler/builtins/src/roc.rs +++ b/crates/compiler/builtins/src/roc.rs @@ -16,6 +16,7 @@ pub fn module_source(module_id: ModuleId) -> &'static str { ModuleId::DECODE => DECODE, ModuleId::HASH => HASH, ModuleId::INSPECT => INSPECT, + ModuleId::TASK => TASK, _ => internal_error!( "ModuleId {:?} is not part of the standard library", module_id @@ -35,3 +36,4 @@ const ENCODE: &str = include_str!("../roc/Encode.roc"); const DECODE: &str = include_str!("../roc/Decode.roc"); const HASH: &str = include_str!("../roc/Hash.roc"); const INSPECT: &str = include_str!("../roc/Inspect.roc"); +const TASK: &str = include_str!("../roc/Task.roc"); diff --git a/crates/compiler/can/src/copy.rs b/crates/compiler/can/src/copy.rs index c8d3b63267..7876723625 100644 --- a/crates/compiler/can/src/copy.rs +++ b/crates/compiler/can/src/copy.rs @@ -290,12 +290,14 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr Var(sym, var) => Var(*sym, sub!(*var)), ParamsVar { symbol, - params, var, + params_symbol, + params_var, } => ParamsVar { symbol: *symbol, - params: *params, var: sub!(*var), + params_symbol: *params_symbol, + params_var: sub!(*params_var), }, ImportParams(module_id, region, opt_provided) => ImportParams( *module_id, diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs index 0779a9e82d..c95b62bf8d 100644 --- a/crates/compiler/can/src/def.rs +++ b/crates/compiler/can/src/def.rs @@ -22,6 +22,7 @@ use crate::pattern::{canonicalize_def_header_pattern, BindingsFromPattern, Patte use crate::procedure::QualifiedReference; use crate::procedure::References; use crate::scope::create_alias; +use crate::scope::SymbolLookup; use crate::scope::{PendingAbilitiesInScope, Scope}; use roc_collections::ReferenceMatrix; use roc_collections::VecMap; @@ -111,6 +112,23 @@ impl Annotation { self } + + pub fn convert_to_fn(&mut self, argument_count: usize, var_store: &mut VarStore) { + let mut arg_types = Vec::with_capacity(argument_count); + + for _ in 0..argument_count { + let var = var_store.fresh(); + self.introduced_variables.insert_inferred(Loc::at_zero(var)); + + arg_types.push(Type::Variable(var)); + } + + self.signature = Type::Function( + arg_types, + Box::new(Type::Variable(var_store.fresh())), + Box::new(self.signature.clone()), + ); + } } #[derive(Debug)] @@ -165,6 +183,7 @@ enum PendingValueDef<'a> { /// Module params from an import ImportParams { symbol: Symbol, + variable: Variable, loc_pattern: Loc, module_id: ModuleId, opt_provided: Option>>>>, @@ -186,6 +205,7 @@ impl PendingValueDef<'_> { PendingValueDef::ImportParams { loc_pattern, symbol: _, + variable: _, module_id: _, opt_provided: _, } => loc_pattern, @@ -1153,6 +1173,7 @@ fn canonicalize_value_defs<'a>( pending_value_defs.push(PendingValueDef::ImportParams { symbol: params.symbol, + variable: params.variable, loc_pattern: params.loc_pattern, opt_provided: params.opt_provided, module_id, @@ -2400,15 +2421,17 @@ fn canonicalize_pending_value_def<'a>( } ImportParams { symbol, + variable, loc_pattern, module_id, opt_provided, } => { // Insert a reference to the record so that we don't report it as unused // If the whole module is unused, we'll report that separately - output - .references - .insert_value_lookup(symbol, QualifiedReference::Unqualified); + output.references.insert_value_lookup( + SymbolLookup::no_params(symbol), + QualifiedReference::Unqualified, + ); let (opt_var_record, references) = match opt_provided { Some(params) => { @@ -2418,7 +2441,7 @@ fn canonicalize_pending_value_def<'a>( let references = can_output.references.clone(); output.union(can_output); - (Some((var_store.fresh(), Box::new(record))), references) + (Some((variable, Box::new(record))), references) } None => (None, References::new()), }; @@ -2725,12 +2748,11 @@ pub fn report_unused_imports( for (symbol, region) in &import.exposed_symbols { if !references.has_unqualified_type_or_value_lookup(*symbol) && !scope.abilities_store.is_specialization_name(*symbol) - && !import.is_task(env) { env.problem(Problem::UnusedImport(*symbol, *region)); } } - } else if !import.is_task(env) { + } else { env.problem(Problem::UnusedModuleImport(import.module_id, import.region)); } } @@ -3003,6 +3025,7 @@ struct PendingModuleImport<'a> { struct PendingModuleImportParams<'a> { symbol: Symbol, + variable: Variable, loc_pattern: Loc, opt_provided: Option>>>>, } @@ -3013,16 +3036,6 @@ pub struct IntroducedImport { exposed_symbols: Vec<(Symbol, Region)>, } -impl IntroducedImport { - pub fn is_task(&self, env: &Env<'_>) -> bool { - // Temporarily needed for `!` convenience. Can be removed when Task becomes a builtin. - match env.qualified_module_ids.get_name(self.module_id) { - Some(name) => name.as_inner().as_str() == "Task", - None => false, - } - } -} - #[allow(clippy::too_many_arguments)] fn to_pending_value_def<'a>( env: &mut Env<'a>, @@ -3160,15 +3173,17 @@ fn to_pending_value_def<'a>( // We do this even if params weren't provided so that solve can report if they are missing let params_sym = scope.gen_unique_symbol(); let params_region = module_import.params.map(|p| p.params.region).unwrap_or(region); + let params_var = var_store.fresh(); let params = PendingModuleImportParams { symbol: params_sym, + variable: params_var, loc_pattern: Loc::at(params_region, Pattern::Identifier(params_sym)), opt_provided: module_import.params.map(|p| p.params.value), }; - let provided_params_sym = if module_import.params.is_some() { + let provided_params = if module_import.params.is_some() { // Only add params to scope if they are provided - Some(params_sym) + Some((params_var, params_sym)) } else { None }; @@ -3176,7 +3191,7 @@ fn to_pending_value_def<'a>( if let Err(existing_import) = scope .modules - .insert(name_with_alias.clone(), module_id, provided_params_sym, region) + .insert(name_with_alias.clone(), module_id, provided_params, region) { env.problems.push(Problem::ImportNameConflict { name: name_with_alias, diff --git a/crates/compiler/can/src/desugar.rs b/crates/compiler/can/src/desugar.rs index 1a0e60d509..e5f6fa8ceb 100644 --- a/crates/compiler/can/src/desugar.rs +++ b/crates/compiler/can/src/desugar.rs @@ -1,5 +1,7 @@ #![allow(clippy::manual_map)] +use crate::env::Env; +use crate::scope::Scope; use crate::suffixed::{apply_try_function, unwrap_suffixed_expression, EUnwrapped}; use bumpalo::collections::Vec; use bumpalo::Bump; @@ -9,11 +11,11 @@ use roc_module::called_via::{BinOp, CalledVia}; use roc_module::ident::ModuleName; use roc_parse::ast::Expr::{self, *}; use roc_parse::ast::{ - AssignedField, Collection, ModuleImportParams, OldRecordBuilderField, Pattern, StrLiteral, - StrSegment, TypeAnnotation, ValueDef, WhenBranch, + AssignedField, Collection, Defs, ModuleImportParams, OldRecordBuilderField, Pattern, + StrLiteral, StrSegment, TypeAnnotation, ValueDef, WhenBranch, }; use roc_problem::can::Problem; -use roc_region::all::{LineInfo, Loc, Region}; +use roc_region::all::{Loc, Region}; // BinOp precedence logic adapted from Gluon by Markus Westerlind // https://github.com/gluon-lang/gluon - license information can be found in @@ -22,7 +24,8 @@ use roc_region::all::{LineInfo, Loc, Region}; // Thank you, Markus! fn new_op_call_expr<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, left: &'a Loc>, loc_op: Loc, right: &'a Loc>, @@ -35,7 +38,7 @@ fn new_op_call_expr<'a>( match &right.value { Apply(function, arguments, _called_via) => { - let mut args = Vec::with_capacity_in(1 + arguments.len(), arena); + let mut args = Vec::with_capacity_in(1 + arguments.len(), env.arena); args.push(left); args.extend(arguments.iter()); @@ -44,9 +47,10 @@ fn new_op_call_expr<'a>( Apply(function, args, CalledVia::BinOp(Pizza)) } + Dbg => *desugar_dbg_expr(env, scope, left, region), _ => { // e.g. `1 |> (if b then (\a -> a) else (\c -> c))` - Apply(right, arena.alloc([left]), CalledVia::BinOp(Pizza)) + Apply(right, env.arena.alloc([left]), CalledVia::BinOp(Pizza)) } } } @@ -55,9 +59,9 @@ fn new_op_call_expr<'a>( // into the appropriate function call. let (module_name, ident) = binop_to_function(binop); - let args = arena.alloc([left, right]); + let args = env.arena.alloc([left, right]); - let loc_expr = arena.alloc(Loc { + let loc_expr = env.arena.alloc(Loc { value: Expr::Var { module_name, ident }, region: loc_op.region, }); @@ -70,19 +74,16 @@ fn new_op_call_expr<'a>( } fn desugar_value_def<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, def: &'a ValueDef<'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, problems), - desugar_expr(arena, loc_expr, src, line_info, module_path, problems), + desugar_loc_pattern(env, scope, loc_pattern), + desugar_expr(env, scope, loc_expr), ), ann @ Annotation(_, _) => *ann, AnnotatedBody { @@ -95,29 +96,15 @@ fn desugar_value_def<'a>( ann_pattern, ann_type, lines_between, - 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), + body_pattern: desugar_loc_pattern(env, scope, body_pattern), + body_expr: desugar_expr(env, scope, body_expr), }, Dbg { condition, preceding_comment, } => { - let desugared_condition = &*arena.alloc(desugar_expr( - arena, - condition, - src, - line_info, - module_path, - problems, - )); + let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition)); Dbg { condition: desugared_condition, preceding_comment: *preceding_comment, @@ -127,14 +114,7 @@ fn desugar_value_def<'a>( condition, preceding_comment, } => { - let desugared_condition = &*arena.alloc(desugar_expr( - arena, - condition, - src, - line_info, - module_path, - problems, - )); + let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition)); Expect { condition: desugared_condition, preceding_comment: *preceding_comment, @@ -144,14 +124,7 @@ fn desugar_value_def<'a>( condition, preceding_comment, } => { - let desugared_condition = &*arena.alloc(desugar_expr( - arena, - condition, - src, - line_info, - module_path, - problems, - )); + let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition)); ExpectFx { condition: desugared_condition, preceding_comment: *preceding_comment, @@ -167,16 +140,7 @@ fn desugar_value_def<'a>( let desugared_params = params.map(|ModuleImportParams { before, params }| ModuleImportParams { before, - params: params.map(|params| { - desugar_field_collection( - arena, - *params, - src, - line_info, - module_path, - problems, - ) - }), + params: params.map(|params| desugar_field_collection(env, scope, *params)), }); ModuleImport(roc_parse::ast::ModuleImport { @@ -195,11 +159,13 @@ fn desugar_value_def<'a>( // _ = stmt_expr! let region = stmt_expr.region; - let new_pat = arena.alloc(Loc::at(region, Pattern::Underscore("#!stmt"))); + let new_pat = env + .arena + .alloc(Loc::at(region, Pattern::Underscore("#!stmt"))); ValueDef::AnnotatedBody { ann_pattern: new_pat, - ann_type: arena.alloc(Loc::at( + ann_type: env.arena.alloc(Loc::at( region, TypeAnnotation::Record { fields: Collection::empty(), @@ -208,30 +174,20 @@ fn desugar_value_def<'a>( )), lines_between: &[], body_pattern: new_pat, - body_expr: desugar_expr(arena, stmt_expr, src, line_info, module_path, problems), + body_expr: desugar_expr(env, scope, stmt_expr), } } } } pub fn desugar_defs_node_values<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, defs: &mut roc_parse::ast::Defs<'a>, - src: &'a str, - 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, - problems, - ); + *value_def = desugar_value_def(env, scope, env.arena.alloc(*value_def)); } // `desugar_defs_node_values` is called recursively in `desugar_expr` @@ -241,7 +197,7 @@ pub fn desugar_defs_node_values<'a>( // been desugared if top_level_def { for value_def in defs.value_defs.iter_mut() { - *value_def = desugar_value_def_suffixed(arena, *value_def); + *value_def = desugar_value_def_suffixed(env.arena, *value_def); } } } @@ -349,12 +305,9 @@ pub fn desugar_value_def_suffixed<'a>(arena: &'a Bump, value_def: ValueDef<'a>) /// Reorder the expression tree based on operator precedence and associativity rules, /// then replace the BinOp nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes. pub fn desugar_expr<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, loc_expr: &'a Loc>, - src: &'a str, - line_info: &mut Option, - module_path: &str, - problems: &mut std::vec::Vec, ) -> &'a Loc> { match &loc_expr.value { Float(..) @@ -381,28 +334,19 @@ pub fn desugar_expr<'a>( StrLiteral::PlainLine(_) => loc_expr, StrLiteral::Line(segments) => { let region = loc_expr.region; - let value = Str(StrLiteral::Line(desugar_str_segments( - arena, - segments, - src, - line_info, - module_path, - problems, - ))); + let value = Str(StrLiteral::Line(desugar_str_segments(env, scope, segments))); - arena.alloc(Loc { region, value }) + env.arena.alloc(Loc { region, value }) } StrLiteral::Block(lines) => { 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, problems) - }), - arena, - ); + let mut new_lines = Vec::with_capacity_in(lines.len(), env.arena); + for segments in lines.iter() { + new_lines.push(desugar_str_segments(env, scope, segments)); + } let value = Str(StrLiteral::Block(new_lines.into_bump_slice())); - arena.alloc(Loc { region, value }) + env.arena.alloc(Loc { region, value }) } }, @@ -413,19 +357,11 @@ pub fn desugar_expr<'a>( value: **sub_expr, }; let value = TupleAccess( - &desugar_expr( - arena, - arena.alloc(loc_sub_expr), - src, - line_info, - module_path, - problems, - ) - .value, + &desugar_expr(env, scope, env.arena.alloc(loc_sub_expr)).value, paths, ); - arena.alloc(Loc { region, value }) + env.arena.alloc(Loc { region, value }) } // desugar the sub_expression, but leave the TrySuffix as this will // be unwrapped later in desugar_value_def_suffixed @@ -433,12 +369,11 @@ pub fn desugar_expr<'a>( expr: sub_expr, 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, problems); - let new_sub_expr = arena.alloc(new_sub_loc_expr.value); + let intermediate = env.arena.alloc(Loc::at(loc_expr.region, **sub_expr)); + let new_sub_loc_expr = desugar_expr(env, scope, intermediate); + let new_sub_expr = env.arena.alloc(new_sub_loc_expr.value); - arena.alloc(Loc::at( + env.arena.alloc(Loc::at( loc_expr.region, TrySuffix { expr: new_sub_expr, @@ -453,57 +388,41 @@ pub fn desugar_expr<'a>( value: **sub_expr, }; let value = RecordAccess( - &desugar_expr( - arena, - arena.alloc(loc_sub_expr), - src, - line_info, - module_path, - problems, - ) - .value, + &desugar_expr(env, scope, env.arena.alloc(loc_sub_expr)).value, paths, ); - arena.alloc(Loc { region, value }) + env.arena.alloc(Loc { region, value }) } List(items) => { - let mut new_items = Vec::with_capacity_in(items.len(), arena); + let mut new_items = Vec::with_capacity_in(items.len(), env.arena); for item in items.iter() { - new_items.push(desugar_expr( - arena, - item, - src, - line_info, - module_path, - problems, - )); + new_items.push(desugar_expr(env, scope, item)); } let new_items = new_items.into_bump_slice(); let value: Expr<'a> = List(items.replace_items(new_items)); - arena.alloc(Loc { + env.arena.alloc(Loc { region: loc_expr.region, value, }) } Record(fields) => { - let fields = - desugar_field_collection(arena, *fields, src, line_info, module_path, problems); - arena.alloc(Loc { + let fields = desugar_field_collection(env, scope, *fields); + env.arena.alloc(Loc { region: loc_expr.region, value: Record(fields), }) } Tuple(fields) => { - let mut allocated = Vec::with_capacity_in(fields.len(), arena); + let mut allocated = Vec::with_capacity_in(fields.len(), env.arena); for field in fields.iter() { - let expr = desugar_expr(arena, field, src, line_info, module_path, problems); + let expr = desugar_expr(env, scope, field); allocated.push(expr); } let fields = fields.replace_items(allocated.into_bump_slice()); - arena.alloc(Loc { + env.arena.alloc(Loc { region: loc_expr.region, value: Tuple(fields), }) @@ -511,12 +430,11 @@ 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, problems); + let new_update = desugar_expr(env, scope, update); - let mut allocated = Vec::with_capacity_in(fields.len(), arena); + let mut allocated = Vec::with_capacity_in(fields.len(), env.arena); for field in fields.iter() { - let value = - desugar_field(arena, &field.value, src, line_info, module_path, problems); + let value = desugar_field(env, scope, &field.value); allocated.push(Loc { value, region: field.region, @@ -524,7 +442,7 @@ pub fn desugar_expr<'a>( } let new_fields = fields.replace_items(allocated.into_bump_slice()); - arena.alloc(Loc { + env.arena.alloc(Loc { region: loc_expr.region, value: RecordUpdate { update: new_update, @@ -536,7 +454,7 @@ pub fn desugar_expr<'a>( let region = loc_expr.region; let closure_body = RecordUpdate { - update: arena.alloc(Loc { + update: env.arena.alloc(Loc { region, value: Expr::Var { module_name: "", @@ -550,7 +468,7 @@ pub fn desugar_expr<'a>( AssignedField::RequiredValue( Loc::at(region, field_name), &[], - &*arena.alloc(Loc { + &*env.arena.alloc(Loc { region, value: Expr::Var { module_name: "", @@ -559,16 +477,16 @@ pub fn desugar_expr<'a>( }), ), )], - arena, + env.arena, ) .into_bump_slice(), ), }; - arena.alloc(Loc { + env.arena.alloc(Loc { region, value: Closure( - arena.alloc_slice_copy(&[ + env.arena.alloc_slice_copy(&[ Loc::at( region, Pattern::Identifier { @@ -582,15 +500,15 @@ pub fn desugar_expr<'a>( }, ), ]), - arena.alloc(Loc::at(region, closure_body)), + env.arena.alloc(Loc::at(region, closure_body)), ), }) } - Closure(loc_patterns, loc_ret) => arena.alloc(Loc { + Closure(loc_patterns, loc_ret) => env.arena.alloc(Loc { region: loc_expr.region, value: Closure( - desugar_loc_patterns(arena, loc_patterns, src, line_info, module_path, problems), - desugar_expr(arena, loc_ret, src, line_info, module_path, problems), + desugar_loc_patterns(env, scope, loc_patterns), + desugar_expr(env, scope, loc_ret), ), }), Backpassing(loc_patterns, loc_body, loc_ret) => { @@ -602,59 +520,57 @@ pub fn desugar_expr<'a>( &Region::across_all(loc_patterns.iter().map(|loc_pattern| &loc_pattern.region)), &loc_body.region, ); - problems.push(Problem::DeprecatedBackpassing(problem_region)); + env.problem(Problem::DeprecatedBackpassing(problem_region)); // first desugar the body, because it may contain |> - let desugared_body = - desugar_expr(arena, loc_body, src, line_info, module_path, problems); + let desugared_body = desugar_expr(env, scope, loc_body); - 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, problems); + let desugared_ret = desugar_expr(env, scope, loc_ret); + let desugared_loc_patterns = desugar_loc_patterns(env, scope, loc_patterns); let closure = Expr::Closure(desugared_loc_patterns, desugared_ret); let loc_closure = Loc::at(loc_expr.region, closure); match &desugared_body.value { Expr::Apply(function, arguments, called_via) => { let mut new_arguments: Vec<'a, &'a Loc>> = - Vec::with_capacity_in(arguments.len() + 1, arena); + Vec::with_capacity_in(arguments.len() + 1, env.arena); new_arguments.extend(arguments.iter()); - new_arguments.push(arena.alloc(loc_closure)); + new_arguments.push(env.arena.alloc(loc_closure)); let call = Expr::Apply(function, new_arguments.into_bump_slice(), *called_via); let loc_call = Loc::at(loc_expr.region, call); - arena.alloc(loc_call) + env.arena.alloc(loc_call) } _ => { // e.g. `x <- (if b then (\a -> a) else (\c -> c))` let call = Expr::Apply( desugared_body, - arena.alloc([&*arena.alloc(loc_closure)]), + env.arena.alloc([&*env.arena.alloc(loc_closure)]), CalledVia::Space, ); let loc_call = Loc::at(loc_expr.region, call); - arena.alloc(loc_call) + env.arena.alloc(loc_call) } } } - OldRecordBuilder(_) => arena.alloc(Loc { + OldRecordBuilder(_) => env.arena.alloc(Loc { value: UnappliedOldRecordBuilder(loc_expr), region: loc_expr.region, }), 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, problems); + let new_mapper = desugar_expr(env, scope, mapper); if fields.is_empty() { - return arena.alloc(Loc { + return env.arena.alloc(Loc { value: EmptyRecordBuilder(loc_expr), region: loc_expr.region, }); } else if fields.len() == 1 { - return arena.alloc(Loc { + return env.arena.alloc(Loc { value: SingleFieldRecordBuilder(loc_expr), region: loc_expr.region, }); @@ -666,40 +582,37 @@ pub fn desugar_expr<'a>( ignored: bool, } - let mut field_data = Vec::with_capacity_in(fields.len(), arena); + let mut field_data = Vec::with_capacity_in(fields.len(), env.arena); for field in fields.items { - let (name, value, ignored) = - match desugar_field(arena, &field.value, src, line_info, module_path, problems) - { - AssignedField::RequiredValue(loc_name, _, loc_val) => { - (loc_name, loc_val, false) - } - AssignedField::IgnoredValue(loc_name, _, loc_val) => { - (loc_name, loc_val, true) - } - AssignedField::LabelOnly(loc_name) => ( - loc_name, - &*arena.alloc(Loc { - region: loc_name.region, - value: Expr::Var { - module_name: "", - ident: loc_name.value, - }, - }), - false, - ), - AssignedField::OptionalValue(loc_name, _, loc_val) => { - return arena.alloc(Loc { - region: loc_expr.region, - value: OptionalFieldInRecordBuilder(arena.alloc(loc_name), loc_val), - }); - } - AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => { - unreachable!("Should have been desugared in `desugar_field`") - } - AssignedField::Malformed(_name) => continue, - }; + let desugared_field = desugar_field(env, scope, &field.value); + let (name, value, ignored) = match desugared_field { + AssignedField::RequiredValue(loc_name, _, loc_val) => { + (loc_name, loc_val, false) + } + AssignedField::IgnoredValue(loc_name, _, loc_val) => (loc_name, loc_val, true), + AssignedField::LabelOnly(loc_name) => ( + loc_name, + &*env.arena.alloc(Loc { + region: loc_name.region, + value: Expr::Var { + module_name: "", + ident: loc_name.value, + }, + }), + false, + ), + AssignedField::OptionalValue(loc_name, _, loc_val) => { + return env.arena.alloc(Loc { + region: loc_expr.region, + value: OptionalFieldInRecordBuilder(env.arena.alloc(loc_name), loc_val), + }); + } + AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => { + unreachable!("Should have been desugared in `desugar_field`") + } + AssignedField::Malformed(_name) => continue, + }; field_data.push(FieldData { name, @@ -719,7 +632,7 @@ pub fn desugar_expr<'a>( Pattern::Underscore(name.value) } else { Pattern::Identifier { - ident: arena.alloc_str(&format!("#{}", name.value)), + ident: env.arena.alloc_str(&format!("#{}", name.value)), } }, }; @@ -728,14 +641,14 @@ pub fn desugar_expr<'a>( let closure_body = Tuple(Collection::with_items( Vec::from_iter_in( [ - &*arena.alloc(Loc::at( + &*env.arena.alloc(Loc::at( region, Expr::Var { module_name: "", ident: "#record_builder_closure_arg_a", }, )), - &*arena.alloc(Loc::at( + &*env.arena.alloc(Loc::at( region, Expr::Var { module_name: "", @@ -743,15 +656,15 @@ pub fn desugar_expr<'a>( }, )), ], - arena, + env.arena, ) .into_bump_slice(), )); - arena.alloc(Loc::at( + env.arena.alloc(Loc::at( region, Closure( - arena.alloc_slice_copy(&[ + env.arena.alloc_slice_copy(&[ Loc::at( region, Pattern::Identifier { @@ -765,14 +678,14 @@ pub fn desugar_expr<'a>( }, ), ]), - arena.alloc(Loc::at(region, closure_body)), + env.arena.alloc(Loc::at(region, closure_body)), ), )) }; let closure_args = { if field_data.len() == 2 { - arena.alloc_slice_copy(&[ + env.arena.alloc_slice_copy(&[ closure_arg_from_field(&field_data[0]), closure_arg_from_field(&field_data[1]), ]) @@ -782,14 +695,14 @@ pub fn desugar_expr<'a>( let last_arg = closure_arg_from_field(&field_data[field_data.len() - 1]); let mut second_arg = Pattern::Tuple(Collection::with_items( - arena.alloc_slice_copy(&[second_to_last_arg, last_arg]), + env.arena.alloc_slice_copy(&[second_to_last_arg, last_arg]), )); let mut second_arg_region = Region::span_across(&second_to_last_arg.region, &last_arg.region); for index in (1..(field_data.len() - 2)).rev() { second_arg = - Pattern::Tuple(Collection::with_items(arena.alloc_slice_copy(&[ + Pattern::Tuple(Collection::with_items(env.arena.alloc_slice_copy(&[ closure_arg_from_field(&field_data[index]), Loc::at(second_arg_region, second_arg), ]))); @@ -797,7 +710,7 @@ pub fn desugar_expr<'a>( Region::span_across(&field_data[index].name.region, &second_arg_region); } - arena.alloc_slice_copy(&[ + env.arena.alloc_slice_copy(&[ closure_arg_from_field(&field_data[0]), Loc::at(second_arg_region, second_arg), ]) @@ -815,36 +728,37 @@ pub fn desugar_expr<'a>( AssignedField::RequiredValue( field.name, &[], - arena.alloc(Loc::at( + env.arena.alloc(Loc::at( field.name.region, Expr::Var { module_name: "", - ident: arena + ident: env + .arena .alloc_str(&format!("#{}", field.name.value)), }, )), ), ) }), - arena, + env.arena, ) .into_bump_slice(), )); - let record_combiner_closure = arena.alloc(Loc { + let record_combiner_closure = env.arena.alloc(Loc { region: loc_expr.region, value: Closure( closure_args, - arena.alloc(Loc::at(loc_expr.region, record_val)), + env.arena.alloc(Loc::at(loc_expr.region, record_val)), ), }); if field_data.len() == 2 { - return arena.alloc(Loc { + return env.arena.alloc(Loc { region: loc_expr.region, value: Apply( new_mapper, - arena.alloc_slice_copy(&[ + env.arena.alloc_slice_copy(&[ field_data[0].value, field_data[1].value, record_combiner_closure, @@ -854,14 +768,14 @@ pub fn desugar_expr<'a>( }); } - let mut inner_combined = arena.alloc(Loc { + let mut inner_combined = env.arena.alloc(Loc { region: Region::span_across( &field_data[field_data.len() - 2].value.region, &field_data[field_data.len() - 1].value.region, ), value: Apply( new_mapper, - arena.alloc_slice_copy(&[ + env.arena.alloc_slice_copy(&[ field_data[field_data.len() - 2].value, field_data[field_data.len() - 1].value, combiner_closure_in_region(loc_expr.region), @@ -871,14 +785,14 @@ pub fn desugar_expr<'a>( }); for index in (1..(field_data.len() - 2)).rev() { - inner_combined = arena.alloc(Loc { + inner_combined = env.arena.alloc(Loc { region: Region::span_across( &field_data[index].value.region, &inner_combined.region, ), value: Apply( new_mapper, - arena.alloc_slice_copy(&[ + env.arena.alloc_slice_copy(&[ field_data[index].value, inner_combined, combiner_closure_in_region(loc_expr.region), @@ -888,11 +802,11 @@ pub fn desugar_expr<'a>( }); } - arena.alloc(Loc { + env.arena.alloc(Loc { region: loc_expr.region, value: Apply( new_mapper, - arena.alloc_slice_copy(&[ + env.arena.alloc_slice_copy(&[ field_data[0].value, inner_combined, record_combiner_closure, @@ -901,33 +815,44 @@ pub fn desugar_expr<'a>( ), }) } - BinOps(lefts, right) => desugar_bin_ops( - arena, - loc_expr.region, - lefts, - right, - src, - line_info, - module_path, - problems, - ), + BinOps(lefts, right) => desugar_bin_ops(env, scope, loc_expr.region, lefts, right), Defs(defs, loc_ret) => { let mut defs = (*defs).clone(); - 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); + desugar_defs_node_values(env, scope, &mut defs, false); + let loc_ret = desugar_expr(env, scope, loc_ret); - arena.alloc(Loc::at(loc_expr.region, Defs(arena.alloc(defs), loc_ret))) + env.arena.alloc(Loc::at( + loc_expr.region, + Defs(env.arena.alloc(defs), loc_ret), + )) + } + Apply(Loc { value: Dbg, .. }, loc_args, _called_via) => { + debug_assert!(!loc_args.is_empty()); + + if loc_args.len() > 1 { + let args_region = Region::span_across( + &loc_args.first().unwrap().region, + &loc_args.last().unwrap().region, + ); + env.problem(Problem::OverAppliedDbg { + region: args_region, + }); + + env.arena.alloc(Loc { + value: *desugar_invalid_dbg_expr(env, scope, loc_expr.region), + region: loc_expr.region, + }) + } else { + let desugared_arg = desugar_expr(env, scope, loc_args.first().unwrap()); + + env.arena.alloc(Loc { + value: *desugar_dbg_expr(env, scope, desugared_arg, loc_expr.region), + region: loc_expr.region, + }) + } } Apply(loc_fn, loc_args, called_via) => { - let mut desugared_args = Vec::with_capacity_in(loc_args.len(), arena); + let mut desugared_args = Vec::with_capacity_in(loc_args.len(), env.arena); let mut builder_apply_exprs = None; for loc_arg in loc_args.iter() { @@ -936,13 +861,13 @@ pub fn desugar_expr<'a>( match current { OldRecordBuilder(fields) => { if builder_apply_exprs.is_some() { - return arena.alloc(Loc { + return env.arena.alloc(Loc { value: MultipleOldRecordBuilders(loc_expr), region: loc_expr.region, }); } - let builder_arg = old_record_builder_arg(arena, loc_arg.region, fields); + let builder_arg = old_record_builder_arg(env, loc_arg.region, fields); builder_apply_exprs = Some(builder_arg.apply_exprs); break builder_arg.closure; @@ -954,21 +879,14 @@ pub fn desugar_expr<'a>( } }; - desugared_args.push(desugar_expr( - arena, - arg, - src, - line_info, - module_path, - problems, - )); + desugared_args.push(desugar_expr(env, scope, arg)); } let desugared_args = desugared_args.into_bump_slice(); - let mut apply: &Loc = arena.alloc(Loc { + let mut apply: &Loc = env.arena.alloc(Loc { value: Apply( - desugar_expr(arena, loc_fn, src, line_info, module_path, problems), + desugar_expr(env, scope, loc_fn), desugared_args, *called_via, ), @@ -980,12 +898,11 @@ 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, problems); + let desugared_expr = desugar_expr(env, scope, expr); - let args = std::slice::from_ref(arena.alloc(apply)); + let args = std::slice::from_ref(env.arena.alloc(apply)); - apply = arena.alloc(Loc { + apply = env.arena.alloc(Loc { value: Apply(desugared_expr, args, CalledVia::OldRecordBuilder), region: loc_expr.region, }); @@ -996,42 +913,20 @@ pub fn desugar_expr<'a>( apply } When(loc_cond_expr, branches) => { - let loc_desugared_cond = &*arena.alloc(desugar_expr( - arena, - loc_cond_expr, - src, - line_info, - module_path, - problems, - )); - let mut desugared_branches = Vec::with_capacity_in(branches.len(), arena); + let loc_desugared_cond = &*env.arena.alloc(desugar_expr(env, scope, loc_cond_expr)); + let mut desugared_branches = Vec::with_capacity_in(branches.len(), env.arena); for branch in branches.iter() { - let desugared_expr = - 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_expr = desugar_expr(env, scope, &branch.value); + let desugared_patterns = desugar_loc_patterns(env, scope, branch.patterns); let desugared_guard = if let Some(guard) = &branch.guard { - Some(*desugar_expr( - arena, - guard, - src, - line_info, - module_path, - problems, - )) + Some(*desugar_expr(env, scope, guard)) } else { None }; - desugared_branches.push(&*arena.alloc(WhenBranch { + desugared_branches.push(&*env.arena.alloc(WhenBranch { patterns: desugared_patterns, value: *desugared_expr, guard: desugared_guard, @@ -1040,7 +935,7 @@ pub fn desugar_expr<'a>( let desugared_branches = desugared_branches.into_bump_slice(); - arena.alloc(Loc { + env.arena.alloc(Loc { value: When(loc_desugared_cond, desugared_branches), region: loc_expr.region, }) @@ -1063,17 +958,10 @@ pub fn desugar_expr<'a>( ident: "not", }, }; - let loc_fn_var = arena.alloc(Loc { region, value }); - let desugared_args = arena.alloc([desugar_expr( - arena, - loc_arg, - src, - line_info, - module_path, - problems, - )]); + let loc_fn_var = env.arena.alloc(Loc { region, value }); + let desugared_args = env.arena.alloc([desugar_expr(env, scope, loc_arg)]); - arena.alloc(Loc { + env.arena.alloc(Loc { value: Apply(loc_fn_var, desugared_args, CalledVia::UnaryOp(op)), region: loc_expr.region, }) @@ -1082,141 +970,67 @@ pub fn desugar_expr<'a>( // Since we've already begun canonicalization, spaces and parens // are no longer needed and should be dropped. desugar_expr( - arena, - arena.alloc(Loc { + env, + scope, + env.arena.alloc(Loc { value: **expr, region: loc_expr.region, }), - src, - line_info, - module_path, - problems, ) } ParensAround(expr) => { let desugared = desugar_expr( - arena, - arena.alloc(Loc { + env, + scope, + env.arena.alloc(Loc { value: **expr, region: loc_expr.region, }), - src, - line_info, - module_path, - problems, ); - arena.alloc(Loc { + env.arena.alloc(Loc { value: ParensAround(&desugared.value), region: loc_expr.region, }) } If(if_thens, final_else_branch) => { // If does not get desugared into `when` so we can give more targeted error messages during type checking. - let desugared_final_else = &*arena.alloc(desugar_expr( - arena, - final_else_branch, - src, - line_info, - module_path, - problems, - )); + let desugared_final_else = + &*env.arena.alloc(desugar_expr(env, scope, final_else_branch)); - 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(), env.arena); for (condition, then_branch) in if_thens.iter() { - desugared_if_thens.push(( - *desugar_expr(arena, condition, src, line_info, module_path, problems), - *desugar_expr(arena, then_branch, src, line_info, module_path, problems), - )); + let desugared_condition = *desugar_expr(env, scope, condition); + let desugared_then_branch = *desugar_expr(env, scope, then_branch); + + desugared_if_thens.push((desugared_condition, desugared_then_branch)); } - arena.alloc(Loc { + env.arena.alloc(Loc { value: If(desugared_if_thens.into_bump_slice(), desugared_final_else), region: loc_expr.region, }) } Expect(condition, continuation) => { - 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 { + let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition)); + let desugared_continuation = &*env.arena.alloc(desugar_expr(env, scope, continuation)); + env.arena.alloc(Loc { value: Expect(desugared_condition, desugared_continuation), region: loc_expr.region, }) } - Dbg(condition, continuation) => { - // Desugars a `dbg x` statement into essentially - // Inspect.toStr x |> LowLevelDbg - let desugared_continuation = &*arena.alloc(desugar_expr( - arena, - continuation, - src, - line_info, - module_path, - problems, - )); + Dbg => { + // Allow naked dbg, necessary for piping values into dbg with the `Pizza` binop + loc_expr + } + DbgStmt(condition, continuation) => { + let desugared_condition = &*env.arena.alloc(desugar_expr(env, scope, condition)); - let region = condition.region; - // Inspect.toStr x - let inspect_fn = Var { - module_name: ModuleName::INSPECT, - ident: "toStr", - }; - let loc_inspect_fn_var = arena.alloc(Loc { - value: inspect_fn, - region, - }); - let desugared_inspect_args = arena.alloc([desugar_expr( - arena, - condition, - src, - line_info, - module_path, - problems, - )]); + let desugared_continuation = &*env.arena.alloc(desugar_expr(env, scope, continuation)); - let dbg_str = arena.alloc(Loc { - value: Apply(loc_inspect_fn_var, desugared_inspect_args, CalledVia::Space), - region, - }); - - // line_info is an option so that we can lazily calculate it. - // That way it there are no `dbg` statements, we never pay the cast of scanning the source an extra time. - if line_info.is_none() { - *line_info = Some(LineInfo::new(src)); - } - let line_col = line_info.as_ref().unwrap().convert_pos(region.start()); - - let dbg_src = src - .split_at(region.start().offset as usize) - .1 - .split_at((region.end().offset - region.start().offset) as usize) - .0; - - // |> LowLevelDbg - arena.alloc(Loc { - value: LowLevelDbg( - arena.alloc(( - &*arena.alloc_str(&format!("{}:{}", module_path, line_col.line + 1)), - &*arena.alloc_str(dbg_src), - )), - dbg_str, - desugared_continuation, - ), + env.arena.alloc(Loc { + value: *desugar_dbg_stmt(env, desugared_condition, desugared_continuation), region: loc_expr.region, }) } @@ -1227,70 +1041,60 @@ pub fn desugar_expr<'a>( } fn desugar_str_segments<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, segments: &'a [StrSegment<'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 { + let mut allocated = Vec::with_capacity_in(segments.len(), env.arena); + + for segment in segments.iter() { + allocated.push(match segment { StrSegment::Plaintext(_) | StrSegment::Unicode(_) | StrSegment::EscapedChar(_) => { *segment } StrSegment::DeprecatedInterpolated(loc_expr) => { let loc_desugared = desugar_expr( - arena, - arena.alloc(Loc { + env, + scope, + env.arena.alloc(Loc { region: loc_expr.region, value: *loc_expr.value, }), - src, - line_info, - module_path, - problems, ); StrSegment::DeprecatedInterpolated(Loc { region: loc_desugared.region, - value: arena.alloc(loc_desugared.value), + value: env.arena.alloc(loc_desugared.value), }) } StrSegment::Interpolated(loc_expr) => { let loc_desugared = desugar_expr( - arena, - arena.alloc(Loc { + env, + scope, + env.arena.alloc(Loc { region: loc_expr.region, value: *loc_expr.value, }), - src, - line_info, - module_path, - problems, ); StrSegment::Interpolated(Loc { region: loc_desugared.region, - value: arena.alloc(loc_desugared.value), + value: env.arena.alloc(loc_desugared.value), }) } - }), - arena, - ) - .into_bump_slice() + }); + } + + allocated.into_bump_slice() } fn desugar_field_collection<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, fields: Collection<'a, Loc>>>, - 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); + let mut allocated = Vec::with_capacity_in(fields.len(), env.arena); for field in fields.iter() { - let value = desugar_field(arena, &field.value, src, line_info, module_path, problems); + let value = desugar_field(env, scope, &field.value); allocated.push(Loc::at(field.region, value)); } @@ -1299,12 +1103,9 @@ fn desugar_field_collection<'a>( } fn desugar_field<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, field: &'a AssignedField<'a, Expr<'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::*; @@ -1315,7 +1116,7 @@ fn desugar_field<'a>( region: loc_str.region, }, spaces, - desugar_expr(arena, loc_expr, src, line_info, module_path, problems), + desugar_expr(env, scope, loc_expr), ), OptionalValue(loc_str, spaces, loc_expr) => OptionalValue( Loc { @@ -1323,7 +1124,7 @@ fn desugar_field<'a>( region: loc_str.region, }, spaces, - desugar_expr(arena, loc_expr, src, line_info, module_path, problems), + desugar_expr(env, scope, loc_expr), ), IgnoredValue(loc_str, spaces, loc_expr) => IgnoredValue( Loc { @@ -1331,7 +1132,7 @@ fn desugar_field<'a>( region: loc_str.region, }, spaces, - desugar_expr(arena, loc_expr, src, line_info, module_path, problems), + desugar_expr(env, scope, loc_expr), ), LabelOnly(loc_str) => { // Desugar { x } into { x: x } @@ -1349,81 +1150,45 @@ fn desugar_field<'a>( region: loc_str.region, }, &[], - desugar_expr( - arena, - arena.alloc(loc_expr), - src, - line_info, - module_path, - problems, - ), + desugar_expr(env, scope, env.arena.alloc(loc_expr)), ) } - 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) - } + SpaceBefore(field, _spaces) => desugar_field(env, scope, field), + SpaceAfter(field, _spaces) => desugar_field(env, scope, field), Malformed(string) => Malformed(string), } } fn desugar_loc_patterns<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, loc_patterns: &'a [Loc>], - 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 { + let mut allocated = Vec::with_capacity_in(loc_patterns.len(), env.arena); + + for loc_pattern in loc_patterns.iter() { + allocated.push(Loc { region: loc_pattern.region, - value: desugar_pattern( - arena, - loc_pattern.value, - src, - line_info, - module_path, - problems, - ), - }), - arena, - ) - .into_bump_slice() + value: desugar_pattern(env, scope, loc_pattern.value), + }); + } + + allocated.into_bump_slice() } fn desugar_loc_pattern<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, loc_pattern: &'a Loc>, - src: &'a str, - line_info: &mut Option, - module_path: &str, - problems: &mut std::vec::Vec, ) -> &'a Loc> { - arena.alloc(Loc { + env.arena.alloc(Loc { region: loc_pattern.region, - value: desugar_pattern( - arena, - loc_pattern.value, - src, - line_info, - module_path, - problems, - ), + value: desugar_pattern(env, scope, loc_pattern.value), }) } -fn desugar_pattern<'a>( - arena: &'a Bump, - pattern: Pattern<'a>, - src: &'a str, - line_info: &mut Option, - module_path: &str, - problems: &mut std::vec::Vec, -) -> Pattern<'a> { +fn desugar_pattern<'a>(env: &mut Env<'a>, scope: &mut Scope, pattern: Pattern<'a>) -> Pattern<'a> { use roc_parse::ast::Pattern::*; match pattern { @@ -1443,35 +1208,20 @@ fn desugar_pattern<'a>( Apply(tag, arg_patterns) => { // Skip desugaring the tag, it should either be a Tag or OpaqueRef - let desugared_arg_patterns = Vec::from_iter_in( - arg_patterns.iter().map(|arg_pattern| Loc { + let mut desugared_arg_patterns = Vec::with_capacity_in(arg_patterns.len(), env.arena); + for arg_pattern in arg_patterns.iter() { + desugared_arg_patterns.push(Loc { region: arg_pattern.region, - value: desugar_pattern( - arena, - arg_pattern.value, - src, - line_info, - module_path, - problems, - ), - }), - arena, - ) - .into_bump_slice(); + value: desugar_pattern(env, scope, arg_pattern.value), + }); + } - Apply(tag, desugared_arg_patterns) + Apply(tag, desugared_arg_patterns.into_bump_slice()) } RecordDestructure(field_patterns) => { - let mut allocated = Vec::with_capacity_in(field_patterns.len(), arena); + let mut allocated = Vec::with_capacity_in(field_patterns.len(), env.arena); for field_pattern in field_patterns.iter() { - let value = desugar_pattern( - arena, - field_pattern.value, - src, - line_info, - module_path, - problems, - ); + let value = desugar_pattern(env, scope, field_pattern.value); allocated.push(Loc { value, region: field_pattern.region, @@ -1481,19 +1231,14 @@ fn desugar_pattern<'a>( RecordDestructure(field_patterns) } - RequiredField(name, field_pattern) => RequiredField( - name, - 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), - ), + RequiredField(name, field_pattern) => { + RequiredField(name, desugar_loc_pattern(env, scope, field_pattern)) + } + OptionalField(name, expr) => OptionalField(name, desugar_expr(env, scope, expr)), Tuple(patterns) => { - let mut allocated = Vec::with_capacity_in(patterns.len(), arena); + let mut allocated = Vec::with_capacity_in(patterns.len(), env.arena); for pattern in patterns.iter() { - let value = - desugar_pattern(arena, pattern.value, src, line_info, module_path, problems); + let value = desugar_pattern(env, scope, pattern.value); allocated.push(Loc { value, region: pattern.region, @@ -1504,10 +1249,9 @@ fn desugar_pattern<'a>( Tuple(patterns) } List(patterns) => { - let mut allocated = Vec::with_capacity_in(patterns.len(), arena); + let mut allocated = Vec::with_capacity_in(patterns.len(), env.arena); for pattern in patterns.iter() { - let value = - desugar_pattern(arena, pattern.value, src, line_info, module_path, problems); + let value = desugar_pattern(env, scope, pattern.value); allocated.push(Loc { value, region: pattern.region, @@ -1517,32 +1261,134 @@ fn desugar_pattern<'a>( List(patterns) } - As(sub_pattern, symbol) => As( - 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, problems) - } - SpaceAfter(sub_pattern, _spaces) => { - desugar_pattern(arena, *sub_pattern, src, line_info, module_path, problems) - } + As(sub_pattern, symbol) => As(desugar_loc_pattern(env, scope, sub_pattern), symbol), + SpaceBefore(sub_pattern, _spaces) => desugar_pattern(env, scope, *sub_pattern), + SpaceAfter(sub_pattern, _spaces) => desugar_pattern(env, scope, *sub_pattern), } } +/// Desugars a `dbg expr` expression into a statement block that prints and returns the +/// value produced by `expr`. Essentially: +/// ( +/// tmpVar = expr +/// LowLevelDbg (Inspect.toStr tmpVar) +/// tmpVar +/// ) +fn desugar_dbg_expr<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + expr: &'a Loc>, + outer_region: Region, +) -> &'a Expr<'a> { + let region = expr.region; + + // tmpVar = expr + let ident = env.arena.alloc(scope.gen_unique_symbol_name().to_string()); + + let value_def = ValueDef::Body( + env.arena.alloc(Loc { + value: Pattern::Identifier { ident }, + region, + }), + expr, + ); + + let defs = env.arena.alloc(Defs::default()); + defs.push_value_def(value_def, region, &[], &[]); + + // tmpVar + let tmp_var = env.arena.alloc(Loc { + value: Var { + module_name: "", + ident, + }, + region, + }); + + // LowLevelDbg + let dbg_stmt = env.arena.alloc(Loc { + value: *desugar_dbg_stmt(env, tmp_var, tmp_var), + region: outer_region, + }); + + env.arena.alloc(Defs(defs, dbg_stmt)) +} + +/// Build a desugared `dbg {}` expression to act as a placeholder when the AST +/// is invalid. +pub fn desugar_invalid_dbg_expr<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + outer_region: Region, +) -> &'a Expr<'a> { + let placeholder_expr = env.arena.alloc(Loc { + value: Record(Collection::empty()), + region: outer_region, + }); + + desugar_dbg_expr(env, scope, placeholder_expr, outer_region) +} + +/// Desugars a `dbg x` statement into essentially `Inspect.toStr x |> LowLevelDbg` +fn desugar_dbg_stmt<'a>( + env: &mut Env<'a>, + condition: &'a Loc>, + continuation: &'a Loc>, +) -> &'a Expr<'a> { + let region = condition.region; + + let inspect_fn = Var { + module_name: ModuleName::INSPECT, + ident: "toStr", + }; + let loc_inspect_fn_var = env.arena.alloc(Loc { + value: inspect_fn, + region, + }); + let inspect_args = &*env.arena.alloc([condition]); + + let dbg_str = env.arena.alloc(Loc { + value: Apply(loc_inspect_fn_var, inspect_args, CalledVia::Space), + region, + }); + + let line_col = env.line_info().convert_pos(region.start()); + + let dbg_src = env + .src + .split_at(region.start().offset as usize) + .1 + .split_at((region.end().offset - region.start().offset) as usize) + .0; + + let module_path_str = env.module_path.to_string_lossy(); + + // |> LowLevelDbg + env.arena.alloc(LowLevelDbg( + env.arena.alloc(( + &*env + .arena + .alloc_str(&format!("{}:{}", module_path_str, line_col.line + 1)), + &*env.arena.alloc_str(dbg_src), + )), + dbg_str, + continuation, + )) +} + struct OldRecordBuilderArg<'a> { closure: &'a Loc>, apply_exprs: Vec<'a, &'a Loc>>, } fn old_record_builder_arg<'a>( - arena: &'a Bump, + env: &mut Env<'a>, region: Region, fields: Collection<'a, Loc>>, ) -> OldRecordBuilderArg<'a> { - let mut record_fields = Vec::with_capacity_in(fields.len(), arena); - let mut apply_exprs = Vec::with_capacity_in(fields.len(), arena); - let mut apply_field_names = Vec::with_capacity_in(fields.len(), arena); + let mut record_fields = Vec::with_capacity_in(fields.len(), env.arena); + let mut apply_exprs = Vec::with_capacity_in(fields.len(), env.arena); + let mut apply_field_names = Vec::with_capacity_in(fields.len(), env.arena); // Build the record that the closure will return and gather apply expressions @@ -1558,11 +1404,11 @@ fn old_record_builder_arg<'a>( apply_field_names.push(label); apply_exprs.push(expr); - let var = arena.alloc(Loc { + let var = env.arena.alloc(Loc { region: label.region, value: Expr::Var { module_name: "", - ident: arena.alloc("#".to_owned() + label.value), + ident: env.arena.alloc("#".to_owned() + label.value), }, }); @@ -1589,7 +1435,7 @@ fn old_record_builder_arg<'a>( let record_fields = fields.replace_items(record_fields.into_bump_slice()); - let mut body = arena.alloc(Loc { + let mut body = env.arena.alloc(Loc { value: Record(record_fields), region, }); @@ -1601,15 +1447,15 @@ fn old_record_builder_arg<'a>( // \#x -> \#y -> { x: #x, y: #y, z: 3 } for label in apply_field_names.iter().rev() { - let name = arena.alloc("#".to_owned() + label.value); + let name = env.arena.alloc("#".to_owned() + label.value); let ident = roc_parse::ast::Pattern::Identifier { ident: name }; - let arg_pattern = arena.alloc(Loc { + let arg_pattern = env.arena.alloc(Loc { value: ident, region: label.region, }); - body = arena.alloc(Loc { + body = env.arena.alloc(Loc { value: Closure(std::slice::from_ref(arg_pattern), body), region, }); @@ -1646,39 +1492,37 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { } } -#[allow(clippy::too_many_arguments)] fn desugar_bin_ops<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, whole_region: Region, lefts: &'a [(Loc>, Loc)], right: &'a Loc>, - 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); + let mut arg_stack: Vec<&'a Loc> = Vec::with_capacity_in(lefts.len() + 1, env.arena); + let mut op_stack: Vec> = Vec::with_capacity_in(lefts.len(), env.arena); for (loc_expr, loc_op) in lefts { - 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) { + arg_stack.push(desugar_expr(env, scope, loc_expr)); + match run_binop_step( + env, + scope, + 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, problems); + let mut expr = desugar_expr(env, scope, right); 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 = env + .arena + .alloc(new_op_call_expr(env, scope, left, loc_op, expr)); } expr @@ -1691,7 +1535,8 @@ enum Step<'a> { } fn run_binop_step<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, whole_region: Region, arg_stack: &mut Vec<&'a Loc>>, op_stack: &mut Vec>, @@ -1699,15 +1544,16 @@ fn run_binop_step<'a>( ) -> Result<(), &'a Loc>> { use Step::*; - match binop_step(arena, whole_region, arg_stack, op_stack, next_op) { + match binop_step(env, scope, whole_region, arg_stack, op_stack, next_op) { Error(problem) => Err(problem), - Push(loc_op) => run_binop_step(arena, whole_region, arg_stack, op_stack, loc_op), + Push(loc_op) => run_binop_step(env, scope, whole_region, arg_stack, op_stack, loc_op), Skip => Ok(()), } } fn binop_step<'a>( - arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, whole_region: Region, arg_stack: &mut Vec<&'a Loc>>, op_stack: &mut Vec>, @@ -1724,7 +1570,10 @@ fn binop_step<'a>( let right = arg_stack.pop().unwrap(); let left = arg_stack.pop().unwrap(); - arg_stack.push(arena.alloc(new_op_call_expr(arena, left, stack_op, right))); + arg_stack.push( + env.arena + .alloc(new_op_call_expr(env, scope, left, stack_op, right)), + ); Step::Push(next_op) } @@ -1747,8 +1596,10 @@ fn binop_step<'a>( let right = arg_stack.pop().unwrap(); let left = arg_stack.pop().unwrap(); - arg_stack - .push(arena.alloc(new_op_call_expr(arena, left, stack_op, right))); + arg_stack.push( + env.arena + .alloc(new_op_call_expr(env, scope, left, stack_op, right)), + ); Step::Push(next_op) } @@ -1767,8 +1618,9 @@ fn binop_step<'a>( let bad_op = next_op; let right = arg_stack.pop().unwrap(); let left = arg_stack.pop().unwrap(); - let broken_expr = - arena.alloc(new_op_call_expr(arena, left, stack_op, right)); + let broken_expr = env + .arena + .alloc(new_op_call_expr(env, scope, left, stack_op, right)); let region = broken_expr.region; let data = roc_parse::ast::PrecedenceConflict { whole_region, @@ -1776,11 +1628,11 @@ fn binop_step<'a>( binop1: stack_op.value, binop2_position: bad_op.region.start(), binop2: bad_op.value, - expr: arena.alloc(broken_expr), + expr: env.arena.alloc(broken_expr), }; - let value = Expr::PrecedenceConflict(arena.alloc(data)); + let value = Expr::PrecedenceConflict(env.arena.alloc(data)); - Step::Error(arena.alloc(Loc { region, value })) + Step::Error(env.arena.alloc(Loc { region, value })) } _ => { diff --git a/crates/compiler/can/src/effect_module.rs b/crates/compiler/can/src/effect_module.rs deleted file mode 100644 index 91fdb5dd72..0000000000 --- a/crates/compiler/can/src/effect_module.rs +++ /dev/null @@ -1,1571 +0,0 @@ -use crate::annotation::IntroducedVariables; -use crate::def::Def; -use crate::expr::{AnnotatedMark, ClosureData, Declarations, Expr, Recursive, WhenBranchPattern}; -use crate::pattern::Pattern; -use crate::scope::Scope; -use roc_collections::{SendMap, VecMap, VecSet}; -use roc_module::called_via::CalledVia; -use roc_module::ident::TagName; -use roc_module::symbol::Symbol; -use roc_region::all::{Loc, Region}; -use roc_types::subs::{ExhaustiveMark, RedundantMark, VarStore, Variable}; -use roc_types::types::{AliasKind, LambdaSet, OptAbleType, OptAbleVar, Type, TypeExtension}; - -#[derive(Debug, Default, Clone, Copy)] -pub(crate) struct HostedGeneratedFunctions { - pub(crate) after: bool, - pub(crate) map: bool, - pub(crate) always: bool, - pub(crate) loop_: bool, - pub(crate) forever: bool, -} - -/// the Effects alias & associated functions -/// -/// A platform can define an Effect type in its header. It can have an arbitrary name -/// (e.g. Task, IO), but we'll call it an Effect in general. -/// -/// From that name, we generate an effect module, an effect alias, and some functions. -/// -/// The effect alias is implemented as -/// -/// Effect a := {} -> a -/// -/// For this alias we implement the functions specified in HostedGeneratedFunctions with the -/// standard implementation. -pub(crate) fn build_effect_builtins( - scope: &mut Scope, - effect_symbol: Symbol, - var_store: &mut VarStore, - exposed_symbols: &mut VecSet, - declarations: &mut Declarations, - generated_functions: HostedGeneratedFunctions, -) { - macro_rules! helper { - ($f:expr) => {{ - let (symbol, def) = $f(scope, effect_symbol, var_store); - - // make the outside world know this symbol exists - exposed_symbols.insert(symbol); - - def - }}; - } - - if generated_functions.after { - let def = helper!(build_effect_after); - declarations.push_def(def); - } - - // Effect.map : Effect a, (a -> b) -> Effect b - if generated_functions.map { - let def = helper!(build_effect_map); - declarations.push_def(def); - } - - // Effect.always : a -> Effect a - if generated_functions.always { - let def = helper!(build_effect_always); - declarations.push_def(def); - } - - // Effect.forever : Effect a -> Effect b - if generated_functions.forever { - let def = helper!(build_effect_forever); - declarations.push_def(def); - } - - // Effect.loop : a, (a -> Effect [Step a, Done b]) -> Effect b - if generated_functions.loop_ { - let def = helper!(build_effect_loop); - declarations.push_def(def); - } - - // Useful when working on functions in this module. By default symbols that we named do now - // show up with their name. We have to register them like below to make the names show up in - // debug prints - if false { - scope.register_debug_idents(); - } -} - -macro_rules! new_symbol { - ($scope:expr, $name:expr) => {{ - $scope.introduce($name.into(), Region::zero()).unwrap() - }}; -} - -fn build_effect_always( - scope: &mut Scope, - effect_symbol: Symbol, - var_store: &mut VarStore, -) -> (Symbol, Def) { - // Effect.always = \value -> @Effect \{} -> value - - let value_symbol = { - scope - .introduce("effect_always_value".into(), Region::zero()) - .unwrap() - }; - - let inner_closure_symbol = { - scope - .introduce("effect_always_inner".into(), Region::zero()) - .unwrap() - }; - - let always_symbol = { scope.introduce("always".into(), Region::zero()).unwrap() }; - - // \{} -> value - let const_closure = { - let arguments = vec![( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(empty_record_pattern(var_store)), - )]; - - let value_var = var_store.fresh(); - let body = Expr::Var(value_symbol, value_var); - - Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - return_type: var_store.fresh(), - name: inner_closure_symbol, - captured_symbols: vec![(value_symbol, value_var)], - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(body)), - }) - }; - - // \value -> @Effect \{} -> value - let (function_var, always_closure) = { - // `@Effect \{} -> value` - let (specialized_def_type, type_arguments, lambda_set_variables) = - build_fresh_opaque_variables(var_store); - let body = Expr::OpaqueRef { - opaque_var: var_store.fresh(), - name: effect_symbol, - argument: Box::new((var_store.fresh(), Loc::at_zero(const_closure))), - specialized_def_type, - type_arguments, - lambda_set_variables, - }; - - let arguments = vec![( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(Pattern::Identifier(value_symbol)), - )]; - - let function_var = var_store.fresh(); - let closure = Expr::Closure(ClosureData { - function_type: function_var, - closure_type: var_store.fresh(), - return_type: var_store.fresh(), - name: always_symbol, - captured_symbols: Vec::new(), - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(body)), - }); - - (function_var, closure) - }; - - let mut introduced_variables = IntroducedVariables::default(); - - let signature = { - // Effect.always : a -> Effect a - let var_a = var_store.fresh(); - introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); - - let effect_a = build_effect_opaque( - effect_symbol, - var_a, - Type::Variable(var_a), - var_store, - &mut introduced_variables, - ); - - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - - Type::Function( - vec![Type::Variable(var_a)], - Box::new(Type::Variable(closure_var)), - Box::new(effect_a), - ) - }; - - let def_annotation = crate::def::Annotation { - signature, - introduced_variables, - aliases: VecMap::default(), - region: Region::zero(), - }; - - let pattern = Pattern::Identifier(always_symbol); - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(always_symbol, function_var); - let def = Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(always_closure), - expr_var: function_var, - pattern_vars, - annotation: Some(def_annotation), - }; - - (always_symbol, def) -} - -fn build_effect_map( - scope: &mut Scope, - effect_symbol: Symbol, - var_store: &mut VarStore, -) -> (Symbol, Def) { - // Effect.map = \@Effect thunk, mapper -> @Effect \{} -> mapper (thunk {}) - - let thunk_symbol = { - scope - .introduce("effect_map_thunk".into(), Region::zero()) - .unwrap() - }; - let thunk_var = var_store.fresh(); - - let mapper_symbol = { - scope - .introduce("effect_map_mapper".into(), Region::zero()) - .unwrap() - }; - let mapper_var = var_store.fresh(); - - let map_symbol = { scope.introduce("map".into(), Region::zero()).unwrap() }; - - // `thunk {}` - let force_thunk_call = { - let boxed = ( - thunk_var, - Loc::at_zero(Expr::Var(thunk_symbol, thunk_var)), - var_store.fresh(), - var_store.fresh(), - ); - - let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; - Expr::Call(Box::new(boxed), arguments, CalledVia::Space) - }; - - // `toEffect (thunk {})` - let mapper_call = { - let boxed = ( - mapper_var, - Loc::at_zero(Expr::Var(mapper_symbol, mapper_var)), - var_store.fresh(), - var_store.fresh(), - ); - - let arguments = vec![(var_store.fresh(), Loc::at_zero(force_thunk_call))]; - Expr::Call(Box::new(boxed), arguments, CalledVia::Space) - }; - - let inner_closure_symbol = { - scope - .introduce("effect_map_inner".into(), Region::zero()) - .unwrap() - }; - - // \{} -> mapper (thunk {}) - let inner_closure = { - let arguments = vec![( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(empty_record_pattern(var_store)), - )]; - - Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - return_type: var_store.fresh(), - name: inner_closure_symbol, - captured_symbols: vec![ - (thunk_symbol, var_store.fresh()), - (mapper_symbol, var_store.fresh()), - ], - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(mapper_call)), - }) - }; - - // \@Effect thunk, mapper - let (specialized_def_type, type_arguments, lambda_set_variables) = - build_fresh_opaque_variables(var_store); - let arguments = vec![ - ( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(Pattern::UnwrappedOpaque { - opaque: effect_symbol, - whole_var: var_store.fresh(), - argument: Box::new(( - var_store.fresh(), - Loc::at_zero(Pattern::Identifier(thunk_symbol)), - )), - specialized_def_type, - type_arguments, - lambda_set_variables, - }), - ), - ( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(Pattern::Identifier(mapper_symbol)), - ), - ]; - - // `@Effect \{} -> (mapper (thunk {}))` - let (specialized_def_type, type_arguments, lambda_set_variables) = - build_fresh_opaque_variables(var_store); - let body = Expr::OpaqueRef { - opaque_var: var_store.fresh(), - name: effect_symbol, - argument: Box::new((var_store.fresh(), Loc::at_zero(inner_closure))), - specialized_def_type, - type_arguments, - lambda_set_variables, - }; - - let function_var = var_store.fresh(); - let map_closure = Expr::Closure(ClosureData { - function_type: function_var, - closure_type: var_store.fresh(), - return_type: var_store.fresh(), - name: map_symbol, - captured_symbols: Vec::new(), - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(body)), - }); - - let mut introduced_variables = IntroducedVariables::default(); - - let signature = { - // Effect.map : Effect a, (a -> b) -> Effect b - let var_a = var_store.fresh(); - let var_b = var_store.fresh(); - - introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); - introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); - - let effect_a = build_effect_opaque( - effect_symbol, - var_a, - Type::Variable(var_a), - var_store, - &mut introduced_variables, - ); - - let effect_b = build_effect_opaque( - effect_symbol, - var_b, - Type::Variable(var_b), - var_store, - &mut introduced_variables, - ); - - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - let a_to_b = { - Type::Function( - vec![Type::Variable(var_a)], - Box::new(Type::Variable(closure_var)), - Box::new(Type::Variable(var_b)), - ) - }; - - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - Type::Function( - vec![effect_a, a_to_b], - Box::new(Type::Variable(closure_var)), - Box::new(effect_b), - ) - }; - - let def_annotation = crate::def::Annotation { - signature, - introduced_variables, - aliases: VecMap::default(), - region: Region::zero(), - }; - - let pattern = Pattern::Identifier(map_symbol); - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(map_symbol, function_var); - let def = Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(map_closure), - expr_var: function_var, - pattern_vars, - annotation: Some(def_annotation), - }; - - (map_symbol, def) -} - -fn force_thunk(expr: Expr, thunk_var: Variable, var_store: &mut VarStore) -> Expr { - let boxed = ( - thunk_var, - Loc::at_zero(expr), - var_store.fresh(), - var_store.fresh(), - ); - - let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; - Expr::Call(Box::new(boxed), arguments, CalledVia::Space) -} - -fn build_effect_after( - scope: &mut Scope, - effect_opaque_symbol: Symbol, - var_store: &mut VarStore, -) -> (Symbol, Def) { - // Effect.after = \@Effect effect, toEffect -> - // @Effect \{} -> - // when toEffect (effect {}) is - // @Effect thunk -> thunk {} - - let thunk_symbol = new_symbol!(scope, "effect_after_thunk"); - - let effect_symbol = new_symbol!(scope, "effect_after_effect"); - let to_effect_symbol = new_symbol!(scope, "effect_after_toEffect"); - let after_symbol = new_symbol!(scope, "after"); - let outer_closure_symbol = new_symbol!(scope, "effect_after_inner"); - - // `effect {}` - let force_effect_var = var_store.fresh(); - let force_effect_call = force_thunk( - Expr::Var(effect_symbol, force_effect_var), - force_effect_var, - var_store, - ); - - // `toEffect (effect {})` - let to_effect_var = var_store.fresh(); - let to_effect_call = { - let boxed = ( - to_effect_var, - Loc::at_zero(Expr::Var(to_effect_symbol, to_effect_var)), - var_store.fresh(), - var_store.fresh(), - ); - - let arguments = vec![(var_store.fresh(), Loc::at_zero(force_effect_call))]; - Expr::Call(Box::new(boxed), arguments, CalledVia::Space) - }; - - // let @Effect thunk = toEffect (effect {}) in thunk {} - let let_effect_thunk = { - // `thunk {}` - let force_inner_thunk_var = var_store.fresh(); - let force_inner_thunk_call = force_thunk( - Expr::Var(thunk_symbol, force_inner_thunk_var), - force_inner_thunk_var, - var_store, - ); - - let (specialized_def_type, type_arguments, lambda_set_variables) = - build_fresh_opaque_variables(var_store); - - let pattern = Pattern::UnwrappedOpaque { - whole_var: var_store.fresh(), - opaque: effect_opaque_symbol, - argument: Box::new(( - var_store.fresh(), - Loc::at_zero(Pattern::Identifier(thunk_symbol)), - )), - specialized_def_type, - type_arguments, - lambda_set_variables, - }; - let pattern = WhenBranchPattern { - pattern: Loc::at_zero(pattern), - degenerate: false, - }; - - let branches = vec![crate::expr::WhenBranch { - guard: None, - value: Loc::at_zero(force_inner_thunk_call), - patterns: vec![pattern], - redundant: RedundantMark::new(var_store), - }]; - - Expr::When { - cond_var: var_store.fresh(), - branches_cond_var: var_store.fresh(), - expr_var: var_store.fresh(), - region: Region::zero(), - loc_cond: Box::new(Loc::at_zero(to_effect_call)), - branches, - exhaustive: ExhaustiveMark::new(var_store), - } - }; - - // @Effect \{} -> when toEffect (effect {}) is @Effect thunk -> thunk {} - let outer_effect = wrap_in_effect_thunk( - let_effect_thunk, - effect_opaque_symbol, - outer_closure_symbol, - vec![effect_symbol, to_effect_symbol], - var_store, - ); - - scope.register_debug_idents(); - - let (specialized_def_type, type_arguments, lambda_set_variables) = - build_fresh_opaque_variables(var_store); - - let arguments = vec![ - ( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(Pattern::UnwrappedOpaque { - opaque: effect_opaque_symbol, - whole_var: var_store.fresh(), - argument: Box::new(( - var_store.fresh(), - Loc::at_zero(Pattern::Identifier(effect_symbol)), - )), - specialized_def_type, - type_arguments, - lambda_set_variables, - }), - ), - ( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(Pattern::Identifier(to_effect_symbol)), - ), - ]; - - let function_var = var_store.fresh(); - let after_closure = Expr::Closure(ClosureData { - function_type: function_var, - closure_type: var_store.fresh(), - return_type: var_store.fresh(), - name: after_symbol, - captured_symbols: Vec::new(), - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(outer_effect)), - }); - - let mut introduced_variables = IntroducedVariables::default(); - - let signature = { - let var_a = var_store.fresh(); - let var_b = var_store.fresh(); - - introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); - introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); - - let effect_a = build_effect_opaque( - effect_opaque_symbol, - var_a, - Type::Variable(var_a), - var_store, - &mut introduced_variables, - ); - - let effect_b1 = build_effect_opaque( - effect_opaque_symbol, - var_b, - Type::Variable(var_b), - var_store, - &mut introduced_variables, - ); - - // we need a second b2 to give it a unique lambda set variable - let effect_b2 = build_effect_opaque( - effect_opaque_symbol, - var_b, - Type::Variable(var_b), - var_store, - &mut introduced_variables, - ); - - let closure_var = var_store.fresh(); - introduced_variables.insert_lambda_set(closure_var); - let a_to_effect_b = Type::Function( - vec![Type::Variable(var_a)], - Box::new(Type::Variable(closure_var)), - Box::new(effect_b1), - ); - - let closure_var = var_store.fresh(); - introduced_variables.insert_lambda_set(closure_var); - Type::Function( - vec![effect_a, a_to_effect_b], - Box::new(Type::Variable(closure_var)), - Box::new(effect_b2), - ) - }; - - let def_annotation = crate::def::Annotation { - signature, - introduced_variables, - aliases: VecMap::default(), - region: Region::zero(), - }; - - let pattern = Pattern::Identifier(after_symbol); - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(after_symbol, function_var); - let def = Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(after_closure), - expr_var: function_var, - pattern_vars, - annotation: Some(def_annotation), - }; - - (after_symbol, def) -} - -/// turn `value` into `@Effect \{} -> value` -fn wrap_in_effect_thunk( - body: Expr, - effect_symbol: Symbol, - closure_name: Symbol, - captured_symbols: Vec, - var_store: &mut VarStore, -) -> Expr { - let captured_symbols: Vec<_> = captured_symbols - .into_iter() - .map(|x| (x, var_store.fresh())) - .collect(); - - // \{} -> body - let const_closure = { - let arguments = vec![( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(empty_record_pattern(var_store)), - )]; - - Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - return_type: var_store.fresh(), - name: closure_name, - // captured_symbols: vec![(value_symbol, var_store.fresh())], - captured_symbols, - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(body)), - }) - }; - - // `@Effect \{} -> value` - let (specialized_def_type, type_arguments, lambda_set_variables) = - build_fresh_opaque_variables(var_store); - Expr::OpaqueRef { - opaque_var: var_store.fresh(), - name: effect_symbol, - argument: Box::new((var_store.fresh(), Loc::at_zero(const_closure))), - specialized_def_type, - type_arguments, - lambda_set_variables, - } -} - -/// given `effect : Effect a`, unwrap the thunk and force it, giving a value of type `a` -fn force_effect( - effect: Expr, - effect_symbol: Symbol, - thunk_symbol: Symbol, - var_store: &mut VarStore, -) -> Expr { - let whole_var = var_store.fresh(); - - let thunk_var = var_store.fresh(); - - let (specialized_def_type, type_arguments, lambda_set_variables) = - build_fresh_opaque_variables(var_store); - let pattern = Pattern::UnwrappedOpaque { - whole_var, - opaque: effect_symbol, - argument: Box::new((thunk_var, Loc::at_zero(Pattern::Identifier(thunk_symbol)))), - specialized_def_type, - type_arguments, - lambda_set_variables, - }; - - let pattern_vars = SendMap::default(); - // pattern_vars.insert(thunk_symbol, thunk_var); - - let def = Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(effect), - expr_var: var_store.fresh(), - pattern_vars, - annotation: None, - }; - - let ret_var = var_store.fresh(); - - let force_thunk_call = { - let thunk_var = var_store.fresh(); - let boxed = ( - thunk_var, - Loc::at_zero(Expr::Var(thunk_symbol, thunk_var)), - var_store.fresh(), - ret_var, - ); - - let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; - let call = Expr::Call(Box::new(boxed), arguments, CalledVia::Space); - - Loc::at_zero(call) - }; - - Expr::LetNonRec(Box::new(def), Box::new(force_thunk_call)) -} - -fn build_effect_forever( - scope: &mut Scope, - effect_symbol: Symbol, - var_store: &mut VarStore, -) -> (Symbol, Def) { - // morally - // - // Effect.forever = \effect -> Effect.after effect (\_ -> Effect.forever effect) - // - // Here we inline the `Effect.after`, and get - // - // Effect.forever : Effect a -> Effect b - // Effect.forever = \effect -> - // @Effect \{} -> - // @Effect thunk1 = effect - // _ = thunk1 {} - // @Effect thunk2 = Effect.forever effect - // thunk2 {} - // - // We then rely on our defunctionalization to turn this into a tail-recursive loop. - // First the `@Effect` wrapper melts away - // - // Effect.forever : ({} -> a) -> ({} -> b) - // Effect.forever = \effect -> - // \{} -> - // thunk1 = effect - // _ = thunk1 {} - // thunk2 = Effect.forever effect - // thunk2 {} - // - // Then we defunctionalize - // - // foreverInner = \{}, { effect } -> - // thunk1 = effect - // _ = thunk1 {} - // thunk2 = Effect.forever effect - // thunk2 {} - // - // Effect.forever : [C foreverInner { effect : T }] - // Effect.forever = \effect -> - // C { effect } - // - // And we have to adjust the call - // - // foreverInner = \{}, { effect } -> - // thunk1 = effect - // _ = thunk1 {} - // thunk2 = Effect.forever effect - // when thunk2 is - // C env -> foreverInner {} env.effect - // - // Making `foreverInner` perfectly tail-call optimizable - - let forever_symbol = { scope.introduce("forever".into(), Region::zero()).unwrap() }; - - let effect = { scope.introduce("effect".into(), Region::zero()).unwrap() }; - - let body = build_effect_forever_body(scope, effect_symbol, forever_symbol, effect, var_store); - - let arguments = vec![( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(Pattern::Identifier(effect)), - )]; - - let function_var = var_store.fresh(); - let after_closure = Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - return_type: var_store.fresh(), - name: forever_symbol, - captured_symbols: Vec::new(), - recursive: Recursive::Recursive, - arguments, - loc_body: Box::new(Loc::at_zero(body)), - }); - - let mut introduced_variables = IntroducedVariables::default(); - - let signature = { - let var_a = var_store.fresh(); - let var_b = var_store.fresh(); - - introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); - introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); - - let effect_a = build_effect_opaque( - effect_symbol, - var_a, - Type::Variable(var_a), - var_store, - &mut introduced_variables, - ); - - let effect_b = build_effect_opaque( - effect_symbol, - var_b, - Type::Variable(var_b), - var_store, - &mut introduced_variables, - ); - - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - - Type::Function( - vec![effect_a], - Box::new(Type::Variable(closure_var)), - Box::new(effect_b), - ) - }; - - let def_annotation = crate::def::Annotation { - signature, - introduced_variables, - aliases: VecMap::default(), - region: Region::zero(), - }; - - let pattern = Pattern::Identifier(forever_symbol); - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(forever_symbol, function_var); - let def = Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(after_closure), - expr_var: function_var, - pattern_vars, - annotation: Some(def_annotation), - }; - - (forever_symbol, def) -} - -fn build_effect_forever_body( - scope: &mut Scope, - effect_symbol: Symbol, - forever_symbol: Symbol, - effect: Symbol, - var_store: &mut VarStore, -) -> Expr { - let closure_name = { - scope - .introduce("forever_inner".into(), Region::zero()) - .unwrap() - }; - - let inner_body = - build_effect_forever_inner_body(scope, effect_symbol, forever_symbol, effect, var_store); - - let captured_symbols = vec![effect]; - wrap_in_effect_thunk( - inner_body, - effect_symbol, - closure_name, - captured_symbols, - var_store, - ) -} - -fn build_effect_forever_inner_body( - scope: &mut Scope, - effect_symbol: Symbol, - forever_symbol: Symbol, - effect: Symbol, - var_store: &mut VarStore, -) -> Expr { - let thunk1_var = var_store.fresh(); - let thunk1_symbol = { scope.introduce("thunk1".into(), Region::zero()).unwrap() }; - - let thunk2_symbol = { scope.introduce("thunk2".into(), Region::zero()).unwrap() }; - - // @Effect thunk1 = effect - let thunk_from_effect = { - let whole_var = var_store.fresh(); - - let thunk_var = var_store.fresh(); - - let (specialized_def_type, type_arguments, lambda_set_variables) = - build_fresh_opaque_variables(var_store); - let pattern = Pattern::UnwrappedOpaque { - whole_var, - opaque: effect_symbol, - argument: Box::new((thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))), - specialized_def_type, - type_arguments, - lambda_set_variables, - }; - - let pattern_vars = SendMap::default(); - - Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(Expr::Var(effect, var_store.fresh())), - expr_var: var_store.fresh(), - pattern_vars, - annotation: None, - } - }; - - // thunk1 {} - let force_thunk_call = { - let ret_var = var_store.fresh(); - let boxed = ( - thunk1_var, - Loc::at_zero(Expr::Var(thunk1_symbol, thunk1_var)), - var_store.fresh(), - ret_var, - ); - - let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; - let call = Expr::Call(Box::new(boxed), arguments, CalledVia::Space); - - Loc::at_zero(call) - }; - - // _ = thunk1 {} - let force_thunk1 = Def { - loc_pattern: Loc::at_zero(Pattern::Underscore), - loc_expr: force_thunk_call, - expr_var: var_store.fresh(), - pattern_vars: Default::default(), - annotation: None, - }; - - // recursive call `forever effect` - let forever_effect = { - let boxed = ( - var_store.fresh(), - Loc::at_zero(Expr::Var(forever_symbol, var_store.fresh())), - var_store.fresh(), - var_store.fresh(), - ); - - let effect_var = var_store.fresh(); - let arguments = vec![(effect_var, Loc::at_zero(Expr::Var(effect, effect_var)))]; - Expr::Call(Box::new(boxed), arguments, CalledVia::Space) - }; - - // ``` - // @Effect thunk2 = forever effect - // thunk2 {} - // ``` - let force_thunk2 = Loc::at_zero(force_effect( - forever_effect, - effect_symbol, - thunk2_symbol, - var_store, - )); - - Expr::LetNonRec( - Box::new(thunk_from_effect), - Box::new(Loc::at_zero(Expr::LetNonRec( - Box::new(force_thunk1), - Box::new(force_thunk2), - ))), - ) -} - -fn build_effect_loop( - scope: &mut Scope, - effect_symbol: Symbol, - var_store: &mut VarStore, -) -> (Symbol, Def) { - let loop_symbol = new_symbol!(scope, "loop"); - let state_symbol = new_symbol!(scope, "state"); - let step_symbol = new_symbol!(scope, "step"); - - let body = build_effect_loop_body( - scope, - effect_symbol, - loop_symbol, - state_symbol, - step_symbol, - var_store, - ); - - let arguments = vec![ - ( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(Pattern::Identifier(state_symbol)), - ), - ( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(Pattern::Identifier(step_symbol)), - ), - ]; - - let function_var = var_store.fresh(); - let after_closure = Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - return_type: var_store.fresh(), - name: loop_symbol, - captured_symbols: Vec::new(), - recursive: Recursive::Recursive, - arguments, - loc_body: Box::new(Loc::at_zero(body)), - }); - - let mut introduced_variables = IntroducedVariables::default(); - - let signature = { - let var_a = var_store.fresh(); - let var_b = var_store.fresh(); - - introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); - introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); - - let effect_b = build_effect_opaque( - effect_symbol, - var_b, - Type::Variable(var_b), - var_store, - &mut introduced_variables, - ); - - let state_type = { - let step_tag_name = TagName("Step".into()); - let done_tag_name = TagName("Done".into()); - - Type::TagUnion( - vec![ - (step_tag_name, vec![Type::Variable(var_a)]), - (done_tag_name, vec![Type::Variable(var_b)]), - ], - TypeExtension::Closed, - ) - }; - - let effect_state_type = { - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - - let actual = Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(state_type.clone()), - ); - - Type::Alias { - symbol: effect_symbol, - type_arguments: vec![OptAbleType::unbound(state_type)], - lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable( - closure_var, - ))], - infer_ext_in_output_types: vec![], - actual: Box::new(actual), - kind: AliasKind::Opaque, - } - }; - - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - - let step_type = Type::Function( - vec![Type::Variable(var_a)], - Box::new(Type::Variable(closure_var)), - Box::new(effect_state_type), - ); - - let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - - Type::Function( - vec![Type::Variable(var_a), step_type], - Box::new(Type::Variable(closure_var)), - Box::new(effect_b), - ) - }; - - let def_annotation = crate::def::Annotation { - signature, - introduced_variables, - aliases: VecMap::default(), - region: Region::zero(), - }; - - let pattern = Pattern::Identifier(loop_symbol); - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(loop_symbol, function_var); - let def = Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(after_closure), - expr_var: function_var, - pattern_vars, - annotation: Some(def_annotation), - }; - - (loop_symbol, def) -} - -fn build_effect_loop_body( - scope: &mut Scope, - effect_symbol: Symbol, - loop_symbol: Symbol, - state_symbol: Symbol, - step_symbol: Symbol, - var_store: &mut VarStore, -) -> Expr { - let closure_name = { - scope - .introduce("loop_inner".into(), Region::zero()) - .unwrap() - }; - - let inner_body = build_effect_loop_inner_body( - scope, - effect_symbol, - loop_symbol, - state_symbol, - step_symbol, - var_store, - ); - - let captured_symbols = vec![state_symbol, step_symbol]; - wrap_in_effect_thunk( - inner_body, - effect_symbol, - closure_name, - captured_symbols, - var_store, - ) -} - -fn applied_tag_pattern( - tag_name: TagName, - argument_symbols: &[Symbol], - var_store: &mut VarStore, -) -> Pattern { - let arguments = argument_symbols - .iter() - .map(|s| { - let pattern = Pattern::Identifier(*s); - - (var_store.fresh(), Loc::at_zero(pattern)) - }) - .collect(); - - Pattern::AppliedTag { - ext_var: var_store.fresh(), - whole_var: var_store.fresh(), - tag_name, - arguments, - } -} - -fn build_effect_loop_inner_body( - scope: &mut Scope, - effect_symbol: Symbol, - loop_symbol: Symbol, - state_symbol: Symbol, - step_symbol: Symbol, - var_store: &mut VarStore, -) -> Expr { - let thunk1_symbol = new_symbol!(scope, "thunk3"); - let thunk2_symbol = new_symbol!(scope, "thunk4"); - - let new_state_symbol = new_symbol!(scope, "newState"); - let done_symbol = new_symbol!(scope, "done"); - - // Effect thunk1 = step state - let thunk_from_effect = { - let whole_var = var_store.fresh(); - - let thunk_var = var_store.fresh(); - - let (specialized_def_type, type_arguments, lambda_set_variables) = - build_fresh_opaque_variables(var_store); - let pattern = Pattern::UnwrappedOpaque { - whole_var, - opaque: effect_symbol, - argument: Box::new((thunk_var, Loc::at_zero(Pattern::Identifier(thunk1_symbol)))), - specialized_def_type, - type_arguments, - lambda_set_variables, - }; - - let pattern_vars = SendMap::default(); - - // `step state` - let rhs = { - let step_var = var_store.fresh(); - let boxed = ( - step_var, - Loc::at_zero(Expr::Var(step_symbol, step_var)), - var_store.fresh(), - var_store.fresh(), - ); - - let state_var = var_store.fresh(); - let arguments = vec![(state_var, Loc::at_zero(Expr::Var(state_symbol, state_var)))]; - Expr::Call(Box::new(boxed), arguments, CalledVia::Space) - }; - - Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(rhs), - expr_var: var_store.fresh(), - pattern_vars, - annotation: None, - } - }; - - // thunk1 {} - let force_thunk_call = { - let thunk1_var = var_store.fresh(); - let ret_var = var_store.fresh(); - let boxed = ( - thunk1_var, - Loc::at_zero(Expr::Var(thunk1_symbol, thunk1_var)), - var_store.fresh(), - ret_var, - ); - - let arguments = vec![(var_store.fresh(), Loc::at_zero(Expr::EmptyRecord))]; - let call = Expr::Call(Box::new(boxed), arguments, CalledVia::Space); - - Loc::at_zero(call) - }; - - // recursive call `loop newState step` - let loop_new_state_step = { - let loop_var = var_store.fresh(); - let boxed = ( - loop_var, - Loc::at_zero(Expr::Var(loop_symbol, loop_var)), - var_store.fresh(), - var_store.fresh(), - ); - - let new_state_var = var_store.fresh(); - let step_var = var_store.fresh(); - let arguments = vec![ - ( - new_state_var, - Loc::at_zero(Expr::Var(new_state_symbol, new_state_var)), - ), - (step_var, Loc::at_zero(Expr::Var(step_symbol, step_var))), - ]; - Expr::Call(Box::new(boxed), arguments, CalledVia::Space) - }; - - // ``` - // @Effect thunk2 = loop effect - // thunk2 {} - // ``` - let force_thunk2 = force_effect(loop_new_state_step, effect_symbol, thunk2_symbol, var_store); - - let step_branch = { - let step_tag_name = TagName("Step".into()); - - let step_pattern = applied_tag_pattern(step_tag_name, &[new_state_symbol], var_store); - let step_pattern = WhenBranchPattern { - pattern: Loc::at_zero(step_pattern), - degenerate: false, - }; - - crate::expr::WhenBranch { - patterns: vec![step_pattern], - value: Loc::at_zero(force_thunk2), - guard: None, - redundant: RedundantMark::new(var_store), - } - }; - - let done_branch = { - let done_tag_name = TagName("Done".into()); - let done_pattern = applied_tag_pattern(done_tag_name, &[done_symbol], var_store); - let done_pattern = WhenBranchPattern { - pattern: Loc::at_zero(done_pattern), - degenerate: false, - }; - - crate::expr::WhenBranch { - patterns: vec![done_pattern], - value: Loc::at_zero(Expr::Var(done_symbol, var_store.fresh())), - guard: None, - redundant: RedundantMark::new(var_store), - } - }; - - let branches = vec![step_branch, done_branch]; - - let match_on_force_thunk1 = Expr::When { - cond_var: var_store.fresh(), - expr_var: var_store.fresh(), - region: Region::zero(), - loc_cond: Box::new(force_thunk_call), - branches, - branches_cond_var: var_store.fresh(), - exhaustive: ExhaustiveMark::new(var_store), - }; - - Expr::LetNonRec( - Box::new(thunk_from_effect), - Box::new(Loc::at_zero(match_on_force_thunk1)), - ) -} - -pub fn build_host_exposed_def( - scope: &mut Scope, - symbol: Symbol, - ident: &str, - effect_symbol: Symbol, - var_store: &mut VarStore, - annotation: crate::annotation::Annotation, -) -> Def { - let expr_var = var_store.fresh(); - let pattern = Pattern::Identifier(symbol); - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(symbol, expr_var); - - let mut arguments: Vec<(Variable, AnnotatedMark, Loc)> = Vec::new(); - let mut linked_symbol_arguments: Vec<(Variable, Expr)> = Vec::new(); - let mut captured_symbols: Vec<(Symbol, Variable)> = Vec::new(); - - let crate::annotation::Annotation { - introduced_variables, - typ, - aliases, - .. - } = annotation; - - let def_body = { - match typ.shallow_structural_dealias() { - Type::Function(args, _, _) => { - for i in 0..args.len() { - let name = format!("closure_arg_{ident}_{i}"); - - let arg_symbol = { - let ident = name.clone().into(); - scope.introduce(ident, Region::zero()).unwrap() - }; - - let arg_var = var_store.fresh(); - - arguments.push(( - arg_var, - AnnotatedMark::new(var_store), - Loc::at_zero(Pattern::Identifier(arg_symbol)), - )); - - captured_symbols.push((arg_symbol, arg_var)); - linked_symbol_arguments.push((arg_var, Expr::Var(arg_symbol, arg_var))); - } - - let foreign_symbol_name = format!("roc_fx_{ident}"); - let low_level_call = Expr::ForeignCall { - foreign_symbol: foreign_symbol_name.into(), - args: linked_symbol_arguments, - ret_var: var_store.fresh(), - }; - - let effect_closure_symbol = { - let name = format!("effect_closure_{ident}"); - - let ident = name.into(); - scope.introduce(ident, Region::zero()).unwrap() - }; - - let effect_closure = Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - return_type: var_store.fresh(), - name: effect_closure_symbol, - captured_symbols, - recursive: Recursive::NotRecursive, - arguments: vec![( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(empty_record_pattern(var_store)), - )], - loc_body: Box::new(Loc::at_zero(low_level_call)), - }); - - let (specialized_def_type, type_arguments, lambda_set_variables) = - build_fresh_opaque_variables(var_store); - let body = Expr::OpaqueRef { - opaque_var: var_store.fresh(), - name: effect_symbol, - argument: Box::new((var_store.fresh(), Loc::at_zero(effect_closure))), - specialized_def_type, - type_arguments, - lambda_set_variables, - }; - - Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - return_type: var_store.fresh(), - name: symbol, - captured_symbols: std::vec::Vec::new(), - recursive: Recursive::NotRecursive, - arguments, - loc_body: Box::new(Loc::at_zero(body)), - }) - } - _ => { - // not a function - - let foreign_symbol_name = format!("roc_fx_{ident}"); - let low_level_call = Expr::ForeignCall { - foreign_symbol: foreign_symbol_name.into(), - args: linked_symbol_arguments, - ret_var: var_store.fresh(), - }; - - let effect_closure_symbol = { - let name = format!("effect_closure_{ident}"); - - let ident = name.into(); - scope.introduce(ident, Region::zero()).unwrap() - }; - - let empty_record_pattern = Pattern::RecordDestructure { - whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - destructs: vec![], - }; - - let effect_closure = Expr::Closure(ClosureData { - function_type: var_store.fresh(), - closure_type: var_store.fresh(), - return_type: var_store.fresh(), - name: effect_closure_symbol, - captured_symbols, - recursive: Recursive::NotRecursive, - arguments: vec![( - var_store.fresh(), - AnnotatedMark::new(var_store), - Loc::at_zero(empty_record_pattern), - )], - loc_body: Box::new(Loc::at_zero(low_level_call)), - }); - - let (specialized_def_type, type_arguments, lambda_set_variables) = - build_fresh_opaque_variables(var_store); - Expr::OpaqueRef { - opaque_var: var_store.fresh(), - name: effect_symbol, - argument: Box::new((var_store.fresh(), Loc::at_zero(effect_closure))), - specialized_def_type, - type_arguments, - lambda_set_variables, - } - } - } - }; - - let def_annotation = crate::def::Annotation { - signature: typ, - introduced_variables, - aliases, - region: Region::zero(), - }; - - Def { - loc_pattern: Loc::at_zero(pattern), - loc_expr: Loc::at_zero(def_body), - expr_var, - pattern_vars, - annotation: Some(def_annotation), - } -} - -pub fn build_effect_actual(a_type: Type, var_store: &mut VarStore) -> Type { - let closure_var = var_store.fresh(); - - Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(a_type), - ) -} - -/// Effect a := {} -> a -fn build_effect_opaque( - effect_symbol: Symbol, - a_var: Variable, - a_type: Type, - var_store: &mut VarStore, - introduced_variables: &mut IntroducedVariables, -) -> Type { - let closure_var = var_store.fresh(); - // introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - introduced_variables.insert_lambda_set(closure_var); - - let actual = Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(a_type), - ); - - Type::Alias { - symbol: effect_symbol, - type_arguments: vec![OptAbleType::unbound(Type::Variable(a_var))], - lambda_set_variables: vec![roc_types::types::LambdaSet(Type::Variable(closure_var))], - infer_ext_in_output_types: vec![], - actual: Box::new(actual), - kind: AliasKind::Opaque, - } -} - -fn build_fresh_opaque_variables( - var_store: &mut VarStore, -) -> (Box, Vec, Vec) { - let closure_var = var_store.fresh(); - - // NB: if there are bugs, check whether not introducing variables is a problem! - // introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); - - let a_var = var_store.fresh(); - let actual = Type::Function( - vec![Type::EmptyRec], - Box::new(Type::Variable(closure_var)), - Box::new(Type::Variable(a_var)), - ); - let type_arguments = vec![OptAbleVar { - var: a_var, - opt_abilities: None, - }]; - let lambda_set_variables = vec![roc_types::types::LambdaSet(Type::Variable(closure_var))]; - - (Box::new(actual), type_arguments, lambda_set_variables) -} - -#[inline(always)] -fn empty_record_pattern(var_store: &mut VarStore) -> Pattern { - Pattern::RecordDestructure { - whole_var: var_store.fresh(), - ext_var: var_store.fresh(), - destructs: vec![], - } -} diff --git a/crates/compiler/can/src/env.rs b/crates/compiler/can/src/env.rs index a5f72fd906..3b996648c9 100644 --- a/crates/compiler/can/src/env.rs +++ b/crates/compiler/can/src/env.rs @@ -7,7 +7,8 @@ use roc_collections::{MutMap, VecSet}; use roc_module::ident::{Ident, ModuleName}; use roc_module::symbol::{IdentIdsByModule, ModuleId, PQModuleName, PackageModuleIds, Symbol}; use roc_problem::can::{Problem, RuntimeError}; -use roc_region::all::{Loc, Region}; +use roc_region::all::{LineInfo, Loc, Region}; +use roc_types::subs::Variable; /// The canonicalization environment for a particular module. pub struct Env<'a> { @@ -38,14 +39,24 @@ pub struct Env<'a> { pub top_level_symbols: VecSet, + pub home_params_record: Option<(Symbol, Variable)>, + pub arena: &'a Bump, pub opt_shorthand: Option<&'a str>, + + pub src: &'a str, + + /// Lazily calculated line info. This data is only needed if the code contains calls to `dbg`, + /// otherwise we can leave it as `None` and never pay the cost of scanning the source an extra + /// time. + line_info: &'a mut Option, } impl<'a> Env<'a> { pub fn new( arena: &'a Bump, + src: &'a str, home: ModuleId, module_path: &'a Path, dep_idents: &'a IdentIdsByModule, @@ -54,6 +65,7 @@ impl<'a> Env<'a> { ) -> Env<'a> { Env { arena, + src, home, module_path, dep_idents, @@ -64,7 +76,9 @@ impl<'a> Env<'a> { qualified_type_lookups: VecSet::default(), tailcallable_symbol: None, top_level_symbols: VecSet::default(), + home_params_record: None, opt_shorthand, + line_info: arena.alloc(None), } } @@ -215,4 +229,11 @@ impl<'a> Env<'a> { pub fn problem(&mut self, problem: Problem) { self.problems.push(problem) } + + pub fn line_info(&mut self) -> &LineInfo { + if self.line_info.is_none() { + *self.line_info = Some(LineInfo::new(self.src)); + } + self.line_info.as_ref().unwrap() + } } diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs index d08d6efd66..e3d1576dfc 100644 --- a/crates/compiler/can/src/expr.rs +++ b/crates/compiler/can/src/expr.rs @@ -18,7 +18,7 @@ use roc_error_macros::internal_error; use roc_module::called_via::CalledVia; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; -use roc_module::symbol::{ModuleId, Symbol}; +use roc_module::symbol::{IdentId, ModuleId, Symbol}; use roc_parse::ast::{self, Defs, PrecedenceConflict, StrLiteral}; use roc_parse::ident::Accessor; use roc_parse::pattern::PatternType::*; @@ -111,8 +111,9 @@ pub enum Expr { /// Like Var, but from a module with params ParamsVar { symbol: Symbol, - params: Symbol, var: Variable, + params_symbol: Symbol, + params_var: Variable, }, AbilityMember( /// Actual member name @@ -320,8 +321,9 @@ impl Expr { &Self::Var(sym, _) => Category::Lookup(sym), &Self::ParamsVar { symbol, - params: _, var: _, + params_symbol: _, + params_var: _, } => Category::Lookup(symbol), &Self::AbilityMember(sym, _, _) => Category::Lookup(sym), Self::When { .. } => Category::When, @@ -1207,8 +1209,20 @@ pub fn canonicalize_expr<'a>( output, ) } - ast::Expr::Dbg(_, _) => { - internal_error!("Dbg should have been desugared by now") + ast::Expr::Dbg => { + // Dbg was not desugared as either part of an `Apply` or a `Pizza` binop, so it's + // invalid. + env.problem(Problem::UnappliedDbg { region }); + + let invalid_dbg_expr = crate::desugar::desugar_invalid_dbg_expr(env, scope, region); + + let (loc_expr, output) = + canonicalize_expr(env, var_store, scope, region, invalid_dbg_expr); + + (loc_expr.value, output) + } + ast::Expr::DbgStmt(_, _) => { + internal_error!("DbgStmt should have been desugared by now") } ast::Expr::LowLevelDbg((source_location, source), message, continuation) => { let mut output = Output::default(); @@ -1554,6 +1568,8 @@ fn canonicalize_closure_body<'a>( &loc_body_expr.value, ); + let mut references_top_level = false; + let mut captured_symbols: Vec<_> = new_output .references .value_lookups() @@ -1564,7 +1580,11 @@ fn canonicalize_closure_body<'a>( .filter(|s| !new_output.references.bound_symbols().any(|x| x == s)) .filter(|s| bound_by_argument_patterns.iter().all(|(k, _)| s != k)) // filter out top-level symbols those will be globally available, and don't need to be captured - .filter(|s| !env.top_level_symbols.contains(s)) + .filter(|s| { + let is_top_level = env.top_level_symbols.contains(s); + references_top_level = references_top_level || is_top_level; + !is_top_level + }) // filter out imported symbols those will be globally available, and don't need to be captured .filter(|s| s.module_id() == env.home) // filter out functions that don't close over anything @@ -1573,6 +1593,15 @@ fn canonicalize_closure_body<'a>( .map(|s| (s, var_store.fresh())) .collect(); + if references_top_level { + if let Some(params_record) = env.home_params_record { + // If this module has params and the closure references top-level symbols, + // we need to capture the whole record so we can pass it. + // The lower_params pass will take care of removing the captures for top-level fns. + captured_symbols.push(params_record); + } + } + output.union(new_output); // Now that we've collected all the references, check to see if any of the args we defined @@ -1918,7 +1947,7 @@ fn canonicalize_var_lookup( Ok(lookup) => { output .references - .insert_value_lookup(lookup.symbol, QualifiedReference::Unqualified); + .insert_value_lookup(lookup, QualifiedReference::Unqualified); if scope.abilities_store.is_ability_member_name(lookup.symbol) { AbilityMember( @@ -1927,7 +1956,7 @@ fn canonicalize_var_lookup( var_store.fresh(), ) } else { - lookup_to_expr(lookup, var_store.fresh()) + lookup_to_expr(var_store, lookup) } } Err(problem) => { @@ -1943,7 +1972,7 @@ fn canonicalize_var_lookup( Ok(lookup) => { output .references - .insert_value_lookup(lookup.symbol, QualifiedReference::Qualified); + .insert_value_lookup(lookup, QualifiedReference::Qualified); if scope.abilities_store.is_ability_member_name(lookup.symbol) { AbilityMember( @@ -1952,7 +1981,7 @@ fn canonicalize_var_lookup( var_store.fresh(), ) } else { - lookup_to_expr(lookup, var_store.fresh()) + lookup_to_expr(var_store, lookup) } } Err(problem) => { @@ -1971,20 +2000,21 @@ fn canonicalize_var_lookup( } fn lookup_to_expr( + var_store: &mut VarStore, SymbolLookup { symbol, - module_params: params, + module_params, }: SymbolLookup, - var: Variable, ) -> Expr { - if let Some(params) = params { + if let Some((params_var, params_symbol)) = module_params { Expr::ParamsVar { symbol, - params, - var, + var: var_store.fresh(), + params_symbol, + params_var, } } else { - Expr::Var(symbol, var) + Expr::Var(symbol, var_store.fresh()) } } @@ -2470,21 +2500,28 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool { | ast::Expr::AccessorFunction(_) | ast::Expr::RecordUpdater(_) | ast::Expr::Crash + | ast::Expr::Dbg | ast::Expr::Underscore(_) | ast::Expr::MalformedIdent(_, _) | ast::Expr::Tag(_) | ast::Expr::OpaqueRef(_) | ast::Expr::MalformedClosure => true, // Newlines are disallowed inside interpolation, and these all require newlines - ast::Expr::Dbg(_, _) + ast::Expr::DbgStmt(_, _) | ast::Expr::LowLevelDbg(_, _, _) - | ast::Expr::Defs(_, _) | ast::Expr::Expect(_, _) | ast::Expr::When(_, _) | ast::Expr::Backpassing(_, _, _) | ast::Expr::SpaceBefore(_, _) | ast::Expr::Str(StrLiteral::Block(_)) | ast::Expr::SpaceAfter(_, _) => false, + // Desugared dbg expression + ast::Expr::Defs(_, loc_ret) => match loc_ret.value { + ast::Expr::LowLevelDbg(_, _, continuation) => { + is_valid_interpolation(&continuation.value) + } + _ => false, + }, // These can contain subexpressions, so we need to recursively check those ast::Expr::Str(StrLiteral::Line(segments)) => { segments.iter().all(|segment| match segment { @@ -2774,6 +2811,9 @@ pub struct Declarations { // used for ability member specializatons. pub specializes: VecMap, + // used while lowering params. + arity_by_name: VecMap, + pub host_exposed_annotations: VecMap, pub function_bodies: Vec>, @@ -2802,6 +2842,7 @@ impl Declarations { expressions: Vec::with_capacity(capacity), specializes: VecMap::default(), // number of specializations is probably low destructs: Vec::new(), // number of destructs is probably low + arity_by_name: VecMap::with_capacity(capacity), } } @@ -2840,6 +2881,9 @@ impl Declarations { arguments: loc_closure_data.value.arguments, }; + self.arity_by_name + .insert(symbol.value.ident_id(), function_def.arguments.len()); + let loc_function_def = Loc::at(loc_closure_data.region, function_def); let function_def_index = Index::push_new(&mut self.function_bodies, loc_function_def); @@ -2888,6 +2932,9 @@ impl Declarations { arguments: loc_closure_data.value.arguments, }; + self.arity_by_name + .insert(symbol.value.ident_id(), function_def.arguments.len()); + let loc_function_def = Loc::at(loc_closure_data.region, function_def); let function_def_index = Index::push_new(&mut self.function_bodies, loc_function_def); @@ -2964,6 +3011,8 @@ impl Declarations { .insert(self.declarations.len(), annotation); } + self.arity_by_name.insert(symbol.value.ident_id(), 0); + self.declarations.push(DeclarationTag::Value); self.variables.push(expr_var); self.symbols.push(symbol); @@ -3080,6 +3129,60 @@ impl Declarations { } } + /// Convert a value def to a function def with the given arguments + /// Currently used in lower_params + pub fn convert_value_to_function( + &mut self, + index: usize, + new_arguments: Vec<(Variable, AnnotatedMark, Loc)>, + var_store: &mut VarStore, + ) { + match self.declarations[index] { + DeclarationTag::Value => { + let new_args_len = new_arguments.len(); + + let loc_body = self.expressions[index].clone(); + let region = loc_body.region; + + let closure_data = ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + return_type: var_store.fresh(), + name: self.symbols[index].value, + captured_symbols: vec![], + recursive: Recursive::NotRecursive, + arguments: new_arguments, + loc_body: Box::new(loc_body), + }; + + let loc_closure_data = Loc::at(region, closure_data); + + let function_def = FunctionDef { + closure_type: loc_closure_data.value.closure_type, + return_type: loc_closure_data.value.return_type, + captured_symbols: loc_closure_data.value.captured_symbols, + arguments: loc_closure_data.value.arguments, + }; + + let loc_function_def = Loc::at(region, function_def); + + let function_def_index = + Index::push_new(&mut self.function_bodies, loc_function_def); + + if let Some(annotation) = &mut self.annotations[index] { + annotation.convert_to_fn(new_args_len, var_store); + } + + if let Some((_var, annotation)) = self.host_exposed_annotations.get_mut(&index) { + annotation.convert_to_fn(new_args_len, var_store); + } + + self.declarations[index] = DeclarationTag::Function(function_def_index); + } + _ => internal_error!("Expected value declaration"), + }; + } + pub fn len(&self) -> usize { self.declarations.len() } @@ -3149,6 +3252,11 @@ impl Declarations { collector } + + pub(crate) fn take_arity_by_name(&mut self) -> VecMap { + // `arity_by_name` is only needed for lowering module params + std::mem::take(&mut self.arity_by_name) + } } roc_error_macros::assert_sizeof_default!(DeclarationTag, 8); @@ -3205,8 +3313,9 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec { Expr::Var(symbol, var) | Expr::ParamsVar { symbol, - params: _, var, + params_symbol: _, + params_var: _, } | Expr::RecordUpdate { symbol, diff --git a/crates/compiler/can/src/lib.rs b/crates/compiler/can/src/lib.rs index 5bb8ce1906..17fc63d2ce 100644 --- a/crates/compiler/can/src/lib.rs +++ b/crates/compiler/can/src/lib.rs @@ -14,7 +14,6 @@ pub mod copy; pub mod def; mod derive; pub mod desugar; -pub mod effect_module; pub mod env; pub mod exhaustive; pub mod expected; @@ -26,6 +25,7 @@ pub mod procedure; pub mod scope; pub mod string; pub mod suffixed; +pub mod task_module; pub mod traverse; pub use derive::DERIVED_REGION; diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs index 6a13626e30..14dc2f827b 100644 --- a/crates/compiler/can/src/module.rs +++ b/crates/compiler/can/src/module.rs @@ -3,13 +3,12 @@ use std::path::Path; use crate::abilities::{AbilitiesStore, ImplKey, PendingAbilitiesStore, ResolvedImpl}; use crate::annotation::{canonicalize_annotation, AnnotationFor}; use crate::def::{canonicalize_defs, report_unused_imports, Def}; -use crate::effect_module::HostedGeneratedFunctions; use crate::env::Env; use crate::expr::{ - AnnotatedMark, ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives, + ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives, }; use crate::pattern::{ - canonicalize_record_destructure, BindingsFromPattern, Pattern, PermitShadows, + canonicalize_record_destructs, BindingsFromPattern, Pattern, PermitShadows, RecordDestruct, }; use crate::procedure::References; use crate::scope::Scope; @@ -18,14 +17,14 @@ use roc_collections::{MutMap, SendMap, VecMap, VecSet}; use roc_error_macros::internal_error; use roc_module::ident::Ident; use roc_module::ident::Lowercase; -use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, PackageModuleIds, Symbol}; +use roc_module::symbol::{IdentId, IdentIds, IdentIdsByModule, ModuleId, PackageModuleIds, Symbol}; use roc_parse::ast::{Defs, TypeAnnotation}; -use roc_parse::header::{HeaderType, ModuleParams}; +use roc_parse::header::HeaderType; use roc_parse::pattern::PatternType; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::{ExposedTypesStorageSubs, Subs, VarStore, Variable}; -use roc_types::types::{AbilitySet, Alias, AliasKind, AliasVar, Type}; +use roc_types::types::{AbilitySet, Alias, Type}; /// The types of all exposed values/functions of a collection of modules #[derive(Clone, Debug, Default)] @@ -138,7 +137,33 @@ pub struct Module { pub abilities_store: PendingAbilitiesStore, pub loc_expects: VecMap>, pub loc_dbgs: VecMap, - pub params_pattern: Option<(Variable, AnnotatedMark, Loc)>, + pub module_params: Option, +} + +#[derive(Debug, Clone)] +pub struct ModuleParams { + pub region: Region, + pub whole_symbol: Symbol, + pub whole_var: Variable, + pub record_var: Variable, + pub record_ext_var: Variable, + pub destructs: Vec>, + // used while lowering passed functions + pub arity_by_name: VecMap, +} + +impl ModuleParams { + pub fn pattern(&self) -> Loc { + let record_pattern = Pattern::RecordDestructure { + whole_var: self.record_var, + ext_var: self.record_ext_var, + destructs: self.destructs.clone(), + }; + let loc_record_pattern = Loc::at(self.region, record_pattern); + + let as_pattern = Pattern::As(Box::new(loc_record_pattern), self.whole_symbol); + Loc::at(self.region, as_pattern) + } } #[derive(Debug, Default)] @@ -152,7 +177,7 @@ pub struct RigidVariables { pub struct ModuleOutput { pub aliases: MutMap, pub rigid_variables: RigidVariables, - pub params_pattern: Option<(Variable, AnnotatedMark, Loc)>, + pub module_params: Option, pub declarations: Declarations, pub exposed_imports: MutMap, pub exposed_symbols: VecSet, @@ -165,99 +190,6 @@ pub struct ModuleOutput { pub loc_dbgs: VecMap, } -fn validate_generate_with<'a>( - generate_with: &'a [Loc>], -) -> (HostedGeneratedFunctions, Vec>) { - let mut functions = HostedGeneratedFunctions::default(); - let mut unknown = Vec::new(); - - for generated in generate_with { - match generated.value.as_str() { - "after" => functions.after = true, - "map" => functions.map = true, - "always" => functions.always = true, - "loop" => functions.loop_ = true, - "forever" => functions.forever = true, - other => { - // we don't know how to generate this function - let ident = Ident::from(other); - unknown.push(Loc::at(generated.region, ident)); - } - } - } - - (functions, unknown) -} - -#[derive(Debug)] -enum GeneratedInfo { - Hosted { - effect_symbol: Symbol, - generated_functions: HostedGeneratedFunctions, - }, - Builtin, - NotSpecial, -} - -impl GeneratedInfo { - fn from_header_type( - env: &mut Env, - scope: &mut Scope, - var_store: &mut VarStore, - header_type: &HeaderType, - ) -> Self { - match header_type { - HeaderType::Hosted { - generates, - generates_with, - name: _, - exposes: _, - } => { - let name: &str = generates.into(); - let (generated_functions, unknown_generated) = - validate_generate_with(generates_with); - - for unknown in unknown_generated { - env.problem(Problem::UnknownGeneratesWith(unknown)); - } - - let effect_symbol = scope.introduce(name.into(), Region::zero()).unwrap(); - - { - let a_var = var_store.fresh(); - - let actual = - crate::effect_module::build_effect_actual(Type::Variable(a_var), var_store); - - scope.add_alias( - effect_symbol, - Region::zero(), - vec![Loc::at_zero(AliasVar::unbound("a".into(), a_var))], - vec![], - actual, - AliasKind::Opaque, - ); - } - - GeneratedInfo::Hosted { - effect_symbol, - generated_functions, - } - } - HeaderType::Builtin { - generates_with, - name: _, - exposes: _, - opt_params: _, - } => { - debug_assert!(generates_with.is_empty()); - GeneratedInfo::Builtin - } - _ => GeneratedInfo::NotSpecial, - } - } -} - fn has_no_implementation(expr: &Expr) -> bool { match expr { Expr::RuntimeError(RuntimeError::NoImplementationNamed { .. }) => true, @@ -308,6 +240,7 @@ pub fn canonicalize_module_defs<'a>( ); let mut env = Env::new( arena, + src, home, arena.alloc(Path::new(module_path)), dep_idents, @@ -326,9 +259,6 @@ pub fn canonicalize_module_defs<'a>( ); } - let generated_info = - GeneratedInfo::from_header_type(&mut env, &mut scope, var_store, header_type); - // Desugar operators (convert them to Apply calls, taking into account // operator precedence and associativity rules), before doing other canonicalization. // @@ -337,19 +267,11 @@ 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, - &mut env.problems, - ); + crate::desugar::desugar_defs_node_values(&mut env, &mut scope, loc_defs, true); let mut rigid_variables = RigidVariables::default(); - // Iniital scope values are treated like defs that appear before any others. + // Initial scope values are treated like defs that appear before any others. // They include builtin types that are automatically imported, and for a platform // package, the required values from the app. // @@ -398,13 +320,13 @@ pub fn canonicalize_module_defs<'a>( let mut output = Output::default(); - let params_pattern = header_type.get_params().as_ref().map( - |ModuleParams { + let module_params = header_type.get_params().as_ref().map( + |roc_parse::header::ModuleParams { pattern, before_arrow: _, after_arrow: _, }| { - let can_pattern = canonicalize_record_destructure( + let (destructs, _) = canonicalize_record_destructs( &mut env, var_store, &mut scope, @@ -415,17 +337,22 @@ pub fn canonicalize_module_defs<'a>( PermitShadows(false), ); - let loc_pattern = Loc::at(pattern.region, can_pattern); + let whole_symbol = scope.gen_unique_symbol(); + env.top_level_symbols.insert(whole_symbol); - for (symbol, _) in BindingsFromPattern::new(&loc_pattern) { - env.top_level_symbols.insert(symbol); + let whole_var = var_store.fresh(); + + env.home_params_record = Some((whole_symbol, whole_var)); + + ModuleParams { + region: pattern.region, + whole_var, + whole_symbol, + record_var: var_store.fresh(), + record_ext_var: var_store.fresh(), + destructs, + arity_by_name: Default::default(), } - - ( - var_store.fresh(), - AnnotatedMark::new(var_store), - loc_pattern, - ) }, ); @@ -503,6 +430,11 @@ pub fn canonicalize_module_defs<'a>( &exposed_symbols, ); + let module_params = module_params.map(|params| ModuleParams { + arity_by_name: declarations.take_arity_by_name(), + ..params + }); + debug_assert!( output.pending_derives.is_empty(), "I thought pending derives are only found during def introduction" @@ -542,24 +474,6 @@ pub fn canonicalize_module_defs<'a>( report_unused_imports(imports_introduced, &output.references, &mut env, &mut scope); - if let GeneratedInfo::Hosted { - effect_symbol, - generated_functions, - } = generated_info - { - let mut exposed_symbols = VecSet::default(); - - // NOTE this currently builds all functions, not just the ones that the user requested - crate::effect_module::build_effect_builtins( - &mut scope, - effect_symbol, - var_store, - &mut exposed_symbols, - &mut declarations, - generated_functions, - ); - } - for index in 0..declarations.len() { use crate::expr::DeclarationTag::*; @@ -580,8 +494,8 @@ pub fn canonicalize_module_defs<'a>( // and which are meant to be normal definitions without a body. So for now // we just assume they are hosted functions (meant to be provided by the platform) if has_no_implementation(&declarations.expressions[index].value) { - match generated_info { - GeneratedInfo::Builtin => { + match header_type { + HeaderType::Builtin { .. } => { match crate::builtins::builtin_defs_map(*symbol, var_store) { None => { internal_error!("A builtin module contains a signature without implementation for {:?}", symbol) @@ -591,7 +505,7 @@ pub fn canonicalize_module_defs<'a>( } } } - GeneratedInfo::Hosted { effect_symbol, .. } => { + HeaderType::Hosted { .. } => { let ident_id = symbol.ident_id(); let ident = scope .locals @@ -609,13 +523,8 @@ pub fn canonicalize_module_defs<'a>( aliases: Default::default(), }; - let hosted_def = crate::effect_module::build_host_exposed_def( - &mut scope, - *symbol, - &ident, - effect_symbol, - var_store, - annotation, + let hosted_def = crate::task_module::build_host_exposed_def( + &mut scope, *symbol, &ident, var_store, annotation, ); declarations.update_builtin_def(index, hosted_def); @@ -638,8 +547,8 @@ pub fn canonicalize_module_defs<'a>( // and which are meant to be normal definitions without a body. So for now // we just assume they are hosted functions (meant to be provided by the platform) if has_no_implementation(&declarations.expressions[index].value) { - match generated_info { - GeneratedInfo::Builtin => { + match header_type { + HeaderType::Builtin { .. } => { match crate::builtins::builtin_defs_map(*symbol, var_store) { None => { internal_error!("A builtin module contains a signature without implementation for {:?}", symbol) @@ -649,7 +558,7 @@ pub fn canonicalize_module_defs<'a>( } } } - GeneratedInfo::Hosted { effect_symbol, .. } => { + HeaderType::Hosted { .. } => { let ident_id = symbol.ident_id(); let ident = scope .locals @@ -667,13 +576,8 @@ pub fn canonicalize_module_defs<'a>( aliases: Default::default(), }; - let hosted_def = crate::effect_module::build_host_exposed_def( - &mut scope, - *symbol, - &ident, - effect_symbol, - var_store, - annotation, + let hosted_def = crate::task_module::build_host_exposed_def( + &mut scope, *symbol, &ident, var_store, annotation, ); declarations.update_builtin_def(index, hosted_def); @@ -699,18 +603,6 @@ pub fn canonicalize_module_defs<'a>( let mut aliases = MutMap::default(); - if let GeneratedInfo::Hosted { effect_symbol, .. } = generated_info { - // Remove this from exposed_symbols, - // so that at the end of the process, - // we can see if there were any - // exposed symbols which did not have - // corresponding defs. - exposed_but_not_defined.remove(&effect_symbol); - - let hosted_alias = scope.lookup_alias(effect_symbol).unwrap().clone(); - aliases.insert(effect_symbol, hosted_alias); - } - for (symbol, alias) in output.aliases { // Remove this from exposed_symbols, // so that at the end of the process, @@ -859,7 +751,7 @@ pub fn canonicalize_module_defs<'a>( scope, aliases, rigid_variables, - params_pattern, + module_params, declarations, referenced_values, exposed_imports: can_exposed_imports, diff --git a/crates/compiler/can/src/pattern.rs b/crates/compiler/can/src/pattern.rs index 129236bb46..ddd6efda30 100644 --- a/crates/compiler/can/src/pattern.rs +++ b/crates/compiler/can/src/pattern.rs @@ -623,16 +623,29 @@ pub fn canonicalize_pattern<'a>( } } - RecordDestructure(patterns) => canonicalize_record_destructure( - env, - var_store, - scope, - output, - pattern_type, - patterns, - region, - permit_shadows, - ), + RecordDestructure(patterns) => { + let ext_var = var_store.fresh(); + let whole_var = var_store.fresh(); + + let (destructs, opt_erroneous) = canonicalize_record_destructs( + env, + var_store, + scope, + output, + pattern_type, + patterns, + region, + permit_shadows, + ); + + // If we encountered an erroneous pattern (e.g. one with shadowing), + // use the resulting RuntimeError. Otherwise, return a successful record destructure. + opt_erroneous.unwrap_or(Pattern::RecordDestructure { + whole_var, + ext_var, + destructs, + }) + } RequiredField(_name, _loc_pattern) => { unreachable!("should have been handled in RecordDestructure"); @@ -779,7 +792,7 @@ pub fn canonicalize_pattern<'a>( } #[allow(clippy::too_many_arguments)] -pub fn canonicalize_record_destructure<'a>( +pub fn canonicalize_record_destructs<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, @@ -788,11 +801,9 @@ pub fn canonicalize_record_destructure<'a>( patterns: &ast::Collection>>, region: Region, permit_shadows: PermitShadows, -) -> Pattern { +) -> (Vec>, Option) { use ast::Pattern::*; - let ext_var = var_store.fresh(); - let whole_var = var_store.fresh(); let mut destructs = Vec::with_capacity(patterns.len()); let mut opt_erroneous = None; @@ -907,13 +918,7 @@ pub fn canonicalize_record_destructure<'a>( } } - // If we encountered an erroneous pattern (e.g. one with shadowing), - // use the resulting RuntimeError. Otherwise, return a successful record destructure. - opt_erroneous.unwrap_or(Pattern::RecordDestructure { - whole_var, - ext_var, - destructs, - }) + (destructs, opt_erroneous) } /// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't diff --git a/crates/compiler/can/src/procedure.rs b/crates/compiler/can/src/procedure.rs index 1a0fc4318f..65d90e0aa1 100644 --- a/crates/compiler/can/src/procedure.rs +++ b/crates/compiler/can/src/procedure.rs @@ -1,5 +1,5 @@ -use crate::expr::Expr; use crate::pattern::Pattern; +use crate::{expr::Expr, scope::SymbolLookup}; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Loc, Region}; use roc_types::subs::Variable; @@ -125,8 +125,18 @@ impl References { } } - pub fn insert_value_lookup(&mut self, symbol: Symbol, qualified: QualifiedReference) { - self.insert(symbol, qualified.flags(ReferencesBitflags::VALUE_LOOKUP)); + pub fn insert_value_lookup(&mut self, lookup: SymbolLookup, qualified: QualifiedReference) { + self.insert( + lookup.symbol, + qualified.flags(ReferencesBitflags::VALUE_LOOKUP), + ); + + if let Some((_, params_symbol)) = lookup.module_params { + self.insert( + params_symbol, + qualified.flags(ReferencesBitflags::VALUE_LOOKUP), + ); + } } pub fn insert_type_lookup(&mut self, symbol: Symbol, qualified: QualifiedReference) { diff --git a/crates/compiler/can/src/scope.rs b/crates/compiler/can/src/scope.rs index 6de854fa80..33127f7dd7 100644 --- a/crates/compiler/can/src/scope.rs +++ b/crates/compiler/can/src/scope.rs @@ -466,7 +466,7 @@ impl Scope { self.home.register_debug_idents(&self.locals.ident_ids) } - /// Generates a unique, new symbol like "$1" or "$5", + /// Generates a unique, new symbol like "1" or "5", /// using the home module as the module_id. /// /// This is used, for example, during canonicalization of an Expr::Closure @@ -475,6 +475,12 @@ impl Scope { Symbol::new(self.home, self.locals.gen_unique()) } + /// Generates a unique new symbol and return the symbol's unqualified identifier name. + pub fn gen_unique_symbol_name(&mut self) -> &str { + let ident_id = self.locals.gen_unique(); + self.locals.ident_ids.get_name(ident_id).unwrap() + } + /// Introduce a new ignored variable (variable starting with an underscore). /// The underscore itself should not be included in `ident`. pub fn introduce_ignored_local(&mut self, ident: &str, region: Region) { @@ -669,7 +675,7 @@ pub struct ScopeModules { /// Why is this module in scope? sources: Vec, /// The params of a module if any - params: Vec>, + params: Vec>, } impl ScopeModules { @@ -731,7 +737,7 @@ impl ScopeModules { &mut self, module_name: ModuleName, module_id: ModuleId, - params_symbol: Option, + params: Option<(Variable, Symbol)>, region: Region, ) -> Result<(), ScopeModuleSource> { if let Some(index) = self.names.iter().position(|name| name == &module_name) { @@ -745,7 +751,7 @@ impl ScopeModules { self.ids.push(module_id); self.names.push(module_name); self.sources.push(ScopeModuleSource::Import(region)); - self.params.push(params_symbol); + self.params.push(params); Ok(()) } @@ -768,14 +774,14 @@ impl ScopeModules { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct SymbolLookup { pub symbol: Symbol, - pub module_params: Option, + pub module_params: Option<(Variable, Symbol)>, } impl SymbolLookup { - pub fn new(symbol: Symbol, params: Option) -> Self { + pub fn new(symbol: Symbol, params: Option<(Variable, Symbol)>) -> Self { Self { symbol, module_params: params, @@ -789,7 +795,7 @@ impl SymbolLookup { pub struct ModuleLookup { pub id: ModuleId, - pub params: Option, + pub params: Option<(Variable, Symbol)>, } impl ModuleLookup { diff --git a/crates/compiler/can/src/task_module.rs b/crates/compiler/can/src/task_module.rs new file mode 100644 index 0000000000..c494b78f9e --- /dev/null +++ b/crates/compiler/can/src/task_module.rs @@ -0,0 +1,207 @@ +use crate::def::Def; +use crate::expr::{AnnotatedMark, ClosureData, Expr, Recursive}; +use crate::pattern::Pattern; +use crate::scope::Scope; +use roc_collections::SendMap; +use roc_module::symbol::Symbol; +use roc_region::all::{Loc, Region}; +use roc_types::subs::{VarStore, Variable}; +use roc_types::types::{LambdaSet, OptAbleVar, Type}; + +pub fn build_host_exposed_def( + scope: &mut Scope, + symbol: Symbol, + ident: &str, + var_store: &mut VarStore, + annotation: crate::annotation::Annotation, +) -> Def { + let expr_var = var_store.fresh(); + let pattern = Pattern::Identifier(symbol); + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(symbol, expr_var); + + let mut arguments: Vec<(Variable, AnnotatedMark, Loc)> = Vec::new(); + let mut linked_symbol_arguments: Vec<(Variable, Expr)> = Vec::new(); + let mut captured_symbols: Vec<(Symbol, Variable)> = Vec::new(); + + let crate::annotation::Annotation { + introduced_variables, + typ, + aliases, + .. + } = annotation; + + let def_body = { + match typ.shallow_structural_dealias() { + Type::Function(args, _, _) => { + for i in 0..args.len() { + let name = format!("closure_arg_{ident}_{i}"); + + let arg_symbol = { + let ident = name.clone().into(); + scope.introduce(ident, Region::zero()).unwrap() + }; + + let arg_var = var_store.fresh(); + + arguments.push(( + arg_var, + AnnotatedMark::new(var_store), + Loc::at_zero(Pattern::Identifier(arg_symbol)), + )); + + captured_symbols.push((arg_symbol, arg_var)); + linked_symbol_arguments.push((arg_var, Expr::Var(arg_symbol, arg_var))); + } + + let foreign_symbol_name = format!("roc_fx_{ident}"); + let low_level_call = Expr::ForeignCall { + foreign_symbol: foreign_symbol_name.into(), + args: linked_symbol_arguments, + ret_var: var_store.fresh(), + }; + + let task_closure_symbol = { + let name = format!("task_closure_{ident}"); + + let ident = name.into(); + scope.introduce(ident, Region::zero()).unwrap() + }; + + let task_closure = Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + return_type: var_store.fresh(), + name: task_closure_symbol, + captured_symbols, + recursive: Recursive::NotRecursive, + arguments: vec![( + var_store.fresh(), + AnnotatedMark::new(var_store), + Loc::at_zero(empty_record_pattern(var_store)), + )], + loc_body: Box::new(Loc::at_zero(low_level_call)), + }); + + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + let body = Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: Symbol::TASK_TASK, + argument: Box::new((var_store.fresh(), Loc::at_zero(task_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, + }; + + Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + return_type: var_store.fresh(), + name: symbol, + captured_symbols: std::vec::Vec::new(), + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }) + } + _ => { + // not a function + + let foreign_symbol_name = format!("roc_fx_{ident}"); + let low_level_call = Expr::ForeignCall { + foreign_symbol: foreign_symbol_name.into(), + args: linked_symbol_arguments, + ret_var: var_store.fresh(), + }; + + let task_closure_symbol = { + let name = format!("task_closure_{ident}"); + + let ident = name.into(); + scope.introduce(ident, Region::zero()).unwrap() + }; + + let task_closure = Expr::Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + return_type: var_store.fresh(), + name: task_closure_symbol, + captured_symbols, + recursive: Recursive::NotRecursive, + arguments: vec![( + var_store.fresh(), + AnnotatedMark::new(var_store), + Loc::at_zero(empty_record_pattern(var_store)), + )], + loc_body: Box::new(Loc::at_zero(low_level_call)), + }); + + let (specialized_def_type, type_arguments, lambda_set_variables) = + build_fresh_opaque_variables(var_store); + Expr::OpaqueRef { + opaque_var: var_store.fresh(), + name: Symbol::TASK_TASK, + argument: Box::new((var_store.fresh(), Loc::at_zero(task_closure))), + specialized_def_type, + type_arguments, + lambda_set_variables, + } + } + } + }; + + let def_annotation = crate::def::Annotation { + signature: typ, + introduced_variables, + aliases, + region: Region::zero(), + }; + + Def { + loc_pattern: Loc::at_zero(pattern), + loc_expr: Loc::at_zero(def_body), + expr_var, + pattern_vars, + annotation: Some(def_annotation), + } +} + +fn build_fresh_opaque_variables( + var_store: &mut VarStore, +) -> (Box, Vec, Vec) { + let closure_var = var_store.fresh(); + + let ok_var = var_store.fresh(); + let err_var = var_store.fresh(); + let result_var = var_store.fresh(); + + let actual = Type::Function( + vec![Type::EmptyRec], + Box::new(Type::Variable(closure_var)), + Box::new(Type::Variable(result_var)), + ); + + let type_arguments = vec![ + OptAbleVar { + var: ok_var, + opt_abilities: None, + }, + OptAbleVar { + var: err_var, + opt_abilities: None, + }, + ]; + let lambda_set_variables = vec![roc_types::types::LambdaSet(Type::Variable(closure_var))]; + + (Box::new(actual), type_arguments, lambda_set_variables) +} + +#[inline(always)] +fn empty_record_pattern(var_store: &mut VarStore) -> Pattern { + Pattern::RecordDestructure { + whole_var: var_store.fresh(), + ext_var: var_store.fresh(), + destructs: vec![], + } +} diff --git a/crates/compiler/can/tests/helpers/mod.rs b/crates/compiler/can/tests/helpers/mod.rs index c1e144c7d2..4fe40c935f 100644 --- a/crates/compiler/can/tests/helpers/mod.rs +++ b/crates/compiler/can/tests/helpers/mod.rs @@ -46,6 +46,24 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut let var = var_store.fresh(); let qualified_module_ids = PackageModuleIds::default(); + let mut scope = Scope::new( + home, + "TestPath".into(), + IdentIds::default(), + Default::default(), + ); + + let dep_idents = IdentIds::exposed_builtins(0); + let mut env = Env::new( + arena, + expr_str, + home, + Path::new("Test.roc"), + &dep_idents, + &qualified_module_ids, + None, + ); + // Desugar operators (convert them to Apply calls, taking into account // operator precedence and associativity rules), before doing other canonicalization. // @@ -53,21 +71,8 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut // visited a BinOp node we'd recursively try to apply this to each of its nested // operators, and then again on *their* nested operators, ultimately applying the // rules multiple times unnecessarily. - let loc_expr = desugar::desugar_expr( - arena, - &loc_expr, - expr_str, - &mut None, - arena.alloc("TestPath"), - &mut Default::default(), - ); + let loc_expr = desugar::desugar_expr(&mut env, &mut scope, &loc_expr); - let mut scope = Scope::new( - home, - "TestPath".into(), - IdentIds::default(), - Default::default(), - ); scope.add_alias( Symbol::NUM_INT, Region::zero(), @@ -80,15 +85,6 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut roc_types::types::AliasKind::Structural, ); - let dep_idents = IdentIds::exposed_builtins(0); - let mut env = Env::new( - arena, - home, - Path::new("Test.roc"), - &dep_idents, - &qualified_module_ids, - None, - ); let (loc_expr, output) = canonicalize_expr( &mut env, &mut var_store, diff --git a/crates/compiler/can/tests/snapshots/test_suffixed__suffixed_tests__dbg_expr.snap b/crates/compiler/can/tests/snapshots/test_suffixed__suffixed_tests__dbg_expr.snap new file mode 100644 index 0000000000..89f3840a66 --- /dev/null +++ b/crates/compiler/can/tests/snapshots/test_suffixed__suffixed_tests__dbg_expr.snap @@ -0,0 +1,145 @@ +--- +source: crates/compiler/can/tests/test_suffixed.rs +assertion_line: 463 +expression: snapshot +--- +Defs { + tags: [ + Index(2147483648), + ], + regions: [ + @0-26, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 1), + ], + spaces: [ + Newline, + ], + type_defs: [], + value_defs: [ + Body( + @0-4 Identifier { + ident: "main", + }, + @11-26 Defs( + Defs { + tags: [ + Index(2147483648), + ], + regions: [ + @15-26, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + Body( + @15-26 Identifier { + ident: "1", + }, + @15-26 ParensAround( + Defs( + Defs { + tags: [ + Index(2147483648), + ], + regions: [ + @20-25, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + Body( + @20-25 Identifier { + ident: "0", + }, + @20-25 Apply( + @22-23 Var { + module_name: "Num", + ident: "add", + }, + [ + @20-21 Num( + "1", + ), + @24-25 Num( + "1", + ), + ], + BinOp( + Plus, + ), + ), + ), + ], + }, + @15-26 LowLevelDbg( + ( + "test.roc:3", + " ", + ), + @20-25 Apply( + @20-25 Var { + module_name: "Inspect", + ident: "toStr", + }, + [ + @20-25 Var { + module_name: "", + ident: "0", + }, + ], + Space, + ), + @20-25 Var { + module_name: "", + ident: "0", + }, + ), + ), + ), + ), + ], + }, + @11-26 LowLevelDbg( + ( + "test.roc:2", + "in =\n ", + ), + @15-26 Apply( + @15-26 Var { + module_name: "Inspect", + ident: "toStr", + }, + [ + @15-26 Var { + module_name: "", + ident: "1", + }, + ], + Space, + ), + @15-26 Var { + module_name: "", + ident: "1", + }, + ), + ), + ), + ], +} diff --git a/crates/compiler/can/tests/snapshots/test_suffixed__suffixed_tests__pizza_dbg.snap b/crates/compiler/can/tests/snapshots/test_suffixed__suffixed_tests__pizza_dbg.snap new file mode 100644 index 0000000000..c23c953eb8 --- /dev/null +++ b/crates/compiler/can/tests/snapshots/test_suffixed__suffixed_tests__pizza_dbg.snap @@ -0,0 +1,143 @@ +--- +source: crates/compiler/can/tests/test_suffixed.rs +assertion_line: 473 +expression: snapshot +--- +Defs { + tags: [ + Index(2147483648), + ], + regions: [ + @0-51, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 1), + ], + spaces: [ + Newline, + ], + type_defs: [], + value_defs: [ + Body( + @0-4 Identifier { + ident: "main", + }, + @11-51 Defs( + Defs { + tags: [ + Index(2147483648), + ], + regions: [ + @11-40, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + Body( + @11-40 Identifier { + ident: "1", + }, + @11-40 Apply( + @31-38 Var { + module_name: "Num", + ident: "add", + }, + [ + @11-23 Defs( + Defs { + tags: [ + Index(2147483648), + ], + regions: [ + @11-12, + ], + space_before: [ + Slice(start = 0, length = 0), + ], + space_after: [ + Slice(start = 0, length = 0), + ], + spaces: [], + type_defs: [], + value_defs: [ + Body( + @11-12 Identifier { + ident: "0", + }, + @11-12 Num( + "1", + ), + ), + ], + }, + @11-23 LowLevelDbg( + ( + "test.roc:2", + " ", + ), + @11-12 Apply( + @11-12 Var { + module_name: "Inspect", + ident: "toStr", + }, + [ + @11-12 Var { + module_name: "", + ident: "0", + }, + ], + Space, + ), + @11-12 Var { + module_name: "", + ident: "0", + }, + ), + ), + @39-40 Num( + "2", + ), + ], + BinOp( + Pizza, + ), + ), + ), + ], + }, + @11-51 LowLevelDbg( + ( + "test.roc:2", + " main =\n 1\n ", + ), + @11-40 Apply( + @11-40 Var { + module_name: "Inspect", + ident: "toStr", + }, + [ + @11-40 Var { + module_name: "", + ident: "1", + }, + ], + Space, + ), + @11-40 Var { + module_name: "", + ident: "1", + }, + ), + ), + ), + ], +} diff --git a/crates/compiler/can/tests/test_suffixed.rs b/crates/compiler/can/tests/test_suffixed.rs index f82d857e5f..9fdad132b7 100644 --- a/crates/compiler/can/tests/test_suffixed.rs +++ b/crates/compiler/can/tests/test_suffixed.rs @@ -6,22 +6,39 @@ mod suffixed_tests { use bumpalo::Bump; use insta::assert_snapshot; use roc_can::desugar::desugar_defs_node_values; + use roc_can::env::Env; + use roc_can::scope::Scope; + use roc_module::symbol::{IdentIds, ModuleIds, PackageModuleIds}; use roc_parse::test_helpers::parse_defs_with; + use std::path::Path; macro_rules! run_test { ($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, - &mut Default::default(), + let home = ModuleIds::default().get_or_insert(&"Test".into()); + + let mut scope = Scope::new( + home, + "TestPath".into(), + IdentIds::default(), + Default::default(), ); + let dep_idents = IdentIds::exposed_builtins(0); + let qualified_module_ids = PackageModuleIds::default(); + let mut env = Env::new( + arena, + $src, + home, + Path::new("test.roc"), + &dep_idents, + &qualified_module_ids, + None, + ); + + let mut defs = parse_defs_with(arena, indoc!($src)).unwrap(); + desugar_defs_node_values(&mut env, &mut scope, &mut defs, true); + let snapshot = format!("{:#?}", &defs); println!("{}", snapshot); assert_snapshot!(snapshot); @@ -441,6 +458,29 @@ mod suffixed_tests { ); } + #[test] + fn dbg_expr() { + run_test!( + r#" + main = + dbg (dbg 1 + 1) + "# + ); + } + + #[test] + fn pizza_dbg() { + run_test!( + r#" + main = + 1 + |> dbg + |> Num.add 2 + |> dbg + "# + ) + } + #[test] fn apply_argument_single() { run_test!( diff --git a/crates/compiler/checkmate/www/package-lock.json b/crates/compiler/checkmate/www/package-lock.json index 8aafeebe07..0f9ae79b54 100644 --- a/crates/compiler/checkmate/www/package-lock.json +++ b/crates/compiler/checkmate/www/package-lock.json @@ -3424,21 +3424,15 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -4351,20 +4345,10 @@ "@types/json-schema": "*" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/express": { @@ -4932,9 +4916,9 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", @@ -4954,9 +4938,9 @@ "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { @@ -4977,15 +4961,15 @@ "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { @@ -5013,28 +4997,28 @@ "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -5042,24 +5026,24 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -5068,12 +5052,12 @@ } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -5142,10 +5126,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "peerDependencies": { "acorn": "^8" @@ -6039,9 +6023,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.21.9", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", - "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -6058,10 +6042,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001503", - "electron-to-chromium": "^1.4.431", - "node-releases": "^2.0.12", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -6178,9 +6162,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001516", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001516.tgz", - "integrity": "sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g==", + "version": "1.0.30001655", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz", + "integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==", "dev": true, "funding": [ { @@ -7575,9 +7559,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.461", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.461.tgz", - "integrity": "sha512-1JkvV2sgEGTDXjdsaQCeSwYYuhLRphRpc+g6EHTFELJXEiznLt3/0pZ9JuAOQ5p2rI3YxKTbivtvajirIfhrEQ==", + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", + "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", "dev": true }, "node_modules/elkjs": { @@ -7622,9 +7606,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -7815,9 +7799,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "engines": { "node": ">=6" @@ -13471,9 +13455,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, "node_modules/normalize-path": { @@ -13926,9 +13910,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -17473,9 +17457,9 @@ } }, "node_modules/terser": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.0.tgz", - "integrity": "sha512-JpcpGOQLOXm2jsomozdMDpd5f8ZHh1rR48OFgWUH3QsyZcfPgv2qDCYbcDEAYNd4OZRj2bWYKpwdll/udZCk/Q==", + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -17491,16 +17475,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -17984,9 +17968,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, "funding": [ { @@ -18003,8 +17987,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -18140,9 +18124,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -18171,34 +18155,33 @@ } }, "node_modules/webpack": { - "version": "5.88.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz", - "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { diff --git a/crates/compiler/collections/src/vec_set.rs b/crates/compiler/collections/src/vec_set.rs index 2a19bf2b9c..f75b4ad33b 100644 --- a/crates/compiler/collections/src/vec_set.rs +++ b/crates/compiler/collections/src/vec_set.rs @@ -17,6 +17,10 @@ impl VecSet { pub fn into_vec(self) -> Vec { self.elements } + + pub fn reserve(&mut self, additional: usize) { + self.elements.reserve(additional) + } } impl VecSet { diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index 2b7448925b..4961cb43db 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -569,8 +569,8 @@ pub fn constrain_expr( Var(symbol, variable) | ParamsVar { symbol, - params: _, var: variable, + .. } => { // Save the expectation in the variable, then lookup the symbol's type in the environment let expected_type = *constraints[expected].get_type_ref(); diff --git a/crates/compiler/constrain/src/module.rs b/crates/compiler/constrain/src/module.rs index f1742216b8..bb0d3a9d5f 100644 --- a/crates/compiler/constrain/src/module.rs +++ b/crates/compiler/constrain/src/module.rs @@ -3,13 +3,12 @@ use crate::pattern::{constrain_pattern, PatternState}; use roc_can::abilities::{PendingAbilitiesStore, PendingMemberType}; use roc_can::constraint::{Constraint, Constraints, Generalizable}; use roc_can::expected::{Expected, PExpected}; -use roc_can::expr::{AnnotatedMark, Declarations}; +use roc_can::expr::Declarations; +use roc_can::module::ModuleParams; use roc_can::pattern::Pattern; use roc_collections::MutMap; -use roc_error_macros::internal_error; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Loc, Region}; -use roc_types::subs::Variable; use roc_types::types::{AnnotationSource, Category, Type, Types}; pub fn constrain_module( @@ -18,12 +17,12 @@ pub fn constrain_module( symbols_from_requires: Vec<(Loc, Loc)>, abilities_store: &PendingAbilitiesStore, declarations: &Declarations, - params_pattern: &Option<(Variable, AnnotatedMark, Loc)>, + opt_module_params: &Option, home: ModuleId, ) -> Constraint { let constraint = crate::expr::constrain_decls(types, constraints, home, declarations); - let constraint = match params_pattern { + let constraint = match opt_module_params { Some(params_pattern) => { constrain_params(types, constraints, home, constraint, params_pattern) } @@ -51,7 +50,7 @@ fn constrain_params( constraints: &mut Constraints, home: ModuleId, constraint: Constraint, - (pattern_var, _, loc_pattern): &(Variable, AnnotatedMark, Loc), + module_params: &ModuleParams, ) -> Constraint { let mut env = Env { home, @@ -59,23 +58,13 @@ fn constrain_params( resolutions_to_make: vec![], }; - let index = constraints.push_variable(*pattern_var); + let index = constraints.push_variable(module_params.whole_var); let expected_params = constraints.push_pat_expected_type(PExpected::NoExpectation(index)); let mut state = PatternState::default(); - let closed_con = match loc_pattern.value { - Pattern::RecordDestructure { - whole_var: _, - ext_var, - destructs: _, - } => { - // Disallow record extension for module params - let empty_rec = constraints.push_type(types, Types::EMPTY_RECORD); - constraints.store(empty_rec, ext_var, file!(), line!()) - } - _ => internal_error!("Only record destructures are allowed in module params. This should've been caught earlier."), - }; + let empty_rec = constraints.push_type(types, Types::EMPTY_RECORD); + let closed_con = constraints.store(empty_rec, module_params.record_ext_var, file!(), line!()); state.constraints.push(closed_con); @@ -83,8 +72,8 @@ fn constrain_params( types, constraints, &mut env, - &loc_pattern.value, - loc_pattern.region, + &module_params.pattern().value, + module_params.region, expected_params, &mut state, ); @@ -100,7 +89,7 @@ fn constrain_params( Generalizable(true), ); - constraints.exists([*pattern_var], cons) + constraints.exists([module_params.whole_var], cons) } fn constrain_symbols_from_requires( diff --git a/crates/compiler/fmt/src/annotation.rs b/crates/compiler/fmt/src/annotation.rs index b279a8fc9c..3e6b18a158 100644 --- a/crates/compiler/fmt/src/annotation.rs +++ b/crates/compiler/fmt/src/annotation.rs @@ -38,6 +38,7 @@ pub enum Parens { InFunctionType, InApply, InOperator, + InAsPattern, } /// In an AST node, do we show newlines around it diff --git a/crates/compiler/fmt/src/expr.rs b/crates/compiler/fmt/src/expr.rs index fad6f02a4d..8b7a6ea147 100644 --- a/crates/compiler/fmt/src/expr.rs +++ b/crates/compiler/fmt/src/expr.rs @@ -46,7 +46,8 @@ impl<'a> Formattable for Expr<'a> { | MalformedClosure | Tag(_) | OpaqueRef(_) - | Crash => false, + | Crash + | Dbg => false, RecordAccess(inner, _) | TupleAccess(inner, _) | TrySuffix { expr: inner, .. } => { inner.is_multiline() @@ -65,7 +66,7 @@ impl<'a> Formattable for Expr<'a> { Expect(condition, continuation) => { condition.is_multiline() || continuation.is_multiline() } - Dbg(condition, _) => condition.is_multiline(), + DbgStmt(condition, _) => condition.is_multiline(), LowLevelDbg(_, _, _) => unreachable!( "LowLevelDbg should only exist after desugaring, not during formatting" ), @@ -453,8 +454,12 @@ impl<'a> Formattable for Expr<'a> { Expect(condition, continuation) => { fmt_expect(buf, condition, continuation, self.is_multiline(), indent); } - Dbg(condition, continuation) => { - fmt_dbg(buf, condition, continuation, self.is_multiline(), indent); + Dbg => { + buf.indent(indent); + buf.push_str("dbg"); + } + DbgStmt(condition, continuation) => { + fmt_dbg_stmt(buf, condition, continuation, self.is_multiline(), indent); } LowLevelDbg(_, _, _) => unreachable!( "LowLevelDbg should only exist after desugaring, not during formatting" @@ -1018,7 +1023,7 @@ fn fmt_when<'a>( } } -fn fmt_dbg<'a>( +fn fmt_dbg_stmt<'a>( buf: &mut Buf, condition: &'a Loc>, continuation: &'a Loc>, @@ -1240,7 +1245,7 @@ fn fmt_closure<'a>( let mut it = loc_patterns.iter().peekable(); while let Some(loc_pattern) = it.next() { - loc_pattern.format(buf, indent); + loc_pattern.format_with_options(buf, Parens::InAsPattern, Newlines::No, indent); if it.peek().is_some() { buf.indent(indent); diff --git a/crates/compiler/fmt/src/header.rs b/crates/compiler/fmt/src/header.rs index e76018956b..325c0649e9 100644 --- a/crates/compiler/fmt/src/header.rs +++ b/crates/compiler/fmt/src/header.rs @@ -7,11 +7,10 @@ use crate::spaces::{fmt_comments_only, fmt_default_spaces, fmt_spaces, NewlineAt use crate::Buf; use roc_parse::ast::{Collection, CommentOrNewline, Header, Spaced, Spaces, SpacesBefore}; use roc_parse::header::{ - AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, HostedHeader, ImportsEntry, - ImportsKeyword, Keyword, KeywordItem, ModuleHeader, ModuleName, PackageEntry, PackageHeader, - PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, PlatformKeyword, - PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, - WithKeyword, + AppHeader, ExposedName, ExposesKeyword, HostedHeader, ImportsEntry, ImportsKeyword, Keyword, + KeywordItem, ModuleHeader, ModuleName, PackageEntry, PackageHeader, PackageKeyword, + PackageName, PackagesKeyword, PlatformHeader, PlatformKeyword, PlatformRequires, + ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, }; use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; @@ -63,8 +62,6 @@ macro_rules! keywords { keywords! { ExposesKeyword, ImportsKeyword, - WithKeyword, - GeneratesKeyword, PackageKeyword, PackagesKeyword, RequiresKeyword, @@ -206,9 +203,6 @@ pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) { fmt_exposes(buf, header.exposes.item, indent); header.imports.keyword.format(buf, indent); fmt_imports(buf, header.imports.item, indent); - header.generates.format(buf, indent); - header.generates_with.keyword.format(buf, indent); - fmt_exposes(buf, header.generates_with.item, indent); } pub fn fmt_app_header<'a>(buf: &mut Buf, header: &'a AppHeader<'a>) { diff --git a/crates/compiler/fmt/src/pattern.rs b/crates/compiler/fmt/src/pattern.rs index 015c739a58..dbde68c8fa 100644 --- a/crates/compiler/fmt/src/pattern.rs +++ b/crates/compiler/fmt/src/pattern.rs @@ -244,9 +244,19 @@ impl<'a> Formattable for Pattern<'a> { } As(pattern, pattern_as) => { + let needs_parens = parens == Parens::InAsPattern; + + if needs_parens { + buf.push('('); + } + fmt_pattern(buf, &pattern.value, indent, parens); pattern_as.format(buf, indent + INDENT); + + if needs_parens { + buf.push(')'); + } } // Space diff --git a/crates/compiler/load/build.rs b/crates/compiler/load/build.rs index f99826dce7..6f0956bf7e 100644 --- a/crates/compiler/load/build.rs +++ b/crates/compiler/load/build.rs @@ -25,6 +25,7 @@ const MODULES: &[(ModuleId, &str)] = &[ (ModuleId::DECODE, "Decode.roc"), (ModuleId::HASH, "Hash.roc"), (ModuleId::INSPECT, "Inspect.roc"), + (ModuleId::TASK, "Task.roc"), ]; fn main() { diff --git a/crates/compiler/load/src/lib.rs b/crates/compiler/load/src/lib.rs index 979ff6e353..179a7d1da5 100644 --- a/crates/compiler/load/src/lib.rs +++ b/crates/compiler/load/src/lib.rs @@ -256,6 +256,7 @@ fn read_cached_types() -> MutMap { let mod_decode = include_bytes_align_as!(u128, concat!(env!("OUT_DIR"), "/Decode.dat")); let mod_hash = include_bytes_align_as!(u128, concat!(env!("OUT_DIR"), "/Hash.dat")); let mod_inspect = include_bytes_align_as!(u128, concat!(env!("OUT_DIR"), "/Inspect.dat")); + let mod_task = include_bytes_align_as!(u128, concat!(env!("OUT_DIR"), "/Task.dat")); let mut output = MutMap::default(); @@ -279,6 +280,8 @@ fn read_cached_types() -> MutMap { output.insert(ModuleId::HASH, deserialize_help(mod_hash)); output.insert(ModuleId::INSPECT, deserialize_help(mod_inspect)); + + output.insert(ModuleId::TASK, deserialize_help(mod_task)); } output diff --git a/crates/compiler/load/tests/helpers/mod.rs b/crates/compiler/load/tests/helpers/mod.rs index 4c86b54c8a..c3a6fff90b 100644 --- a/crates/compiler/load/tests/helpers/mod.rs +++ b/crates/compiler/load/tests/helpers/mod.rs @@ -51,7 +51,7 @@ pub fn infer_expr( exposed_by_module: &Default::default(), derived_module, function_kind: FunctionKind::LambdaSet, - params_pattern: None, + module_params: None, module_params_vars: Default::default(), #[cfg(debug_assertions)] checkmate: None, @@ -161,22 +161,6 @@ pub fn can_expr_with<'a>( // ensure the Test module is accessible in our tests module_ids.get_or_insert(&PQModuleName::Unqualified("Test".into())); - // Desugar operators (convert them to Apply calls, taking into account - // operator precedence and associativity rules), before doing other canonicalization. - // - // If we did this *during* canonicalization, then each time we - // visited a BinOp node we'd recursively try to apply this to each of its nested - // operators, and then again on *their* nested operators, ultimately applying the - // rules multiple times unnecessarily. - let loc_expr = desugar::desugar_expr( - arena, - &loc_expr, - expr_str, - &mut None, - arena.alloc("TestPath"), - &mut Default::default(), - ); - let mut scope = Scope::new( home, "TestPath".into(), @@ -187,12 +171,23 @@ pub fn can_expr_with<'a>( let dep_idents = IdentIds::exposed_builtins(0); let mut env = Env::new( arena, + expr_str, home, Path::new("Test.roc"), &dep_idents, &module_ids, None, ); + + // Desugar operators (convert them to Apply calls, taking into account + // operator precedence and associativity rules), before doing other canonicalization. + // + // If we did this *during* canonicalization, then each time we + // visited a BinOp node we'd recursively try to apply this to each of its nested + // operators, and then again on *their* nested operators, ultimately applying the + // rules multiple times unnecessarily. + let loc_expr = desugar::desugar_expr(&mut env, &mut scope, &loc_expr); + let (loc_expr, output) = canonicalize_expr( &mut env, &mut var_store, diff --git a/crates/compiler/load/tests/test_reporting.rs b/crates/compiler/load/tests/test_reporting.rs index ac4bbc391e..99baf06e15 100644 --- a/crates/compiler/load/tests/test_reporting.rs +++ b/crates/compiler/load/tests/test_reporting.rs @@ -3793,9 +3793,9 @@ mod test_reporting { of these? Set + Task List Dict - Hash ── SYNTAX PROBLEM in /code/proj/Main.roc ─────────────────────────────────────── @@ -3804,7 +3804,7 @@ mod test_reporting { 10β”‚ y = { Test.example & age: 3 } ^^^^^^^^^^^^ - Only variables can be updated with record update syntax. + Only variables can be updated with record update syntax. " ); @@ -5760,28 +5760,6 @@ mod test_reporting { "# ); - test_report!( - dbg_without_final_expression, - indoc!( - r" - dbg 42 - " - ), - @r#" - ── INDENT ENDS AFTER EXPRESSION in tmp/dbg_without_final_expression/Test.roc ─── - - I am partway through parsing a dbg statement, but I got stuck here: - - 4β”‚ dbg 42 - ^ - - I was expecting a final expression, like so - - dbg 42 - "done" - "# - ); - test_report!( expect_without_final_expression, indoc!( @@ -6318,16 +6296,17 @@ All branches in an `if` must have the same type! ) } + // TODO: this test seems out of date (what is the `effects` clause?) and as such should be removed #[test] fn platform_requires_rigids() { report_header_problem_as( indoc!( r#" platform "folkertdev/foo" - requires { main : Effect {} } + requires { main : Task {} [] } exposes [] packages {} - imports [Task] + imports [] provides [mainForHost] effects fx.Effect { @@ -6344,13 +6323,13 @@ All branches in an `if` must have the same type! I am partway through parsing a header, but I got stuck here: 1β”‚ platform "folkertdev/foo" - 2β”‚ requires { main : Effect {} } + 2β”‚ requires { main : Task {} [] } ^ I am expecting a list of type names like `{}` or `{ Model }` next. A full `requires` definition looks like - requires { Model, Msg } {main : Effect {}} + requires { Model, Msg } {main : Task {} []} "# ), ) @@ -8116,7 +8095,7 @@ All branches in an `if` must have the same type! unimported_modules_reported, indoc!( r#" - alt : Task.Task {} [] + alt : Unimported.CustomType alt = "whatever man you don't even know my type" alt "# @@ -8124,18 +8103,18 @@ All branches in an `if` must have the same type! @r" ── MODULE NOT IMPORTED in /code/proj/Main.roc ────────────────────────────────── - The `Task` module is not imported: + The `Unimported` module is not imported: - 4β”‚ alt : Task.Task {} [] - ^^^^^^^^^^^^^^^ + 4β”‚ alt : Unimported.CustomType + ^^^^^^^^^^^^^^^^^^^^^ Is there an import missing? Perhaps there is a typo. Did you mean one of these? - Hash + Encode + Inspect + Dict List - Num - Box " ); @@ -14516,6 +14495,76 @@ All branches in an `if` must have the same type! " ); + test_report!( + dbg_unapplied, + indoc!( + r" + 1 + dbg + 2 + " + ), + @r" + ── UNAPPLIED DBG in /code/proj/Main.roc ──────────────────────────────────────── + + This `dbg` doesn't have a value given to it: + + 4β”‚ 1 + dbg + 2 + ^^^ + + `dbg` must be passed a value to print at the exact place it's used. `dbg` + can't be used as a value that's passed around, like functions can be - + it must be applied immediately! + + ── TYPE MISMATCH in /code/proj/Main.roc ──────────────────────────────────────── + + This 2nd argument to + has an unexpected type: + + 4β”‚ 1 + dbg + 2 + ^^^ + + This value is a: + + {} + + But + needs its 2nd argument to be: + + Num * + " + ); + + test_report!( + dbg_overapplied, + indoc!( + r#" + 1 + dbg "" "" + 2 + "# + ), + @r#" + ── OVERAPPLIED DBG in /code/proj/Main.roc ────────────────────────────────────── + + This `dbg` has too many values given to it: + + 4β”‚ 1 + dbg "" "" + 2 + ^^^^^ + + `dbg` must be given exactly one value to print. + + ── TYPE MISMATCH in /code/proj/Main.roc ──────────────────────────────────────── + + This 2nd argument to + has an unexpected type: + + 4β”‚ 1 + dbg "" "" + 2 + ^^^^^^^^^ + + This value is a: + + {} + + But + needs its 2nd argument to be: + + Num * + "# + ); + // TODO: add the following tests after built-in Tasks are added // https://github.com/roc-lang/roc/pull/6836 diff --git a/crates/compiler/load_internal/Cargo.toml b/crates/compiler/load_internal/Cargo.toml index 22e288acbc..21067825c0 100644 --- a/crates/compiler/load_internal/Cargo.toml +++ b/crates/compiler/load_internal/Cargo.toml @@ -33,6 +33,7 @@ roc_tracing = { path = "../../tracing" } roc_types = { path = "../types" } roc_unify = { path = "../unify" } roc_worker = { path = "../worker" } +roc_lower_params = { path = "../lower_params" } ven_pretty = { path = "../../vendor/pretty" } diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index f83b01074e..0b6881e42a 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -18,7 +18,7 @@ use roc_can::constraint::{Constraint as ConstraintSoa, Constraints, TypeOrVar}; use roc_can::expr::{DbgLookup, Declarations, ExpectLookup, PendingDerives}; use roc_can::module::{ canonicalize_module_defs, ExposedByModule, ExposedForModule, ExposedModuleTypes, Module, - ResolvedImplementations, TypeState, + ModuleParams, ResolvedImplementations, TypeState, }; use roc_collections::{default_hasher, BumpMap, MutMap, MutSet, VecMap, VecSet}; use roc_constrain::module::constrain_module; @@ -251,6 +251,7 @@ fn start_phase<'a>( let mut aliases = MutMap::default(); let mut abilities_store = PendingAbilitiesStore::default(); + let mut imported_module_params = VecMap::default(); for imported in parsed.available_modules.keys() { match state.module_cache.aliases.get(imported) { @@ -293,6 +294,10 @@ fn start_phase<'a>( .union(import_store.closure_from_imported(exposed_symbols)); } } + + if let Some(params) = state.module_cache.module_params.get(imported) { + imported_module_params.insert(*imported, params.clone()); + } } let skip_constraint_gen = { @@ -310,6 +315,8 @@ fn start_phase<'a>( abilities_store, skip_constraint_gen, exposed_module_ids: state.exposed_modules, + exec_mode: state.exec_mode, + imported_module_params, } } @@ -356,6 +363,7 @@ fn start_phase<'a>( declarations, state.cached_types.clone(), derived_module, + state.exec_mode, // #[cfg(debug_assertions)] checkmate, @@ -889,6 +897,8 @@ enum BuildTask<'a> { abilities_store: PendingAbilitiesStore, exposed_module_ids: &'a [ModuleId], skip_constraint_gen: bool, + exec_mode: ExecutionMode, + imported_module_params: VecMap, }, Solve { module: Module, @@ -905,6 +915,7 @@ enum BuildTask<'a> { dep_idents: IdentIdsByModule, cached_subs: CachedTypeState, derived_module: SharedDerivedModule, + exec_mode: ExecutionMode, #[cfg(debug_assertions)] checkmate: Option, @@ -2318,6 +2329,7 @@ fn update<'a>( extend_module_with_builtin_import(parsed, ModuleId::DECODE); extend_module_with_builtin_import(parsed, ModuleId::HASH); extend_module_with_builtin_import(parsed, ModuleId::INSPECT); + extend_module_with_builtin_import(parsed, ModuleId::TASK); } state @@ -2395,11 +2407,11 @@ fn update<'a>( .pending_abilities .insert(module_id, constrained_module.module.abilities_store.clone()); - if let Some(params_pattern) = constrained_module.module.params_pattern.clone() { + if let Some(module_params) = constrained_module.module.module_params.clone() { state .module_cache - .param_patterns - .insert(module_id, params_pattern); + .module_params + .insert(module_id, module_params); } state @@ -3557,7 +3569,6 @@ fn load_builtin_module_help<'a>( header_type: HeaderType::Builtin { name: header::ModuleName::new(name_stem), exposes: unspace(arena, header.exposes.items), - generates_with: &[], opt_params: header.params, }, module_comments: comments, @@ -3640,6 +3651,7 @@ fn load_module<'a>( "Decode", ModuleId::DECODE "Hash", ModuleId::HASH "Inspect", ModuleId::INSPECT + "Task", ModuleId::TASK } let (filename, opt_shorthand) = module_name_to_path(src_dir, &module_name, arc_shorthands); @@ -3875,8 +3887,6 @@ fn parse_header<'a>( header_type: HeaderType::Hosted { name: header.name.value, exposes: unspace(arena, header.exposes.item.items), - generates: header.generates.item, - generates_with: unspace(arena, header.generates_with.item.items), }, module_comments: comments, header_imports: Some(header.imports), @@ -4311,6 +4321,7 @@ impl<'a> BuildTask<'a> { declarations: Declarations, cached_subs: CachedTypeState, derived_module: SharedDerivedModule, + exec_mode: ExecutionMode, #[cfg(debug_assertions)] checkmate: Option, ) -> Self { @@ -4334,6 +4345,7 @@ impl<'a> BuildTask<'a> { module_timing, cached_subs, derived_module, + exec_mode, #[cfg(debug_assertions)] checkmate, @@ -4618,6 +4630,7 @@ struct SolveResult { exposed_vars_by_symbol: Vec<(Symbol, Variable)>, problems: Vec, abilities_store: AbilitiesStore, + imported_modules_with_params: Vec, #[cfg(debug_assertions)] checkmate: Option, @@ -4642,7 +4655,7 @@ fn run_solve_solve( aliases, rigid_variables, abilities_store: pending_abilities, - params_pattern, + module_params, .. } = module; @@ -4663,6 +4676,11 @@ fn run_solve_solve( &mut imported_flex_vars, ); + let imported_modules_with_params = imported_param_vars + .keys() + .copied() + .collect::>(); + let actual_constraint = constraints.let_import_constraint( imported_rigid_vars, imported_flex_vars, @@ -4690,7 +4708,7 @@ fn run_solve_solve( derived_module, #[cfg(debug_assertions)] checkmate, - params_pattern: params_pattern.map(|(_, _, pattern)| pattern.value), + module_params, module_params_vars: imported_param_vars, }; @@ -4745,6 +4763,7 @@ fn run_solve_solve( exposed_vars_by_symbol, problems: errors, abilities_store: resolved_abilities_store, + imported_modules_with_params, #[cfg(debug_assertions)] checkmate, @@ -4766,6 +4785,7 @@ fn run_solve<'a>( dep_idents: IdentIdsByModule, cached_types: CachedTypeState, derived_module: SharedDerivedModule, + exec_mode: ExecutionMode, #[cfg(debug_assertions)] checkmate: Option, ) -> Msg<'a> { @@ -4776,10 +4796,8 @@ fn run_solve<'a>( // TODO remove when we write builtins in roc let aliases = module.aliases.clone(); - let opt_params_var = module - .params_pattern - .as_ref() - .map(|(params_var, _, _)| *params_var); + let opt_params_var = module.module_params.as_ref().map(|params| params.whole_var); + let home_has_params = opt_params_var.is_some(); let mut module = module; let loc_expects = std::mem::take(&mut module.loc_expects); @@ -4814,6 +4832,7 @@ fn run_solve<'a>( exposed_vars_by_symbol, problems: vec![], abilities_store: abilities, + imported_modules_with_params: vec![], #[cfg(debug_assertions)] checkmate: None, @@ -4841,8 +4860,9 @@ fn run_solve<'a>( solved: mut solved_subs, solved_implementations, exposed_vars_by_symbol, - problems, + mut problems, abilities_store, + imported_modules_with_params, #[cfg(debug_assertions)] checkmate, @@ -4857,6 +4877,20 @@ fn run_solve<'a>( &abilities_store, ); + match exec_mode { + ExecutionMode::Check => { + // Params are not lowered in check mode + } + ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck | ExecutionMode::Test => { + roc_lower_params::type_error::remove_module_param_arguments( + &mut problems, + home_has_params, + &decls.symbols, + imported_modules_with_params, + ); + } + } + let solved_module = SolvedModule { exposed_vars_by_symbol, problems, @@ -5006,6 +5040,8 @@ fn canonicalize_and_constrain<'a>( parsed: ParsedModule<'a>, skip_constraint_gen: bool, exposed_module_ids: &[ModuleId], + exec_mode: ExecutionMode, + imported_module_params: VecMap, ) -> CanAndCon { let canonicalize_start = Instant::now(); @@ -5032,7 +5068,7 @@ fn canonicalize_and_constrain<'a>( let mut var_store = VarStore::default(); - let module_output = canonicalize_module_defs( + let mut module_output = canonicalize_module_defs( arena, parsed_defs, &header_type, @@ -5091,6 +5127,26 @@ fn canonicalize_and_constrain<'a>( // _before has an underscore because it's unused in --release builds let _before = roc_types::types::get_type_clone_count(); + match exec_mode { + ExecutionMode::Check => { + // No need to lower params for `roc check` and lang server + // If we did, we'd have to update the language server to exclude the extra arguments + } + ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck | ExecutionMode::Test => { + // We need to lower params only if the current module has any or imports at least one with params + if module_output.module_params.is_some() || !imported_module_params.is_empty() { + roc_lower_params::lower::lower( + module_id, + &module_output.module_params, + imported_module_params, + &mut module_output.declarations, + &mut module_output.scope.locals.ident_ids, + &mut var_store, + ); + } + } + } + let mut constraints = Constraints::new(); let constraint = if skip_constraint_gen { @@ -5102,7 +5158,7 @@ fn canonicalize_and_constrain<'a>( module_output.symbols_from_requires, &module_output.scope.abilities_store, &module_output.declarations, - &module_output.params_pattern, + &module_output.module_params, module_id, ) }; @@ -5140,6 +5196,7 @@ fn canonicalize_and_constrain<'a>( | ModuleId::SET | ModuleId::HASH | ModuleId::INSPECT + | ModuleId::TASK ); if !name.is_builtin() || should_include_builtin { @@ -5159,7 +5216,7 @@ fn canonicalize_and_constrain<'a>( abilities_store: module_output.scope.abilities_store, loc_expects: module_output.loc_expects, loc_dbgs: module_output.loc_dbgs, - params_pattern: module_output.params_pattern, + module_params: module_output.module_params, }; let constrained_module = ConstrainedModule { @@ -6209,6 +6266,8 @@ fn run_task<'a>( abilities_store, skip_constraint_gen, exposed_module_ids, + exec_mode, + imported_module_params, } => { let can_and_con = canonicalize_and_constrain( arena, @@ -6220,6 +6279,8 @@ fn run_task<'a>( parsed, skip_constraint_gen, exposed_module_ids, + exec_mode, + imported_module_params, ); Ok(Msg::CanonicalizedAndConstrained(can_and_con)) @@ -6239,6 +6300,7 @@ fn run_task<'a>( dep_idents, cached_subs, derived_module, + exec_mode, #[cfg(debug_assertions)] checkmate, @@ -6257,6 +6319,7 @@ fn run_task<'a>( dep_idents, cached_subs, derived_module, + exec_mode, // #[cfg(debug_assertions)] checkmate, diff --git a/crates/compiler/load_internal/src/lib.rs b/crates/compiler/load_internal/src/lib.rs index ef7860a54a..a19a0a445f 100644 --- a/crates/compiler/load_internal/src/lib.rs +++ b/crates/compiler/load_internal/src/lib.rs @@ -25,4 +25,5 @@ pub const BUILTIN_MODULES: &[(ModuleId, &str)] = &[ (ModuleId::DECODE, "Decode"), (ModuleId::HASH, "Hash"), (ModuleId::INSPECT, "Inspect"), + (ModuleId::TASK, "Task"), ]; diff --git a/crates/compiler/load_internal/src/module_cache.rs b/crates/compiler/load_internal/src/module_cache.rs index ed62692a26..e53c4ee5cc 100644 --- a/crates/compiler/load_internal/src/module_cache.rs +++ b/crates/compiler/load_internal/src/module_cache.rs @@ -4,13 +4,13 @@ use crate::module::{ ModuleHeader, ParsedModule, TypeCheckedModule, }; use roc_can::abilities::PendingAbilitiesStore; -use roc_can::expr::AnnotatedMark; +use roc_can::module::ModuleParams; use roc_collections::{MutMap, MutSet, VecMap}; use roc_module::ident::ModuleName; use roc_module::symbol::{ModuleId, PQModuleName, Symbol}; use roc_mono::ir::ExternalSpecializations; use roc_problem::Severity; -use roc_region::all::{Loc, Region}; +use roc_region::all::Region; use roc_solve_problem::TypeError; use roc_types::subs::Variable; use roc_types::types::Alias; @@ -27,8 +27,7 @@ pub(crate) struct ModuleCache<'a> { pub(crate) aliases: MutMap>, pub(crate) pending_abilities: MutMap, pub(crate) constrained: MutMap, - pub(crate) param_patterns: - MutMap)>, + pub(crate) module_params: MutMap, pub(crate) typechecked: MutMap>, pub(crate) checked: MutMap, pub(crate) found_specializations: MutMap>, @@ -94,6 +93,7 @@ impl Default for ModuleCache<'_> { DECODE, HASH, INSPECT, + TASK, } Self { @@ -103,7 +103,7 @@ impl Default for ModuleCache<'_> { aliases: Default::default(), pending_abilities: Default::default(), constrained: Default::default(), - param_patterns: Default::default(), + module_params: Default::default(), typechecked: Default::default(), checked: Default::default(), found_specializations: Default::default(), diff --git a/crates/compiler/load_internal/tests/test_load.rs b/crates/compiler/load_internal/tests/test_load.rs index 30d530635e..5abd823c05 100644 --- a/crates/compiler/load_internal/tests/test_load.rs +++ b/crates/compiler/load_internal/tests/test_load.rs @@ -2151,7 +2151,7 @@ fn roc_file_no_extension() { indoc!( r#" app "helloWorld" - packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } + packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } imports [pf.Stdout] provides [main] to pf diff --git a/crates/compiler/lower_params/Cargo.toml b/crates/compiler/lower_params/Cargo.toml new file mode 100644 index 0000000000..8652a3ef8f --- /dev/null +++ b/crates/compiler/lower_params/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "roc_lower_params" +description = "Lowers module params by extending functions and their calls" + +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true + +[dependencies] +roc_can = { path = "../can" } +roc_module = { path = "../module" } +roc_region = { path = "../region" } +roc_types = { path = "../types" } +roc_collections = { path = "../collections" } +roc_solve_problem = { path = "../solve_problem" } + +bumpalo.workspace = true diff --git a/crates/compiler/lower_params/src/lib.rs b/crates/compiler/lower_params/src/lib.rs new file mode 100644 index 0000000000..5d684736c6 --- /dev/null +++ b/crates/compiler/lower_params/src/lib.rs @@ -0,0 +1,2 @@ +pub mod lower; +pub mod type_error; diff --git a/crates/compiler/lower_params/src/lower.rs b/crates/compiler/lower_params/src/lower.rs new file mode 100644 index 0000000000..43f312ce73 --- /dev/null +++ b/crates/compiler/lower_params/src/lower.rs @@ -0,0 +1,566 @@ +use roc_can::{ + expr::{ + AnnotatedMark, ClosureData, + DeclarationTag::*, + Declarations, + Expr::{self, *}, + }, + module::ModuleParams, + pattern::{Pattern, RecordDestruct}, +}; +use roc_collections::VecMap; +use roc_module::symbol::{IdentId, IdentIds, ModuleId, Symbol}; +use roc_region::all::Loc; +use roc_types::{ + subs::{VarStore, Variable}, + types::Type, +}; +use std::iter::once; + +struct LowerParams<'a> { + home_id: ModuleId, + home_params: &'a Option, + imported_params: VecMap, + var_store: &'a mut VarStore, + ident_ids: &'a mut IdentIds, + top_level_idents: Vec, +} + +pub fn lower( + home_id: ModuleId, + home_params: &Option, + imported_params: VecMap, + decls: &mut Declarations, + ident_ids: &mut IdentIds, + var_store: &mut VarStore, +) { + let top_level_idents = decls + .symbols + .iter() + .map(|symbol| symbol.value.ident_id()) + .collect(); + + let mut env = LowerParams { + home_id, + home_params, + imported_params, + ident_ids, + var_store, + top_level_idents, + }; + + env.lower_decls(decls); +} + +impl<'a> LowerParams<'a> { + fn lower_decls(&mut self, decls: &mut Declarations) { + let home_param_symbols = match self.home_params { + Some(params) => params + .destructs + .iter() + .map(|destruct| destruct.value.symbol) + .chain(once(params.whole_symbol)) + .collect::>(), + None => vec![], + }; + + for index in 0..decls.len() { + let tag = decls.declarations[index]; + + match tag { + Value => { + self.lower_expr(&mut decls.expressions[index].value); + + if let Some(new_arg) = self.home_params_argument() { + // This module has params, and this is a top-level value, + // so we need to convert it into a function that takes them. + + decls.convert_value_to_function(index, vec![new_arg], self.var_store); + } + } + Function(fn_def_index) | Recursive(fn_def_index) | TailRecursive(fn_def_index) => { + if let Some((var, mark, pattern)) = self.home_params_argument() { + // This module has params, and this is a top-level function, + // so we need to extend its definition to take them. + + let function_body = &mut decls.function_bodies[fn_def_index.index()].value; + function_body.arguments.push((var, mark, pattern)); + + // Remove home params from the captured symbols, only nested lambdas need them. + function_body + .captured_symbols + .retain(|(sym, _)| !home_param_symbols.contains(sym)); + + if let Some(ann) = &mut decls.annotations[index] { + if let Type::Function(args, _, _) = &mut ann.signature { + args.push(Type::Variable(var)); + } + } + } + + self.lower_expr(&mut decls.expressions[index].value); + } + + Destructure(_) | Expectation | ExpectationFx => { + self.lower_expr(&mut decls.expressions[index].value); + } + MutualRecursion { .. } => {} + } + } + } + + fn lower_expr(&mut self, expr: &mut Expr) { + let mut expr_stack = vec![expr]; + + while let Some(expr) = expr_stack.pop() { + match expr { + // Nodes to lower + ParamsVar { + symbol, + var, + params_symbol, + params_var, + } => { + // The module was imported with params, but it might not actually expect them. + // We should only lower if it does to prevent confusing type errors. + if let Some(arity) = self.get_imported_def_arity(symbol) { + *expr = self.lower_naked_params_var( + arity, + *symbol, + *var, + *params_symbol, + *params_var, + ); + } + } + Var(symbol, var) => { + if let Some((params, arity)) = self.params_extended_home_symbol(symbol) { + *expr = self.lower_naked_params_var( + arity, + *symbol, + *var, + params.whole_symbol, + params.whole_var, + ); + } + } + Call(fun, args, _called_via) => { + expr_stack.reserve(args.len() + 1); + + match fun.1.value { + ParamsVar { + symbol, + var, + params_var, + params_symbol, + } => { + // Calling an imported function with params + + match self.get_imported_def_arity(&symbol) { + Some(0) => { + // We are calling a function but the top-level declaration has no arguments. + // This can either be a function alias or a top-level def that returns functions + // under multiple branches. + // We call the value def with params, and apply the returned function to the original arguments. + fun.1.value = self.call_value_def_with_params( + symbol, + var, + params_symbol, + params_var, + ); + } + Some(_) => { + // The module expects params and they were provided, we need to extend the call. + fun.1.value = Var(symbol, var); + + args.push(( + params_var, + Loc::at_zero(Var(params_symbol, params_var)), + )); + } + None => { + // The module expects no params, do not extend to prevent confusing type errors. + fun.1.value = Var(symbol, var); + } + } + } + Var(symbol, var) => { + if let Some((params, arity)) = self.params_extended_home_symbol(&symbol) + { + if arity == 0 { + // Calling the result of a top-level value def in the current module + fun.1.value = self.call_value_def_with_params( + symbol, + var, + params.whole_symbol, + params.whole_var, + ); + } else { + // Calling a top-level function in the current module with params + args.push(( + params.whole_var, + Loc::at_zero(Var(params.whole_symbol, params.whole_var)), + )); + } + } + } + _ => expr_stack.push(&mut fun.1.value), + } + + for (_, arg) in args.iter_mut() { + expr_stack.push(&mut arg.value); + } + } + + // Nodes to walk + Closure(ClosureData { + loc_body, + captured_symbols: _, + name: _, + function_type: _, + closure_type: _, + return_type: _, + recursive: _, + arguments: _, + }) => { + expr_stack.push(&mut loc_body.value); + } + + LetNonRec(def, cont) => { + expr_stack.reserve(2); + expr_stack.push(&mut def.loc_expr.value); + expr_stack.push(&mut cont.value); + } + + LetRec(defs, cont, _cycle_mark) => { + expr_stack.reserve(defs.len() + 1); + + for def in defs { + expr_stack.push(&mut def.loc_expr.value); + } + + expr_stack.push(&mut cont.value); + } + When { + loc_cond, + branches, + cond_var: _, + expr_var: _, + region: _, + branches_cond_var: _, + exhaustive: _, + } => { + expr_stack.reserve(branches.len() + 1); + expr_stack.push(&mut loc_cond.value); + + for branch in branches.iter_mut() { + expr_stack.push(&mut branch.value.value); + } + } + If { + branches, + final_else, + cond_var: _, + branch_var: _, + } => { + expr_stack.reserve(branches.len() * 2 + 1); + + for (cond, ret) in branches.iter_mut() { + expr_stack.push(&mut cond.value); + expr_stack.push(&mut ret.value); + } + + expr_stack.push(&mut final_else.value); + } + RunLowLevel { + args, + op: _, + ret_var: _, + } + | ForeignCall { + foreign_symbol: _, + args, + ret_var: _, + } => { + expr_stack.extend(args.iter_mut().map(|(_, arg)| arg)); + } + List { + elem_var: _, + loc_elems, + } => { + expr_stack.extend(loc_elems.iter_mut().map(|loc_elem| &mut loc_elem.value)); + } + Record { + record_var: _, + fields, + } => { + expr_stack.extend( + fields + .iter_mut() + .map(|(_, field)| &mut field.loc_expr.value), + ); + } + Tuple { + tuple_var: _, + elems, + } => { + expr_stack.extend(elems.iter_mut().map(|(_, elem)| &mut elem.value)); + } + ImportParams(_, _, Some((_, params_expr))) => { + expr_stack.push(params_expr); + } + Crash { msg, ret_var: _ } => { + expr_stack.push(&mut msg.value); + } + RecordAccess { + loc_expr, + record_var: _, + ext_var: _, + field_var: _, + field: _, + } => expr_stack.push(&mut loc_expr.value), + TupleAccess { + loc_expr, + tuple_var: _, + ext_var: _, + elem_var: _, + index: _, + } => expr_stack.push(&mut loc_expr.value), + RecordUpdate { + updates, + record_var: _, + ext_var: _, + symbol: _, + } => expr_stack.extend( + updates + .iter_mut() + .map(|(_, field)| &mut field.loc_expr.value), + ), + Tag { + arguments, + tag_union_var: _, + ext_var: _, + name: _, + } => expr_stack.extend(arguments.iter_mut().map(|(_, arg)| &mut arg.value)), + OpaqueRef { + argument, + opaque_var: _, + name: _, + specialized_def_type: _, + type_arguments: _, + lambda_set_variables: _, + } => expr_stack.push(&mut argument.1.value), + Expect { + loc_condition, + loc_continuation, + lookups_in_cond: _, + } => { + expr_stack.reserve(2); + expr_stack.push(&mut loc_condition.value); + expr_stack.push(&mut loc_continuation.value); + } + ExpectFx { + loc_condition, + loc_continuation, + lookups_in_cond: _, + } => { + expr_stack.reserve(2); + expr_stack.push(&mut loc_condition.value); + expr_stack.push(&mut loc_continuation.value); + } + Dbg { + loc_message, + loc_continuation, + source_location: _, + source: _, + variable: _, + symbol: _, + } => { + expr_stack.reserve(2); + expr_stack.push(&mut loc_message.value); + expr_stack.push(&mut loc_continuation.value); + } + RecordAccessor(_) + | ImportParams(_, _, None) + | ZeroArgumentTag { + closure_name: _, + variant_var: _, + ext_var: _, + name: _, + } + | OpaqueWrapFunction(_) + | EmptyRecord + | TypedHole(_) + | RuntimeError(_) + | Num(_, _, _, _) + | Int(_, _, _, _, _) + | Float(_, _, _, _, _) + | Str(_) + | SingleQuote(_, _, _, _) + | IngestedFile(_, _, _) + | AbilityMember(_, _, _) => { /* terminal */ } + } + } + } + + fn unique_symbol(&mut self) -> Symbol { + Symbol::new(self.home_id, self.ident_ids.gen_unique()) + } + + fn home_params_argument(&mut self) -> Option<(Variable, AnnotatedMark, Loc)> { + match &self.home_params { + Some(module_params) => { + let destructs: Vec> = module_params + .destructs + .iter() + .map(|destructure| { + destructure.map(|d| RecordDestruct { + symbol: d.symbol, + var: self.var_store.fresh(), + label: d.label.clone(), + typ: d.typ.clone(), + }) + }) + .collect(); + + let record_pattern = Pattern::RecordDestructure { + whole_var: module_params.record_var, + ext_var: module_params.record_ext_var, + destructs, + }; + let loc_record_pattern = Loc::at(module_params.region, record_pattern); + let as_pattern = + Pattern::As(Box::new(loc_record_pattern), module_params.whole_symbol); + let loc_pattern = Loc::at(module_params.region, as_pattern); + + Some(( + self.var_store.fresh(), + AnnotatedMark::new(self.var_store), + loc_pattern, + )) + } + None => None, + } + } + + fn params_extended_home_symbol(&self, symbol: &Symbol) -> Option<(&ModuleParams, usize)> { + if symbol.module_id() == self.home_id { + self.home_params.as_ref().and_then(|params| { + params + .arity_by_name + .get(&symbol.ident_id()) + .map(|arity| (params, *arity)) + }) + } else { + None + } + } + + fn get_imported_def_arity(&self, symbol: &Symbol) -> Option { + self.imported_params + .get(&symbol.module_id()) + .and_then(|params| params.arity_by_name.get(&symbol.ident_id()).copied()) + } + + fn lower_naked_params_var( + &mut self, + arity: usize, + symbol: Symbol, + var: Variable, + params_symbol: Symbol, + params_var: Variable, + ) -> Expr { + if arity == 0 { + // We are passing a top-level value that takes params, so we need to replace the Var + // with a call that passes the params to get the final result. + // + // value = \#params -> #params.x * 2 + // record = \... #params -> { doubled: value } + // ↓ + // value #params + self.call_value_def_with_params(symbol, var, params_symbol, params_var) + } else { + // We are passing a top-level function that takes params, so we need to replace + // the Var with a closure that captures the params and passes them to the function. + // + // fn1 = \arg #params -> #params.x * arg + // fn2 = \... #params -> List.map [1, 2] fn1 + // ↓ + // (\#1 -> fn1 #1 #params) + // + let mut arguments = Vec::with_capacity(arity); + let mut call_arguments = Vec::with_capacity(arity + 1); + + for _ in 0..arity { + let sym = self.unique_symbol(); + let var = self.var_store.fresh(); + + arguments.push(( + var, + AnnotatedMark::new(self.var_store), + Loc::at_zero(Pattern::Identifier(sym)), + )); + call_arguments.push((var, Loc::at_zero(Var(sym, var)))); + } + + let params_arg = (params_var, Loc::at_zero(Var(params_symbol, params_var))); + + call_arguments.push(params_arg); + + let call_fn = Box::new(( + self.var_store.fresh(), + Loc::at_zero(Var(symbol, var)), + self.var_store.fresh(), + self.var_store.fresh(), + )); + + let body = Call( + call_fn, + call_arguments, + roc_module::called_via::CalledVia::NakedParamsVar, + ); + + let captured_symbols = if symbol.module_id() == self.home_id + || !self.top_level_idents.contains(¶ms_symbol.ident_id()) + { + vec![(params_symbol, params_var)] + } else { + vec![] + }; + + Closure(ClosureData { + function_type: self.var_store.fresh(), + closure_type: self.var_store.fresh(), + return_type: self.var_store.fresh(), + name: self.unique_symbol(), + captured_symbols, + recursive: roc_can::expr::Recursive::NotRecursive, + arguments, + loc_body: Box::new(Loc::at_zero(body)), + }) + } + } + + fn call_value_def_with_params( + &mut self, + symbol: Symbol, + var: Variable, + params_symbol: Symbol, + params_var: Variable, + ) -> Expr { + let params_arg = (params_var, Loc::at_zero(Var(params_symbol, params_var))); + + let call_fn = Box::new(( + self.var_store.fresh(), + Loc::at_zero(Var(symbol, var)), + self.var_store.fresh(), + self.var_store.fresh(), + )); + + Call( + call_fn, + vec![params_arg], + roc_module::called_via::CalledVia::NakedParamsVar, + ) + } +} diff --git a/crates/compiler/lower_params/src/type_error.rs b/crates/compiler/lower_params/src/type_error.rs new file mode 100644 index 0000000000..da16882d7e --- /dev/null +++ b/crates/compiler/lower_params/src/type_error.rs @@ -0,0 +1,208 @@ +use roc_can::{expected::Expected, pattern::Pattern}; +use roc_module::symbol::{ModuleId, Symbol}; +use roc_region::all::Loc; +use roc_solve_problem::TypeError; +use roc_types::types::{ErrorType, Reason}; + +/// Removes the arguments added as a result of lowering params +pub fn remove_module_param_arguments( + errors: &mut Vec, + home_has_params: bool, + home_top_level_symbols: &[Loc], + imported_modules_with_params: Vec, +) { + if errors.is_empty() || (!home_has_params && imported_modules_with_params.is_empty()) { + return; + } + + let home_top_level_symbols = if home_has_params { + home_top_level_symbols + .iter() + .map(|loc| loc.value) + .collect::>() + } else { + vec![] + }; + + let env = Env { + home_top_level_symbols, + imported_modules_with_params, + }; + + for error in errors { + match error { + TypeError::BadExpr(_, _, found, Expected::ForReason(reason, expected, _)) => { + remove_for_reason(&env, found, reason, expected); + } + + TypeError::BadExpr( + _, + _, + found, + Expected::FromAnnotation( + Loc { + value: Pattern::Identifier(name), + region: _, + }, + arity, + _, + expected, + ), + ) if env.is_extended(name) => { + if *arity > 1 { + *arity -= 1; + } + + drop_last_argument(found); + drop_last_argument(expected); + + if let ( + ErrorType::Function(found_args, _, _), + ErrorType::Function(expected_args, _, _), + ) = (found, expected) + { + if found_args.len() > expected_args.len() { + // If the found arity doesn't match the annotation's, the params + // record would appear in the error, so we replace it with `?`. + if let Some(last) = found_args.last_mut() { + *last = ErrorType::Error; + } + } + } + } + + TypeError::BadExpr(_, _, _, Expected::FromAnnotation(_, _, _, _)) + | TypeError::BadExpr(_, _, _, Expected::NoExpectation(_)) => {} + + // Irrelevant + TypeError::BadPattern(_, _, _, _) + | TypeError::CircularType(_, _, _) + | TypeError::CircularDef(_) + | TypeError::UnexposedLookup(_, _) + | TypeError::UnfulfilledAbility(_) + | TypeError::BadExprMissingAbility(_, _, _, _) + | TypeError::BadPatternMissingAbility(_, _, _, _) + | TypeError::Exhaustive(_) + | TypeError::StructuralSpecialization { + region: _, + typ: _, + ability: _, + member: _, + } + | TypeError::WrongSpecialization { + region: _, + ability_member: _, + expected_opaque: _, + found_opaque: _, + } + | TypeError::IngestedFileBadUtf8(_, _) + | TypeError::IngestedFileUnsupportedType(_, _) + | TypeError::UnexpectedModuleParams(_, _) + | TypeError::MissingModuleParams(_, _, _) + | TypeError::ModuleParamsMismatch(_, _, _, _) => {} + } + } +} + +struct Env { + home_top_level_symbols: Vec, + imported_modules_with_params: Vec, +} + +impl Env { + fn is_extended(&self, symbol: &Symbol) -> bool { + self.home_top_level_symbols.contains(symbol) + || self + .imported_modules_with_params + .contains(&symbol.module_id()) + } +} + +fn remove_for_reason( + env: &Env, + found_type: &mut ErrorType, + reason: &mut Reason, + expected_type: &mut ErrorType, +) { + match reason { + Reason::FnCall { + name: Some(name), + arity, + called_via: _, + } if env.is_extended(name) => { + *arity -= 1; + debug_assert_ne!(0, *arity); + + drop_last_argument(found_type); + drop_last_argument(expected_type); + } + + Reason::FnCall { .. } => {} + + Reason::FnArg { .. } | Reason::TypedArg { .. } => { + // I believe these don't need to be touched because reporting only + // shows the type of the arguments, not the whole function. + } + + // Irrelevant + Reason::LowLevelOpArg { + op: _, + arg_index: _, + } + | Reason::ForeignCallArg { + foreign_symbol: _, + arg_index: _, + } + | Reason::FloatLiteral + | Reason::IntLiteral + | Reason::NumLiteral + | Reason::StrInterpolation + | Reason::WhenBranches + | Reason::WhenBranch { index: _ } + | Reason::WhenGuard + | Reason::ExpectCondition + | Reason::IfCondition + | Reason::IfBranch { + index: _, + total_branches: _, + } + | Reason::ElemInList { index: _ } + | Reason::RecordUpdateValue(_) + | Reason::RecordUpdateKeys(_, _) + | Reason::RecordDefaultField(_) + | Reason::NumericLiteralSuffix + | Reason::InvalidAbilityMemberSpecialization { + member_name: _, + def_region: _, + unimplemented_abilities: _, + } + | Reason::GeneralizedAbilityMemberSpecialization { + member_name: _, + def_region: _, + } + | Reason::CrashArg + | Reason::ImportParams(_) => {} + } +} + +fn drop_last_argument(err_type: &mut ErrorType) { + match err_type { + ErrorType::Function(arguments, _, _) => { + arguments.pop(); + } + // Irrelevant + ErrorType::Infinite + | ErrorType::Type(_, _) + | ErrorType::FlexVar(_) + | ErrorType::RigidVar(_) + | ErrorType::FlexAbleVar(_, _) + | ErrorType::RigidAbleVar(_, _) + | ErrorType::Record(_, _) + | ErrorType::Tuple(_, _) + | ErrorType::TagUnion(_, _, _) + | ErrorType::RecursiveTagUnion(_, _, _, _) + | ErrorType::Alias(_, _, _, _) + | ErrorType::Range(_) + | ErrorType::Error => {} + } +} diff --git a/crates/compiler/module/src/called_via.rs b/crates/compiler/module/src/called_via.rs index 18c274020a..e64cbbce38 100644 --- a/crates/compiler/module/src/called_via.rs +++ b/crates/compiler/module/src/called_via.rs @@ -99,6 +99,9 @@ pub enum CalledVia { /// This call is the result of desugaring a Result.try from `?` syntax /// e.g. Dict.get? items "key" becomes Result.try (Dict.get items "key") \item -> ... QuestionSuffix, + + /// This call is a result of lowering a reference to a module-params-extended def + NakedParamsVar, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index ab2ddef015..93c4e7b119 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -123,6 +123,10 @@ impl Symbol { .any(|(_, (s, _))| *s == self) } + pub fn is_generated(self, interns: &Interns) -> bool { + self.ident_ids(interns).is_generated_id(self.ident_id()) + } + pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a ModuleName { interns .module_ids @@ -137,24 +141,15 @@ impl Symbol { } pub fn as_str(self, interns: &Interns) -> &str { - let ident_ids = interns - .all_ident_ids - .get(&self.module_id()) + self.ident_ids(interns) + .get_name(self.ident_id()) .unwrap_or_else(|| { internal_error!( - "ident_string could not find IdentIds for module {:?} in {:?}", - self.module_id(), - interns + "ident_string's IdentIds did not contain an entry for {} in module {:?}", + self.ident_id().0, + self.module_id() ) - }); - - ident_ids.get_name(self.ident_id()).unwrap_or_else(|| { - internal_error!( - "ident_string's IdentIds did not contain an entry for {} in module {:?}", - self.ident_id().0, - self.module_id() - ) - }) + }) } pub const fn as_u64(self) -> u64 { @@ -186,6 +181,19 @@ impl Symbol { pub fn contains(self, needle: &str) -> bool { format!("{self:?}").contains(needle) } + + fn ident_ids(self, interns: &Interns) -> &IdentIds { + interns + .all_ident_ids + .get(&self.module_id()) + .unwrap_or_else(|| { + internal_error!( + "ident_string could not find IdentIds for module {:?} in {:?}", + self.module_id(), + interns + ) + }) + } } /// Rather than displaying as this: @@ -707,6 +715,12 @@ impl IdentIds { IdentId(self.interner.insert_index_str() as u32) } + pub fn is_generated_id(&self, id: IdentId) -> bool { + self.interner + .try_get(id.0 as usize) + .map_or(false, |str| str.starts_with(|c: char| c.is_ascii_digit())) + } + #[inline(always)] pub fn get_id(&self, ident_name: &str) -> Option { self.interner @@ -1666,6 +1680,23 @@ define_builtins! { 32 INSPECT_TO_INSPECTOR: "toInspector" 33 INSPECT_TO_STR: "toStr" } + 15 TASK: "Task" => { + 0 TASK_TASK: "Task" exposed_type=true // the Task.Task opaque type + 1 TASK_FOREVER: "forever" + 2 TASK_LOOP: "loop" + 3 TASK_OK: "ok" + 4 TASK_ERR: "err" + 5 TASK_ATTEMPT: "attempt" + 6 TASK_AWAIT: "await" + 7 TASK_ON_ERR: "onErr" + 8 TASK_MAP: "map" + 9 TASK_MAP_ERR: "mapErr" + 10 TASK_FROM_RESULT: "fromResult" + 11 TASK_BATCH: "batch" + 12 TASK_SEQUENCE: "sequence" + 13 TASK_FOR_EACH: "forEach" + 14 TASK_RESULT: "result" + } - num_modules: 15 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro) + num_modules: 16 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro) } diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index d4956cc154..e43e608868 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -2870,7 +2870,6 @@ fn pattern_to_when( body: Loc, ) -> (Symbol, Loc) { use roc_can::expr::Expr::*; - use roc_can::expr::{WhenBranch, WhenBranchPattern}; use roc_can::pattern::Pattern::{self, *}; match &pattern.value { @@ -2888,7 +2887,7 @@ fn pattern_to_when( (*new_symbol, Loc::at_zero(RuntimeError(error))) } - As(_, _) => todo!("as bindings are not supported yet"), + As(pattern, symbol) => pattern_to_when_help(pattern_var, pattern, body_var, body, *symbol), UnsupportedPattern(region) => { // create the runtime error here, instead of delegating to When. @@ -2915,28 +2914,7 @@ fn pattern_to_when( | TupleDestructure { .. } | UnwrappedOpaque { .. } => { let symbol = env.unique_symbol(); - - let wrapped_body = When { - cond_var: pattern_var, - expr_var: body_var, - region: Region::zero(), - loc_cond: Box::new(Loc::at_zero(Var(symbol, pattern_var))), - branches: vec![WhenBranch { - patterns: vec![WhenBranchPattern { - pattern, - degenerate: false, - }], - value: body, - guard: None, - // If this type-checked, it's non-redundant - redundant: RedundantMark::known_non_redundant(), - }], - branches_cond_var: pattern_var, - // If this type-checked, it's exhaustive - exhaustive: ExhaustiveMark::known_exhaustive(), - }; - - (symbol, Loc::at_zero(wrapped_body)) + pattern_to_when_help(pattern_var, &pattern, body_var, body, symbol) } Pattern::List { .. } => todo!(), @@ -2960,6 +2938,38 @@ fn pattern_to_when( } } +fn pattern_to_when_help( + pattern_var: Variable, + pattern: &Loc, + body_var: Variable, + body: Loc, + symbol: Symbol, +) -> (Symbol, Loc) { + use roc_can::expr::{Expr, WhenBranch, WhenBranchPattern}; + + let wrapped_body = Expr::When { + cond_var: pattern_var, + expr_var: body_var, + region: Region::zero(), + loc_cond: Box::new(Loc::at_zero(Expr::Var(symbol, pattern_var))), + branches: vec![WhenBranch { + patterns: vec![WhenBranchPattern { + pattern: pattern.to_owned(), + degenerate: false, + }], + value: body, + guard: None, + // If this type-checked, it's non-redundant + redundant: RedundantMark::known_non_redundant(), + }], + branches_cond_var: pattern_var, + // If this type-checked, it's exhaustive + exhaustive: ExhaustiveMark::known_exhaustive(), + }; + + (symbol, Loc::at_zero(wrapped_body)) +} + fn specialize_suspended<'a>( env: &mut Env<'a, '_>, procs: &mut Procs<'a>, @@ -4444,7 +4454,7 @@ pub fn with_hole<'a>( specialize_naked_symbol(env, variable, procs, layout_cache, assigned, hole, symbol) } ParamsVar { .. } => { - unimplemented!("module params code generation") + internal_error!("ParamsVar should've been lowered to Var") } ImportParams(_, _, Some((_, value))) => { with_hole(env, *value, variable, procs, layout_cache, assigned, hole) diff --git a/crates/compiler/parse/src/ast.rs b/crates/compiler/parse/src/ast.rs index 7ee687b19e..618226c42a 100644 --- a/crates/compiler/parse/src/ast.rs +++ b/crates/compiler/parse/src/ast.rs @@ -497,7 +497,10 @@ pub enum Expr<'a> { Backpassing(&'a [Loc>], &'a Loc>, &'a Loc>), Expect(&'a Loc>, &'a Loc>), - Dbg(&'a Loc>, &'a Loc>), + + Dbg, + DbgStmt(&'a Loc>, &'a Loc>), + // This form of debug is a desugared call to roc_dbg LowLevelDbg(&'a (&'a str, &'a str), &'a Loc>, &'a Loc>), @@ -663,9 +666,9 @@ pub fn is_expr_suffixed(expr: &Expr) -> bool { Expr::Tag(_) => false, Expr::OpaqueRef(_) => false, Expr::Backpassing(_, _, _) => false, // TODO: we might want to check this? - Expr::Expect(a, b) | Expr::Dbg(a, b) => { - is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value) - } + Expr::Expect(a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value), + Expr::Dbg => false, + Expr::DbgStmt(a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value), Expr::LowLevelDbg(_, a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value), Expr::UnaryOp(a, _) => is_expr_suffixed(&a.value), Expr::When(cond, branches) => { @@ -958,9 +961,17 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> { expr_stack.push(&a.value); expr_stack.push(&b.value); } - Expect(condition, cont) - | Dbg(condition, cont) - | LowLevelDbg(_, condition, cont) => { + Expect(condition, cont) => { + expr_stack.reserve(2); + expr_stack.push(&condition.value); + expr_stack.push(&cont.value); + } + DbgStmt(condition, cont) => { + expr_stack.reserve(2); + expr_stack.push(&condition.value); + expr_stack.push(&cont.value); + } + LowLevelDbg(_, condition, cont) => { expr_stack.reserve(2); expr_stack.push(&condition.value); expr_stack.push(&cont.value); @@ -1032,6 +1043,7 @@ impl<'a, 'b> RecursiveValueDefIter<'a, 'b> { | Var { .. } | Underscore(_) | Crash + | Dbg | Tag(_) | OpaqueRef(_) | MalformedIdent(_, _) @@ -2518,8 +2530,9 @@ impl<'a> Malformed for Expr<'a> { Closure(args, body) => args.iter().any(|arg| arg.is_malformed()) || body.is_malformed(), Defs(defs, body) => defs.is_malformed() || body.is_malformed(), Backpassing(args, call, body) => args.iter().any(|arg| arg.is_malformed()) || call.is_malformed() || body.is_malformed(), - Expect(condition, continuation) | - Dbg(condition, continuation) => condition.is_malformed() || continuation.is_malformed(), + Expect(condition, continuation) => condition.is_malformed() || continuation.is_malformed(), + Dbg => false, + DbgStmt(condition, continuation) => condition.is_malformed() || continuation.is_malformed(), LowLevelDbg(_, condition, continuation) => condition.is_malformed() || continuation.is_malformed(), Apply(func, args, _) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()), BinOps(firsts, last) => firsts.iter().any(|(expr, _)| expr.is_malformed()) || last.is_malformed(), diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index dda94cce4f..5c975b1194 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -194,6 +194,7 @@ fn loc_term_or_underscore_or_conditional<'a>( )), loc(specialize_err(EExpr::Closure, closure_help(options))), loc(crash_kw()), + loc(specialize_err(EExpr::Dbg, dbg_kw())), loc(underscore_expression()), loc(record_literal_help()), loc(specialize_err(EExpr::List, list_literal_help())), @@ -215,6 +216,7 @@ fn loc_term_or_underscore<'a>( positive_number_literal_help() )), loc(specialize_err(EExpr::Closure, closure_help(options))), + loc(specialize_err(EExpr::Dbg, dbg_kw())), loc(underscore_expression()), loc(record_literal_help()), loc(specialize_err(EExpr::List, list_literal_help())), @@ -232,6 +234,7 @@ fn loc_term<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc>, EEx positive_number_literal_help() )), loc(specialize_err(EExpr::Closure, closure_help(options))), + loc(specialize_err(EExpr::Dbg, dbg_kw())), loc(record_literal_help()), loc(specialize_err(EExpr::List, list_literal_help())), ident_seq(), @@ -541,7 +544,7 @@ fn stmt_start<'a>( )), loc(specialize_err( EExpr::Dbg, - dbg_help(options, preceding_comment) + dbg_stmt_help(options, preceding_comment) )), loc(specialize_err(EExpr::Import, map(import(), Stmt::ValueDef))), map( @@ -2170,7 +2173,8 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result( } } -fn dbg_help<'a>( +fn dbg_stmt_help<'a>( options: ExprParseOptions, preceding_comment: Region, ) -> impl Parser<'a, Stmt<'a>, EExpect<'a>> { @@ -2672,7 +2676,17 @@ fn dbg_help<'a>( Ok((MadeProgress, stmt, state)) }) - .trace("dbg_help") + .trace("dbg_stmt_help") +} + +fn dbg_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpect<'a>> { + (move |arena: &'a Bump, state: State<'a>, min_indent: u32| { + let (_, _, next_state) = + parser::keyword(keyword::DBG, EExpect::Dbg).parse(arena, state, min_indent)?; + + Ok((MadeProgress, Expr::Dbg, next_state)) + }) + .trace("dbg_kw") } fn import<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> { @@ -2986,14 +3000,17 @@ fn stmts_to_expr<'a>( arena.alloc(e).before(space) } } - Stmt::ValueDef(ValueDef::Dbg { .. }) => { - return Err(EExpr::Dbg( - EExpect::Continuation( - arena.alloc(EExpr::IndentEnd(loc_stmt.region.end())), - loc_stmt.region.end(), - ), - loc_stmt.region.start(), - )); + Stmt::ValueDef(ValueDef::Dbg { condition, .. }) => { + // If we parse a `dbg` as the last thing in a series of statements then it's + // actually an expression. + Expr::Apply( + arena.alloc(Loc { + value: Expr::Dbg, + region: loc_stmt.region, + }), + arena.alloc([condition]), + CalledVia::Space, + ) } Stmt::ValueDef(ValueDef::Expect { .. }) => { return Err(EExpr::Expect( @@ -3145,13 +3162,19 @@ fn stmts_to_defs<'a>( } = vd { if exprify_dbg { - if i + 1 >= stmts.len() { - return Err(EExpr::DbgContinue(sp_stmt.item.region.end())); - } - - let rest = stmts_to_expr(&stmts[i + 1..], arena)?; - - let e = Expr::Dbg(arena.alloc(condition), arena.alloc(rest)); + let e = if i + 1 < stmts.len() { + let rest = stmts_to_expr(&stmts[i + 1..], arena)?; + Expr::DbgStmt(arena.alloc(condition), arena.alloc(rest)) + } else { + Expr::Apply( + arena.alloc(Loc { + value: Expr::Dbg, + region: sp_stmt.item.region, + }), + arena.alloc([condition]), + CalledVia::Space, + ) + }; let e = if sp_stmt.before.is_empty() { e diff --git a/crates/compiler/parse/src/header.rs b/crates/compiler/parse/src/header.rs index ae1d916631..edd0c1e0c3 100644 --- a/crates/compiler/parse/src/header.rs +++ b/crates/compiler/parse/src/header.rs @@ -6,20 +6,20 @@ use crate::ast::{ }; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::expr::merge_spaces; -use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent}; +use crate::ident::{self, lowercase_ident, unqualified_ident, UppercaseIdent}; use crate::parser::Progress::{self, *}; use crate::parser::{ and, backtrackable, byte, collection_trailing_sep_e, increment_min_indent, loc, map, map_with_arena, optional, reset_min_indent, skip_first, skip_second, specialize_err, succeed, - then, two_bytes, zero_or_more, EExposes, EGenerates, EGeneratesWith, EHeader, EImports, - EPackageEntry, EPackageName, EPackages, EParams, EProvides, ERequires, ETypedIdent, Parser, - SourceError, SpaceProblem, SyntaxError, + then, two_bytes, zero_or_more, EExposes, EHeader, EImports, EPackageEntry, EPackageName, + EPackages, EParams, EProvides, ERequires, ETypedIdent, Parser, SourceError, SpaceProblem, + SyntaxError, }; use crate::pattern::record_pattern_fields; use crate::state::State; use crate::string_literal::{self, parse_str_literal}; use crate::type_annotation; -use roc_module::symbol::{ModuleId, Symbol}; +use roc_module::symbol::ModuleId; use roc_region::all::{Loc, Position, Region}; fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> { @@ -188,8 +188,6 @@ fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> { name: loc(module_name_help(EHeader::ModuleName)), exposes: specialize_err(EHeader::Exposes, exposes_values_kw()), imports: specialize_err(EHeader::Imports, imports()), - generates: specialize_err(EHeader::Generates, generates()), - generates_with: specialize_err(EHeader::GeneratesWith, generates_with()), }) .trace("hosted_header") } @@ -761,43 +759,6 @@ fn packages_collection<'a>( ) } -#[inline(always)] -fn generates<'a>( -) -> impl Parser<'a, KeywordItem<'a, GeneratesKeyword, UppercaseIdent<'a>>, EGenerates> { - record!(KeywordItem { - keyword: spaces_around_keyword( - GeneratesKeyword, - EGenerates::Generates, - EGenerates::IndentGenerates, - EGenerates::IndentTypeStart - ), - item: specialize_err(|(), pos| EGenerates::Identifier(pos), uppercase()) - }) -} - -#[inline(always)] -fn generates_with<'a>() -> impl Parser< - 'a, - KeywordItem<'a, WithKeyword, Collection<'a, Loc>>>>, - EGeneratesWith, -> { - record!(KeywordItem { - keyword: spaces_around_keyword( - WithKeyword, - EGeneratesWith::With, - EGeneratesWith::IndentWith, - EGeneratesWith::IndentListStart - ), - item: collection_trailing_sep_e( - byte(b'[', EGeneratesWith::ListStart), - exposes_entry(EGeneratesWith::Identifier), - byte(b',', EGeneratesWith::ListEnd), - byte(b']', EGeneratesWith::ListEnd), - Spaced::SpaceBefore - ) - }) -} - #[inline(always)] fn imports<'a>() -> impl Parser< 'a, @@ -977,14 +938,11 @@ pub enum HeaderType<'a> { Hosted { name: ModuleName<'a>, exposes: &'a [Loc>], - generates: UppercaseIdent<'a>, - generates_with: &'a [Loc>], }, /// Only created during canonicalization, never actually parsed from source Builtin { name: ModuleName<'a>, exposes: &'a [Loc>], - generates_with: &'a [Symbol], opt_params: Option>, }, Package { @@ -1045,7 +1003,6 @@ impl<'a> HeaderType<'a> { opt_params, name: _, exposes: _, - generates_with: _, } => opt_params, Self::App { provides: _, @@ -1059,8 +1016,6 @@ impl<'a> HeaderType<'a> { | Self::Hosted { name: _, exposes: _, - generates: _, - generates_with: _, } | Self::Platform { opt_app_module_id: _, @@ -1083,7 +1038,6 @@ impl<'a> HeaderType<'a> { } if module_id.is_builtin() => HeaderType::Builtin { name, exposes, - generates_with: &[], opt_params, }, _ => self, @@ -1202,8 +1156,6 @@ macro_rules! keywords { keywords! { ExposesKeyword => "exposes", - WithKeyword => "with", - GeneratesKeyword => "generates", PackageKeyword => "package", PackagesKeyword => "packages", RequiresKeyword => "requires", @@ -1247,10 +1199,6 @@ pub struct HostedHeader<'a> { pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc>>>>, pub imports: KeywordItem<'a, ImportsKeyword, Collection<'a, Loc>>>>, - - pub generates: KeywordItem<'a, GeneratesKeyword, UppercaseIdent<'a>>, - pub generates_with: - KeywordItem<'a, WithKeyword, Collection<'a, Loc>>>>, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -1329,7 +1277,7 @@ pub enum ImportsEntry<'a> { /// e.g. /// -/// printLine : Str -> Effect {} +/// printLine : Str -> Result {} * #[derive(Copy, Clone, Debug, PartialEq)] pub struct TypedIdent<'a> { pub ident: Loc<&'a str>, diff --git a/crates/compiler/parse/src/highlight.rs b/crates/compiler/parse/src/highlight.rs index b5b5c5fff4..e54809b2e8 100644 --- a/crates/compiler/parse/src/highlight.rs +++ b/crates/compiler/parse/src/highlight.rs @@ -365,7 +365,7 @@ fn fast_forward_to( tokens.push(Loc::at(Region::between(start, state.pos()), Token::Error)); } -pub const HEADER_KEYWORDS: [&str; 14] = [ +pub const HEADER_KEYWORDS: [&str; 12] = [ "interface", "app", "package", @@ -373,8 +373,6 @@ pub const HEADER_KEYWORDS: [&str; 14] = [ "hosted", "exposes", "imports", - "with", - "generates", "package", "packages", "requires", diff --git a/crates/compiler/parse/src/module.rs b/crates/compiler/parse/src/module.rs new file mode 100644 index 0000000000..899ec154e2 --- /dev/null +++ b/crates/compiler/parse/src/module.rs @@ -0,0 +1,905 @@ +use crate::ast::{Collection, CommentOrNewline, Defs, Header, Module, Spaced, Spaces}; +use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; +use crate::expr::merge_spaces; +use crate::header::{ + package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, HostedHeader, + ImportsCollection, ImportsEntry, ImportsKeyword, ImportsKeywordItem, Keyword, KeywordItem, + ModuleHeader, ModuleName, ModuleParams, PackageEntry, PackageHeader, PackagesKeyword, + PlatformHeader, PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, + TypedIdent, +}; +use crate::ident::{self, lowercase_ident, unqualified_ident, UppercaseIdent}; +use crate::parser::Progress::{self, *}; +use crate::parser::{ + and, backtrackable, byte, collection_trailing_sep_e, increment_min_indent, loc, map, + map_with_arena, optional, reset_min_indent, skip_first, skip_second, specialize_err, succeed, + two_bytes, zero_or_more, EExposes, EHeader, EImports, EPackages, EParams, EProvides, ERequires, + ETypedIdent, Parser, SourceError, SpaceProblem, SyntaxError, +}; +use crate::pattern::record_pattern_fields; +use crate::state::State; +use crate::string_literal::{self, parse_str_literal}; +use crate::type_annotation; +use roc_region::all::{Loc, Position, Region}; + +fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> { + |_arena, state: State<'a>, _min_indent: u32| { + if state.has_reached_end() { + Ok((NoProgress, (), state)) + } else { + Err((NoProgress, SyntaxError::NotEndOfFile(state.pos()))) + } + } +} + +pub fn parse_module_defs<'a>( + arena: &'a bumpalo::Bump, + state: State<'a>, + defs: Defs<'a>, +) -> Result, SyntaxError<'a>> { + let min_indent = 0; + match crate::expr::parse_top_level_defs(arena, state.clone(), defs) { + Ok((_, defs, state)) => match end_of_file().parse(arena, state, min_indent) { + Ok(_) => Ok(defs), + Err((_, fail)) => Err(fail), + }, + Err((_, fail)) => Err(SyntaxError::Expr(fail, state.pos())), + } +} + +pub fn parse_header<'a>( + arena: &'a bumpalo::Bump, + state: State<'a>, +) -> Result<(Module<'a>, State<'a>), SourceError<'a, EHeader<'a>>> { + let min_indent = 0; + match header().parse(arena, state.clone(), min_indent) { + Ok((_, module, state)) => Ok((module, state)), + Err((_, fail)) => Err(SourceError::new(fail, &state)), + } +} + +pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> { + use crate::parser::keyword; + + record!(Module { + comments: space0_e(EHeader::IndentStart), + header: one_of![ + map( + skip_first( + keyword("module", EHeader::Start), + increment_min_indent(module_header()) + ), + Header::Module + ), + map( + skip_first( + keyword("interface", EHeader::Start), + increment_min_indent(interface_header()) + ), + Header::Module + ), + map( + skip_first( + keyword("app", EHeader::Start), + increment_min_indent(one_of![app_header(), old_app_header()]) + ), + Header::App + ), + map( + skip_first( + keyword("package", EHeader::Start), + increment_min_indent(one_of![package_header(), old_package_header()]) + ), + Header::Package + ), + map( + skip_first( + keyword("platform", EHeader::Start), + increment_min_indent(platform_header()) + ), + Header::Platform + ), + map( + skip_first( + keyword("hosted", EHeader::Start), + increment_min_indent(hosted_header()) + ), + Header::Hosted + ), + ] + }) +} + +#[inline(always)] +fn module_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> { + record!(ModuleHeader { + after_keyword: space0_e(EHeader::IndentStart), + params: optional(specialize_err(EHeader::Params, module_params())), + exposes: specialize_err(EHeader::Exposes, exposes_list()), + interface_imports: succeed(None) + }) + .trace("module_header") +} + +fn module_params<'a>() -> impl Parser<'a, ModuleParams<'a>, EParams<'a>> { + record!(ModuleParams { + params: specialize_err(EParams::Pattern, record_pattern_fields()), + before_arrow: skip_second( + space0_e(EParams::BeforeArrow), + loc(two_bytes(b'-', b'>', EParams::Arrow)) + ), + after_arrow: space0_e(EParams::AfterArrow), + }) +} + +// TODO does this need to be a macro? +macro_rules! merge_n_spaces { + ($arena:expr, $($slice:expr),*) => { + { + let mut merged = bumpalo::collections::Vec::with_capacity_in(0 $(+ $slice.len())*, $arena); + $(merged.extend_from_slice($slice);)* + merged.into_bump_slice() + } + }; +} + +/// Parse old interface headers so we can format them into module headers +#[inline(always)] +fn interface_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> { + let after_keyword = map_with_arena( + and( + skip_second( + space0_e(EHeader::IndentStart), + loc(module_name_help(EHeader::ModuleName)), + ), + specialize_err(EHeader::Exposes, exposes_kw()), + ), + |arena: &'a bumpalo::Bump, + (before_name, kw): (&'a [CommentOrNewline<'a>], Spaces<'a, ExposesKeyword>)| { + merge_n_spaces!(arena, before_name, kw.before, kw.after) + }, + ); + + record!(ModuleHeader { + after_keyword: after_keyword, + params: succeed(None), + exposes: specialize_err(EHeader::Exposes, exposes_list()).trace("exposes_list"), + interface_imports: map( + specialize_err(EHeader::Imports, imports()), + imports_none_if_empty + ) + .trace("imports"), + }) + .trace("interface_header") +} + +fn imports_none_if_empty(value: ImportsKeywordItem<'_>) -> Option> { + if value.item.is_empty() { + None + } else { + Some(value) + } +} + +#[inline(always)] +fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> { + record!(HostedHeader { + before_name: space0_e(EHeader::IndentStart), + name: loc(module_name_help(EHeader::ModuleName)), + exposes: specialize_err(EHeader::Exposes, exposes_values_kw()), + imports: specialize_err(EHeader::Imports, imports()), + }) + .trace("hosted_header") +} + +fn chomp_module_name(buffer: &[u8]) -> Result<&str, Progress> { + use encode_unicode::CharExt; + + let mut chomped = 0; + + if let Ok((first_letter, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + if first_letter.is_uppercase() { + chomped += width; + } else { + return Err(Progress::NoProgress); + } + } + + while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + // After the first character, only these are allowed: + // + // * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers + // * ASCII digits - e.g. `1` but not `ΒΎ`, both of which pass .is_numeric() + // * A '.' separating module parts + if ch.is_alphabetic() || ch.is_ascii_digit() { + chomped += width; + } else if ch == '.' { + chomped += width; + + if let Ok((first_letter, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { + if first_letter.is_uppercase() { + chomped += width; + } else if first_letter == '{' { + // the .{ starting a `Foo.{ bar, baz }` importing clauses + chomped -= width; + break; + } else { + return Err(Progress::MadeProgress); + } + } + } else { + // we're done + break; + } + } + + let name = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; + + Ok(name) +} + +#[inline(always)] +fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> { + |_, mut state: State<'a>, _min_indent: u32| match chomp_module_name(state.bytes()) { + Ok(name) => { + let width = name.len(); + state = state.advance(width); + + Ok((MadeProgress, ModuleName::new(name), state)) + } + Err(progress) => Err((progress, ())), + } +} + +#[inline(always)] +fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> { + record!(AppHeader { + before_provides: space0_e(EHeader::IndentStart), + provides: specialize_err(EHeader::Exposes, exposes_list()), + before_packages: space0_e(EHeader::IndentStart), + packages: specialize_err(EHeader::Packages, loc(packages_collection())), + old_imports: succeed(None), + old_provides_to_new_package: succeed(None), + }) + .trace("app_header") +} + +struct OldAppHeader<'a> { + pub before_name: &'a [CommentOrNewline<'a>], + pub packages: Option>>, + pub imports: Option>>, + pub provides: ProvidesTo<'a>, +} + +type OldAppPackages<'a> = + KeywordItem<'a, PackagesKeyword, Collection<'a, Loc>>>>; + +#[inline(always)] +fn old_app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> { + let old = record!(OldAppHeader { + before_name: skip_second( + space0_e(EHeader::IndentStart), + loc(crate::parser::specialize_err( + EHeader::AppName, + string_literal::parse_str_literal() + )) + ), + packages: optional(specialize_err(EHeader::Packages, loc(packages()))), + imports: optional(specialize_err(EHeader::Imports, imports())), + provides: specialize_err(EHeader::Provides, provides_to()), + }); + + map_with_arena(old, |arena: &'a bumpalo::Bump, old: OldAppHeader<'a>| { + let mut before_packages: &'a [CommentOrNewline] = &[]; + + let packages = match old.packages { + Some(packages) => { + before_packages = merge_spaces( + arena, + packages.value.keyword.before, + packages.value.keyword.after, + ); + + if let To::ExistingPackage(platform_shorthand) = old.provides.to.value { + packages.map(|coll| { + coll.item.map_items(arena, |loc_spaced_pkg| { + if loc_spaced_pkg.value.item().shorthand == platform_shorthand { + loc_spaced_pkg.map(|spaced_pkg| { + spaced_pkg.map(arena, |pkg| { + let mut new_pkg = *pkg; + new_pkg.platform_marker = Some(merge_spaces( + arena, + old.provides.to_keyword.before, + old.provides.to_keyword.after, + )); + new_pkg + }) + }) + } else { + *loc_spaced_pkg + } + }) + }) + } else { + packages.map(|kw| kw.item) + } + } + None => Loc { + region: Region::zero(), + value: Collection::empty(), + }, + }; + + let provides = match old.provides.types { + Some(types) => { + let mut combined_items = bumpalo::collections::Vec::with_capacity_in( + old.provides.entries.items.len() + types.items.len(), + arena, + ); + + combined_items.extend_from_slice(old.provides.entries.items); + + for loc_spaced_type_ident in types.items { + combined_items.push(loc_spaced_type_ident.map(|spaced_type_ident| { + spaced_type_ident.map(arena, |type_ident| { + ExposedName::new(From::from(*type_ident)) + }) + })); + } + + let value_comments = old.provides.entries.final_comments(); + let type_comments = types.final_comments(); + + let mut combined_comments = bumpalo::collections::Vec::with_capacity_in( + value_comments.len() + type_comments.len(), + arena, + ); + combined_comments.extend_from_slice(value_comments); + combined_comments.extend_from_slice(type_comments); + + Collection::with_items_and_comments( + arena, + combined_items.into_bump_slice(), + combined_comments.into_bump_slice(), + ) + } + None => old.provides.entries, + }; + + AppHeader { + before_provides: merge_spaces( + arena, + old.before_name, + old.provides.provides_keyword.before, + ), + provides, + before_packages: merge_spaces( + arena, + before_packages, + old.provides.provides_keyword.after, + ), + packages, + old_imports: old.imports.and_then(imports_none_if_empty), + old_provides_to_new_package: match old.provides.to.value { + To::NewPackage(new_pkg) => Some(new_pkg), + To::ExistingPackage(_) => None, + }, + } + }) +} + +#[inline(always)] +fn package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> { + record!(PackageHeader { + before_exposes: space0_e(EHeader::IndentStart), + exposes: specialize_err(EHeader::Exposes, exposes_module_collection()), + before_packages: space0_e(EHeader::IndentStart), + packages: specialize_err(EHeader::Packages, loc(packages_collection())), + }) + .trace("package_header") +} + +#[derive(Debug, Clone, PartialEq)] +struct OldPackageHeader<'a> { + before_name: &'a [CommentOrNewline<'a>], + exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc>>>>, + packages: + Loc>>>>>, +} + +#[inline(always)] +fn old_package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> { + map_with_arena( + record!(OldPackageHeader { + before_name: skip_second( + space0_e(EHeader::IndentStart), + specialize_err(EHeader::PackageName, package_name()) + ), + exposes: specialize_err(EHeader::Exposes, exposes_modules()), + packages: specialize_err(EHeader::Packages, loc(packages())), + }), + |arena: &'a bumpalo::Bump, old: OldPackageHeader<'a>| { + let before_exposes = merge_n_spaces!( + arena, + old.before_name, + old.exposes.keyword.before, + old.exposes.keyword.after + ); + let before_packages = merge_spaces( + arena, + old.packages.value.keyword.before, + old.packages.value.keyword.after, + ); + + PackageHeader { + before_exposes, + exposes: old.exposes.item, + before_packages, + packages: old.packages.map(|kw| kw.item), + } + }, + ) + .trace("old_package_header") +} + +#[inline(always)] +fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> { + record!(PlatformHeader { + before_name: space0_e(EHeader::IndentStart), + name: loc(specialize_err(EHeader::PlatformName, package_name())), + requires: specialize_err(EHeader::Requires, requires()), + exposes: specialize_err(EHeader::Exposes, exposes_modules()), + packages: specialize_err(EHeader::Packages, packages()), + imports: specialize_err(EHeader::Imports, imports()), + provides: specialize_err(EHeader::Provides, provides_exposed()), + }) + .trace("platform_header") +} + +fn provides_to_package<'a>() -> impl Parser<'a, To<'a>, EProvides<'a>> { + one_of![ + specialize_err( + |_, pos| EProvides::Identifier(pos), + map(lowercase_ident(), To::ExistingPackage) + ), + specialize_err(EProvides::Package, map(package_name(), To::NewPackage)) + ] +} + +#[inline(always)] +fn provides_to<'a>() -> impl Parser<'a, ProvidesTo<'a>, EProvides<'a>> { + record!(ProvidesTo { + provides_keyword: spaces_around_keyword( + ProvidesKeyword, + EProvides::Provides, + EProvides::IndentProvides, + EProvides::IndentListStart + ), + entries: collection_trailing_sep_e( + byte(b'[', EProvides::ListStart), + exposes_entry(EProvides::Identifier), + byte(b',', EProvides::ListEnd), + byte(b']', EProvides::ListEnd), + Spaced::SpaceBefore + ), + types: optional(backtrackable(provides_types())), + to_keyword: spaces_around_keyword( + ToKeyword, + EProvides::To, + EProvides::IndentTo, + EProvides::IndentListStart + ), + to: loc(provides_to_package()), + }) + .trace("provides_to") +} + +fn provides_exposed<'a>() -> impl Parser< + 'a, + KeywordItem<'a, ProvidesKeyword, Collection<'a, Loc>>>>, + EProvides<'a>, +> { + record!(KeywordItem { + keyword: spaces_around_keyword( + ProvidesKeyword, + EProvides::Provides, + EProvides::IndentProvides, + EProvides::IndentListStart + ), + item: collection_trailing_sep_e( + byte(b'[', EProvides::ListStart), + exposes_entry(EProvides::Identifier), + byte(b',', EProvides::ListEnd), + byte(b']', EProvides::ListEnd), + Spaced::SpaceBefore + ), + }) +} + +#[inline(always)] +fn provides_types<'a>( +) -> impl Parser<'a, Collection<'a, Loc>>>, EProvides<'a>> { + skip_first( + // We only support spaces here, not newlines, because this is not intended + // to be the design forever. Someday it will hopefully work like Elm, + // where platform authors can provide functions like Browser.sandbox which + // present an API based on ordinary-looking type variables. + zero_or_more(byte( + b' ', + // HACK: If this errors, EProvides::Provides is not an accurate reflection + // of what went wrong. However, this is both skipped and zero_or_more, + // so this error should never be visible to anyone in practice! + EProvides::Provides, + )), + collection_trailing_sep_e( + byte(b'{', EProvides::ListStart), + provides_type_entry(EProvides::Identifier), + byte(b',', EProvides::ListEnd), + byte(b'}', EProvides::ListEnd), + Spaced::SpaceBefore, + ), + ) +} + +fn provides_type_entry<'a, F, E>( + to_expectation: F, +) -> impl Parser<'a, Loc>>, E> +where + F: Fn(Position) -> E, + F: Copy, + E: 'a, +{ + loc(map( + specialize_err(move |_, pos| to_expectation(pos), ident::uppercase()), + Spaced::Item, + )) +} + +fn exposes_entry<'a, F, E>( + to_expectation: F, +) -> impl Parser<'a, Loc>>, E> +where + F: Fn(Position) -> E, + F: Copy, + E: 'a, +{ + loc(map( + specialize_err(move |_, pos| to_expectation(pos), unqualified_ident()), + |n| Spaced::Item(ExposedName::new(n)), + )) +} + +#[inline(always)] +fn requires<'a>( +) -> impl Parser<'a, KeywordItem<'a, RequiresKeyword, PlatformRequires<'a>>, ERequires<'a>> { + record!(KeywordItem { + keyword: spaces_around_keyword( + RequiresKeyword, + ERequires::Requires, + ERequires::IndentRequires, + ERequires::IndentListStart + ), + item: platform_requires(), + }) +} + +#[inline(always)] +fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a>> { + record!(PlatformRequires { + rigids: skip_second(requires_rigids(), space0_e(ERequires::ListStart)), + signature: requires_typed_ident() + }) +} + +#[inline(always)] +fn requires_rigids<'a>( +) -> impl Parser<'a, Collection<'a, Loc>>>, ERequires<'a>> { + collection_trailing_sep_e( + byte(b'{', ERequires::ListStart), + specialize_err( + |_, pos| ERequires::Rigid(pos), + loc(map(ident::uppercase(), Spaced::Item)), + ), + byte(b',', ERequires::ListEnd), + byte(b'}', ERequires::ListEnd), + Spaced::SpaceBefore, + ) +} + +#[inline(always)] +fn requires_typed_ident<'a>() -> impl Parser<'a, Loc>>, ERequires<'a>> { + skip_first( + byte(b'{', ERequires::ListStart), + skip_second( + reset_min_indent(space0_around_ee( + specialize_err(ERequires::TypedIdent, loc(typed_ident())), + ERequires::ListStart, + ERequires::ListEnd, + )), + byte(b'}', ERequires::ListStart), + ), + ) +} + +#[inline(always)] +fn exposes_values_kw<'a>() -> impl Parser< + 'a, + KeywordItem<'a, ExposesKeyword, Collection<'a, Loc>>>>, + EExposes, +> { + record!(KeywordItem { + keyword: exposes_kw(), + item: exposes_list() + }) +} + +#[inline(always)] +fn exposes_kw<'a>() -> impl Parser<'a, Spaces<'a, ExposesKeyword>, EExposes> { + spaces_around_keyword( + ExposesKeyword, + EExposes::Exposes, + EExposes::IndentExposes, + EExposes::IndentListStart, + ) +} + +#[inline(always)] +fn exposes_list<'a>() -> impl Parser<'a, Collection<'a, Loc>>>, EExposes> +{ + collection_trailing_sep_e( + byte(b'[', EExposes::ListStart), + exposes_entry(EExposes::Identifier), + byte(b',', EExposes::ListEnd), + byte(b']', EExposes::ListEnd), + Spaced::SpaceBefore, + ) +} + +pub fn spaces_around_keyword<'a, K: Keyword, E>( + keyword_item: K, + expectation: fn(Position) -> E, + indent_problem1: fn(Position) -> E, + indent_problem2: fn(Position) -> E, +) -> impl Parser<'a, Spaces<'a, K>, E> +where + E: 'a + SpaceProblem, +{ + map( + and( + skip_second( + // parse any leading space before the keyword + backtrackable(space0_e(indent_problem1)), + // parse the keyword + crate::parser::keyword(K::KEYWORD, expectation), + ), + // parse the trailing space + space0_e(indent_problem2), + ), + move |(before, after)| Spaces { + before, + item: keyword_item, + after, + }, + ) +} + +#[inline(always)] +fn exposes_modules<'a>() -> impl Parser< + 'a, + KeywordItem<'a, ExposesKeyword, Collection<'a, Loc>>>>, + EExposes, +> { + record!(KeywordItem { + keyword: spaces_around_keyword( + ExposesKeyword, + EExposes::Exposes, + EExposes::IndentExposes, + EExposes::IndentListStart + ), + item: exposes_module_collection(), + }) +} + +fn exposes_module_collection<'a>( +) -> impl Parser<'a, Collection<'a, Loc>>>, EExposes> { + collection_trailing_sep_e( + byte(b'[', EExposes::ListStart), + exposes_module(EExposes::Identifier), + byte(b',', EExposes::ListEnd), + byte(b']', EExposes::ListEnd), + Spaced::SpaceBefore, + ) +} + +fn exposes_module<'a, F, E>( + to_expectation: F, +) -> impl Parser<'a, Loc>>, E> +where + F: Fn(Position) -> E, + F: Copy, + E: 'a, +{ + loc(map( + specialize_err(move |_, pos| to_expectation(pos), module_name()), + Spaced::Item, + )) +} + +#[inline(always)] +fn packages<'a>() -> impl Parser< + 'a, + KeywordItem<'a, PackagesKeyword, Collection<'a, Loc>>>>, + EPackages<'a>, +> { + record!(KeywordItem { + keyword: packages_kw(), + item: packages_collection() + }) +} + +#[inline(always)] +fn packages_kw<'a>() -> impl Parser<'a, Spaces<'a, PackagesKeyword>, EPackages<'a>> { + spaces_around_keyword( + PackagesKeyword, + EPackages::Packages, + EPackages::IndentPackages, + EPackages::IndentListStart, + ) +} + +#[inline(always)] +fn packages_collection<'a>( +) -> impl Parser<'a, Collection<'a, Loc>>>, EPackages<'a>> { + collection_trailing_sep_e( + byte(b'{', EPackages::ListStart), + specialize_err(EPackages::PackageEntry, loc(package_entry())), + byte(b',', EPackages::ListEnd), + byte(b'}', EPackages::ListEnd), + Spaced::SpaceBefore, + ) +} + +fn imports<'a>() -> impl Parser< + 'a, + KeywordItem<'a, ImportsKeyword, Collection<'a, Loc>>>>, + EImports, +> { + record!(KeywordItem { + keyword: spaces_around_keyword( + ImportsKeyword, + EImports::Imports, + EImports::IndentImports, + EImports::IndentListStart + ), + item: collection_trailing_sep_e( + byte(b'[', EImports::ListStart), + loc(imports_entry()), + byte(b',', EImports::ListEnd), + byte(b']', EImports::ListEnd), + Spaced::SpaceBefore + ) + }) + .trace("imports") +} + +#[inline(always)] +pub fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> { + // e.g. + // + // printLine : Str -> Task {} * + map( + and( + and( + loc(specialize_err( + |_, pos| ETypedIdent::Identifier(pos), + lowercase_ident(), + )), + space0_e(ETypedIdent::IndentHasType), + ), + skip_first( + byte(b':', ETypedIdent::HasType), + space0_before_e( + specialize_err( + ETypedIdent::Type, + reset_min_indent(type_annotation::located(true)), + ), + ETypedIdent::IndentType, + ), + ), + ), + |((ident, spaces_before_colon), ann)| { + Spaced::Item(TypedIdent { + ident, + spaces_before_colon, + ann, + }) + }, + ) +} + +fn shortname<'a>() -> impl Parser<'a, &'a str, EImports> { + specialize_err(|_, pos| EImports::Shorthand(pos), lowercase_ident()) +} + +pub fn module_name_help<'a, F, E>(to_expectation: F) -> impl Parser<'a, ModuleName<'a>, E> +where + F: Fn(Position) -> E, + E: 'a, + F: 'a, +{ + specialize_err(move |_, pos| to_expectation(pos), module_name()) +} + +#[inline(always)] +fn imports_entry<'a>() -> impl Parser<'a, Spaced<'a, ImportsEntry<'a>>, EImports> { + type Temp<'a> = ( + (Option<&'a str>, ModuleName<'a>), + Option>>>>, + ); + + let spaced_import = |((opt_shortname, module_name), opt_values): Temp<'a>| { + let exposed_values = opt_values.unwrap_or_else(Collection::empty); + + let entry = match opt_shortname { + Some(shortname) => ImportsEntry::Package(shortname, module_name, exposed_values), + + None => ImportsEntry::Module(module_name, exposed_values), + }; + + Spaced::Item(entry) + }; + + one_of!( + map( + and( + and( + // e.g. `pf.` + optional(backtrackable(skip_second( + shortname(), + byte(b'.', EImports::ShorthandDot) + ))), + // e.g. `Task` + module_name_help(EImports::ModuleName) + ), + // e.g. `.{ Task, after}` + optional(skip_first( + byte(b'.', EImports::ExposingDot), + collection_trailing_sep_e( + byte(b'{', EImports::SetStart), + exposes_entry(EImports::Identifier), + byte(b',', EImports::SetEnd), + byte(b'}', EImports::SetEnd), + Spaced::SpaceBefore + ) + )) + ), + spaced_import + ) + .trace("normal_import"), + map( + and( + and( + // e.g. "filename" + // TODO: str literal allows for multiline strings. We probably don't want that for file names. + specialize_err(|_, pos| EImports::StrLiteral(pos), parse_str_literal()), + // e.g. as + and( + and( + space0_e(EImports::AsKeyword), + two_bytes(b'a', b's', EImports::AsKeyword) + ), + space0_e(EImports::AsKeyword) + ) + ), + // e.g. file : Str + specialize_err(|_, pos| EImports::TypedIdent(pos), typed_ident()) + ), + |((file_name, _), typed_ident)| { + // TODO: look at blacking block strings during parsing. + Spaced::Item(ImportsEntry::IngestedFile(file_name, typed_ident)) + } + ) + .trace("ingest_file_import") + ) + .trace("imports_entry") +} diff --git a/crates/compiler/parse/src/normalize.rs b/crates/compiler/parse/src/normalize.rs index 1edf0c19a9..43d995d4be 100644 --- a/crates/compiler/parse/src/normalize.rs +++ b/crates/compiler/parse/src/normalize.rs @@ -13,19 +13,18 @@ use crate::{ TypeDef, TypeHeader, ValueDef, WhenBranch, }, header::{ - AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword, HostedHeader, ImportsEntry, - ImportsKeyword, KeywordItem, ModuleHeader, ModuleName, ModuleParams, PackageEntry, - PackageHeader, PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, - PlatformKeyword, PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, - ToKeyword, TypedIdent, WithKeyword, + AppHeader, ExposedName, ExposesKeyword, HostedHeader, ImportsEntry, ImportsKeyword, + KeywordItem, ModuleHeader, ModuleName, ModuleParams, PackageEntry, PackageHeader, + PackageKeyword, PackageName, PackagesKeyword, PlatformHeader, PlatformKeyword, + PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, }, ident::{BadIdent, UppercaseIdent}, parser::{ - EAbility, EClosure, EExpect, EExposes, EExpr, EGenerates, EGeneratesWith, EHeader, EIf, - EImport, EImportParams, EImports, EInParens, EList, EPackageEntry, EPackageName, EPackages, - EParams, EPattern, EProvides, ERecord, ERequires, EString, EType, ETypeAbilityImpl, - ETypeApply, ETypeInParens, ETypeInlineAlias, ETypeRecord, ETypeTagUnion, ETypedIdent, - EWhen, PInParens, PList, PRecord, SyntaxError, + EAbility, EClosure, EExpect, EExposes, EExpr, EHeader, EIf, EImport, EImportParams, + EImports, EInParens, EList, EPackageEntry, EPackageName, EPackages, EParams, EPattern, + EProvides, ERecord, ERequires, EString, EType, ETypeAbilityImpl, ETypeApply, ETypeInParens, + ETypeInlineAlias, ETypeRecord, ETypeTagUnion, ETypedIdent, EWhen, PInParens, PList, + PRecord, SyntaxError, }, }; @@ -59,8 +58,6 @@ macro_rules! keywords { keywords! { ExposesKeyword, ImportsKeyword, - WithKeyword, - GeneratesKeyword, PackageKeyword, PackagesKeyword, RequiresKeyword, @@ -179,8 +176,6 @@ impl<'a> Normalize<'a> for Header<'a> { name: header.name.normalize(arena), exposes: header.exposes.normalize(arena), imports: header.imports.normalize(arena), - generates: header.generates.normalize(arena), - generates_with: header.generates_with.normalize(arena), }), } } @@ -784,7 +779,8 @@ impl<'a> Normalize<'a> for Expr<'a> { arena.alloc(a.normalize(arena)), arena.alloc(b.normalize(arena)), ), - Expr::Dbg(a, b) => Expr::Dbg( + Expr::Dbg => Expr::Dbg, + Expr::DbgStmt(a, b) => Expr::DbgStmt( arena.alloc(a.normalize(arena)), arena.alloc(b.normalize(arena)), ), @@ -1587,38 +1583,6 @@ impl<'a> Normalize<'a> for EAbility<'a> { } } -impl<'a> Normalize<'a> for EGeneratesWith { - fn normalize(&self, _arena: &'a Bump) -> Self { - match self { - EGeneratesWith::Open(_) => EGeneratesWith::Open(Position::zero()), - EGeneratesWith::With(_) => EGeneratesWith::With(Position::zero()), - EGeneratesWith::IndentWith(_) => EGeneratesWith::IndentWith(Position::zero()), - EGeneratesWith::IndentListStart(_) => EGeneratesWith::IndentListStart(Position::zero()), - EGeneratesWith::IndentListEnd(_) => EGeneratesWith::IndentListEnd(Position::zero()), - EGeneratesWith::ListStart(_) => EGeneratesWith::ListStart(Position::zero()), - EGeneratesWith::ListEnd(_) => EGeneratesWith::ListEnd(Position::zero()), - EGeneratesWith::Identifier(_) => EGeneratesWith::Identifier(Position::zero()), - EGeneratesWith::Space(inner_err, _) => { - EGeneratesWith::Space(*inner_err, Position::zero()) - } - } - } -} - -impl<'a> Normalize<'a> for EGenerates { - fn normalize(&self, _arena: &'a Bump) -> Self { - match self { - EGenerates::Open(_) => EGenerates::Open(Position::zero()), - EGenerates::Generates(_) => EGenerates::Generates(Position::zero()), - EGenerates::IndentGenerates(_) => EGenerates::IndentGenerates(Position::zero()), - EGenerates::Identifier(_) => EGenerates::Identifier(Position::zero()), - EGenerates::Space(inner_err, _) => EGenerates::Space(*inner_err, Position::zero()), - EGenerates::IndentTypeStart(_) => EGenerates::IndentTypeStart(Position::zero()), - EGenerates::IndentTypeEnd(_) => EGenerates::IndentTypeEnd(Position::zero()), - } - } -} - impl<'a> Normalize<'a> for EPackages<'a> { fn normalize(&self, arena: &'a Bump) -> Self { match self { @@ -1658,12 +1622,6 @@ impl<'a> Normalize<'a> for EHeader<'a> { EHeader::Packages(inner_err, _) => { EHeader::Packages(inner_err.normalize(arena), Position::zero()) } - EHeader::Generates(inner_err, _) => { - EHeader::Generates(inner_err.normalize(arena), Position::zero()) - } - EHeader::GeneratesWith(inner_err, _) => { - EHeader::GeneratesWith(inner_err.normalize(arena), Position::zero()) - } EHeader::Space(inner_err, _) => EHeader::Space(*inner_err, Position::zero()), EHeader::Start(_) => EHeader::Start(Position::zero()), EHeader::ModuleName(_) => EHeader::ModuleName(Position::zero()), diff --git a/crates/compiler/parse/src/parser.rs b/crates/compiler/parse/src/parser.rs index 75ec4066bb..a5e0900a66 100644 --- a/crates/compiler/parse/src/parser.rs +++ b/crates/compiler/parse/src/parser.rs @@ -83,8 +83,6 @@ impl_space_problem! { EExpect<'a>, EExposes, EExpr<'a>, - EGenerates, - EGeneratesWith, EHeader<'a>, EIf<'a>, EImport<'a>, @@ -122,8 +120,6 @@ pub enum EHeader<'a> { Imports(EImports, Position), Requires(ERequires<'a>, Position), Packages(EPackages<'a>, Position), - Generates(EGenerates, Position), - GeneratesWith(EGeneratesWith, Position), Space(BadInputError, Position), Start(Position), @@ -251,30 +247,6 @@ pub enum EImports { StrLiteral(Position), } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EGenerates { - Open(Position), - Generates(Position), - IndentGenerates(Position), - Identifier(Position), - Space(BadInputError, Position), - IndentTypeStart(Position), - IndentTypeEnd(Position), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EGeneratesWith { - Open(Position), - With(Position), - IndentWith(Position), - IndentListStart(Position), - IndentListEnd(Position), - ListStart(Position), - ListEnd(Position), - Identifier(Position), - Space(BadInputError, Position), -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BadInputError { HasTab, @@ -856,8 +828,7 @@ where } // This should be enough for anyone. Right? RIGHT? - let indent_text = - "| ; : ! | ; : ! | ; : ! | ; : ! | ; : ! | ; : ! | ; : ! | ; : ! | ; : ! "; + let indent_text = "| ; : ! ".repeat(20); let cur_indent = INDENT.with(|i| *i.borrow()); diff --git a/crates/compiler/problem/src/can.rs b/crates/compiler/problem/src/can.rs index 5f2463dea8..5c6383eb87 100644 --- a/crates/compiler/problem/src/can.rs +++ b/crates/compiler/problem/src/can.rs @@ -39,7 +39,6 @@ pub enum Problem { UnusedImport(Symbol, Region), UnusedModuleImport(ModuleId, Region), ExposedButNotDefined(Symbol), - UnknownGeneratesWith(Loc), ImportNameConflict { name: ModuleName, is_alias: bool, @@ -221,6 +220,12 @@ pub enum Problem { OverAppliedCrash { region: Region, }, + UnappliedDbg { + region: Region, + }, + OverAppliedDbg { + region: Region, + }, FileProblem { filename: PathBuf, error: io::ErrorKind, @@ -260,7 +265,6 @@ impl Problem { Problem::ImportShadowsSymbol { .. } => RuntimeError, Problem::DeprecatedBackpassing(_) => Warning, Problem::ExposedButNotDefined(_) => RuntimeError, - Problem::UnknownGeneratesWith(_) => RuntimeError, Problem::UnusedArgument(_, _, _, _) => Warning, Problem::UnusedBranchDef(_, _) => Warning, Problem::PrecedenceProblem(_) => RuntimeError, @@ -315,6 +319,8 @@ impl Problem { // injecting a crash message Problem::UnappliedCrash { .. } => RuntimeError, Problem::OverAppliedCrash { .. } => RuntimeError, + Problem::UnappliedDbg { .. } => RuntimeError, + Problem::OverAppliedDbg { .. } => RuntimeError, Problem::DefsOnlyUsedInRecursion(_, _) => Warning, Problem::FileProblem { .. } => Fatal, } @@ -343,7 +349,6 @@ impl Problem { | Problem::ExplicitBuiltinTypeImport(_, region) | Problem::ImportShadowsSymbol { region, .. } | Problem::DeprecatedBackpassing(region) - | Problem::UnknownGeneratesWith(Loc { region, .. }) | Problem::UnusedArgument(_, _, _, region) | Problem::UnusedBranchDef(_, region) | Problem::PrecedenceProblem(PrecedenceProblem::BothNonAssociative(region, _, _)) @@ -480,6 +485,8 @@ impl Problem { | Problem::UnnecessaryOutputWildcard { region } | Problem::OverAppliedCrash { region } | Problem::UnappliedCrash { region } + | Problem::OverAppliedDbg { region } + | Problem::UnappliedDbg { region } | Problem::DefsOnlyUsedInRecursion(_, region) => Some(*region), Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries)) | Problem::BadRecursion(cycle_entries) => { diff --git a/crates/compiler/solve/src/module.rs b/crates/compiler/solve/src/module.rs index de76e494b6..00e09bb5d3 100644 --- a/crates/compiler/solve/src/module.rs +++ b/crates/compiler/solve/src/module.rs @@ -4,7 +4,7 @@ use crate::{aliases::Aliases, solve}; use roc_can::abilities::{AbilitiesStore, ResolvedImpl}; use roc_can::constraint::{Constraint, Constraints}; use roc_can::expr::PendingDerives; -use roc_can::module::{ExposedByModule, ResolvedImplementations, RigidVariables}; +use roc_can::module::{ExposedByModule, ModuleParams, ResolvedImplementations, RigidVariables}; use roc_collections::all::MutMap; use roc_collections::VecMap; use roc_derive::SharedDerivedModule; @@ -82,7 +82,7 @@ pub struct SolveConfig<'a> { pub checkmate: Option, /// Module params - pub params_pattern: Option, + pub module_params: Option, pub module_params_vars: VecMap, } diff --git a/crates/compiler/solve/src/pools.rs b/crates/compiler/solve/src/pools.rs index 877ebbec79..2f1e53d52b 100644 --- a/crates/compiler/solve/src/pools.rs +++ b/crates/compiler/solve/src/pools.rs @@ -1,3 +1,4 @@ +use roc_error_macros::internal_error; use roc_types::subs::{Rank, Variable}; const DEFAULT_POOLS: usize = 8; @@ -27,14 +28,14 @@ impl Pools { pub fn get_mut(&mut self, rank: Rank) -> &mut Vec { match self.0.get_mut(rank.into_usize()) { Some(reference) => reference, - None => panic!("Compiler bug: could not find pool at rank {rank}"), + None => internal_error!("Compiler bug: could not find pool at rank {rank}"), } } pub fn get(&self, rank: Rank) -> &Vec { match self.0.get(rank.into_usize()) { Some(reference) => reference, - None => panic!("Compiler bug: could not find pool at rank {rank}"), + None => internal_error!("Compiler bug: could not find pool at rank {rank}"), } } @@ -46,7 +47,7 @@ impl Pools { let last = self .0 .pop() - .unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools")); + .unwrap_or_else(|| internal_error!("Attempted to split_last() on non-empty Pools")); (last, self.0) } diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index 17bfb4c637..e82e4d8d9e 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -16,6 +16,7 @@ use roc_can::abilities::{AbilitiesStore, MemberSpecializationInfo}; use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::{Cycle, LetConstraint, OpportunisticResolve}; use roc_can::expected::{Expected, PExpected}; +use roc_can::module::ModuleParams; use roc_collections::VecMap; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] @@ -130,7 +131,7 @@ fn run_help( exposed_by_module, derived_module, function_kind, - params_pattern, + module_params, module_params_vars, .. } = config; @@ -184,7 +185,7 @@ fn run_help( abilities_store, &mut obligation_cache, &mut awaiting_specializations, - params_pattern, + module_params, module_params_vars, ); @@ -243,10 +244,10 @@ fn solve( abilities_store: &mut AbilitiesStore, obligation_cache: &mut ObligationCache, awaiting_specializations: &mut AwaitingSpecializations, - params_pattern: Option, + module_params: Option, module_params_vars: VecMap, ) -> State { - let scope = Scope::new(params_pattern); + let scope = Scope::new(module_params); let initial = Work::Constraint { scope: &scope.clone(), diff --git a/crates/compiler/solve/src/solve/scope.rs b/crates/compiler/solve/src/solve/scope.rs index 9214a9d6b2..c8137872a8 100644 --- a/crates/compiler/solve/src/solve/scope.rs +++ b/crates/compiler/solve/src/solve/scope.rs @@ -1,3 +1,4 @@ +use roc_can::module::ModuleParams; use roc_module::symbol::Symbol; use roc_types::subs::Variable; @@ -9,29 +10,19 @@ pub struct Scope { } impl Scope { - pub fn new(params_pattern: Option) -> Self { - match params_pattern { - Some(params_pattern) => match params_pattern { - roc_can::pattern::Pattern::RecordDestructure { - whole_var: _, - ext_var: _, - destructs, - } => { - let mut symbols = Vec::with_capacity(destructs.len()); - let mut variables = Vec::with_capacity(destructs.len()); + pub fn new(opt_module_params: Option) -> Self { + match opt_module_params { + Some(module_params) => { + let mut symbols = Vec::with_capacity(module_params.destructs.len()); + let mut variables = Vec::with_capacity(module_params.destructs.len()); - for destruct in destructs { - symbols.push(destruct.value.symbol); - variables.push(destruct.value.var); - } - - Self { symbols, variables } + for destruct in module_params.destructs { + symbols.push(destruct.value.symbol); + variables.push(destruct.value.var); } - _ => unreachable!( - "other pattern types should have parsed: {:?}", - params_pattern - ), - }, + + Self { symbols, variables } + } None => Self { symbols: Vec::default(), variables: Vec::default(), diff --git a/crates/compiler/test_derive/src/util.rs b/crates/compiler/test_derive/src/util.rs index a4d78169ed..26ac30e16f 100644 --- a/crates/compiler/test_derive/src/util.rs +++ b/crates/compiler/test_derive/src/util.rs @@ -438,7 +438,7 @@ fn check_derived_typechecks_and_golden( pending_derives: Default::default(), exposed_by_module: &exposed_for_module.exposed_by_module, derived_module: Default::default(), - params_pattern: None, + module_params: None, module_params_vars: imported_param_vars, #[cfg(debug_assertions)] diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index fcbd4c66b9..fdad8eb2d3 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -1885,22 +1885,22 @@ fn task_always_twice() { effectAfter : Effect a, (a -> Effect b) -> Effect b effectAfter = \(@Effect thunk), transform -> transform (thunk {}) - Task a err : Effect (Result a err) + MyTask a err : Effect (Result a err) - always : a -> Task a * + always : a -> MyTask a * always = \x -> effectAlways (Ok x) - fail : err -> Task * err + fail : err -> MyTask * err fail = \x -> effectAlways (Err x) - after : Task a err, (a -> Task b err) -> Task b err + after : MyTask a err, (a -> MyTask b err) -> MyTask b err after = \task, transform -> effectAfter task \res -> when res is Ok x -> transform x Err e -> fail e - main : Task {} F64 + main : MyTask {} F64 main = after (always "foo") (\_ -> always {}) "# @@ -1921,17 +1921,17 @@ fn wildcard_rigid() { Effect a := {} -> a - Task a err : Effect (Result a err) + MyTask a err : Effect (Result a err) # this failed because of the `*`, but worked with `err` - always : a -> Task a * + always : a -> MyTask a * always = \x -> inner = \{} -> (Ok x) @Effect inner - main : Task {} (Frac *) + main : MyTask {} (Frac *) main = always {} "# ), @@ -1950,16 +1950,16 @@ fn alias_of_alias_with_type_arguments() { Effect a := a - Task a err : Effect (Result a err) + MyTask a err : Effect (Result a err) - always : a -> Task a * + always : a -> MyTask a * always = \x -> inner = (Ok x) @Effect inner - main : Task {} F64 + main : MyTask {} F64 main = always {} "# ), @@ -1989,24 +1989,24 @@ fn todo_bad_error_message() { effectAfter : Effect a, (a -> Effect b) -> Effect b effectAfter = \(@Effect thunk), transform -> transform (thunk {}) - Task a err : Effect (Result a err) + MyTask a err : Effect (Result a err) - always : a -> Task a (Frac *) + always : a -> MyTask a (Frac *) always = \x -> effectAlways (Ok x) - # the problem is that this restricts to `Task {} *` - fail : err -> Task {} err + # the problem is that this restricts to `MyTask {} *` + fail : err -> MyTask {} err fail = \x -> effectAlways (Err x) - after : Task a err, (a -> Task b err) -> Task b err + after : MyTask a err, (a -> MyTask b err) -> MyTask b err after = \task, transform -> effectAfter task \res -> when res is Ok x -> transform x - # but here it must be `forall b. Task b {}` + # but here it must be `forall b. MyTask b {}` Err e -> fail e - main : Task {} (Frac *) + main : MyTask {} (Frac *) main = after (always "foo") (\_ -> always {}) "# diff --git a/crates/compiler/test_mono/generated/as_pattern_in_closure_arg.txt b/crates/compiler/test_mono/generated/as_pattern_in_closure_arg.txt new file mode 100644 index 0000000000..5d25c983bf --- /dev/null +++ b/crates/compiler/test_mono/generated/as_pattern_in_closure_arg.txt @@ -0,0 +1,31 @@ +procedure Num.19 (#Attr.2, #Attr.3): + let Num.282 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.282; + +procedure Test.1 (Test.12): + let Test.6 : I64 = StructAtIndex 0 Test.12; + let Test.5 : I64 = StructAtIndex 1 Test.12; + let Test.3 : I64 = StructAtIndex 2 Test.12; + let Test.4 : I64 = StructAtIndex 3 Test.12; + let Test.18 : I64 = CallByName Num.19 Test.3 Test.5; + let Test.19 : I64 = CallByName Num.19 Test.4 Test.6; + let Test.17 : {I64, I64} = Struct {Test.18, Test.19}; + ret Test.17; + +procedure Test.2 (Test.9): + let Test.7 : I64 = StructAtIndex 2 Test.9; + let Test.8 : I64 = StructAtIndex 3 Test.9; + let Test.16 : {I64, I64} = CallByName Test.1 Test.9; + let Test.10 : I64 = StructAtIndex 0 Test.16; + let Test.11 : I64 = StructAtIndex 1 Test.16; + let Test.15 : {I64, I64, I64, I64} = Struct {Test.7, Test.8, Test.10, Test.11}; + ret Test.15; + +procedure Test.0 (): + let Test.20 : I64 = 4i64; + let Test.21 : I64 = 3i64; + let Test.22 : I64 = 1i64; + let Test.23 : I64 = 2i64; + let Test.14 : {I64, I64, I64, I64} = Struct {Test.20, Test.21, Test.22, Test.23}; + let Test.13 : {I64, I64, I64, I64} = CallByName Test.2 Test.14; + ret Test.13; diff --git a/crates/compiler/test_mono/generated/capture_void_layout_task.txt b/crates/compiler/test_mono/generated/capture_void_layout_task.txt index 611c5663b3..9442fc79fb 100644 --- a/crates/compiler/test_mono/generated/capture_void_layout_task.txt +++ b/crates/compiler/test_mono/generated/capture_void_layout_task.txt @@ -38,8 +38,8 @@ procedure Num.51 (#Attr.2, #Attr.3): procedure Test.10 (Test.69, #Attr.12): let Test.72 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let #Derived_gen.18 : Int1 = lowlevel RefCountIsUnique #Attr.12; - if #Derived_gen.18 then + let #Derived_gen.20 : Int1 = lowlevel RefCountIsUnique #Attr.12; + if #Derived_gen.20 then free #Attr.12; ret Test.72; else @@ -53,7 +53,7 @@ procedure Test.10 (Test.69, #Attr.12): procedure Test.14 (Test.45, #Attr.12): let Test.55 : {{}, []} = UnionAtIndex (Id 1) (Index 1) #Attr.12; let Test.54 : [C {}, C *self {{}, []}] = UnionAtIndex (Id 1) (Index 0) #Attr.12; - joinpoint #Derived_gen.19: + joinpoint #Derived_gen.18: let Test.50 : {} = Struct {}; let Test.51 : U8 = GetTagId Test.54; joinpoint Test.52 Test.15: @@ -80,14 +80,14 @@ procedure Test.14 (Test.45, #Attr.12): jump Test.52 Test.53; in - let #Derived_gen.20 : Int1 = lowlevel RefCountIsUnique #Attr.12; - if #Derived_gen.20 then + let #Derived_gen.19 : Int1 = lowlevel RefCountIsUnique #Attr.12; + if #Derived_gen.19 then free #Attr.12; - jump #Derived_gen.19; + jump #Derived_gen.18; else inc Test.54; decref #Attr.12; - jump #Derived_gen.19; + jump #Derived_gen.18; procedure Test.20 (Test.21, Test.18): let Test.23 : [C {}, C []] = CallByName Test.32 Test.21 Test.18; diff --git a/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt b/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt index f1596bff44..b1a449d575 100644 --- a/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt +++ b/crates/compiler/test_mono/generated/compose_recursive_lambda_set_productive_nullable_wrapped.txt @@ -47,11 +47,11 @@ procedure Str.3 (#Attr.2, #Attr.3): procedure Test.1 (Test.5): ret Test.5; -procedure Test.11 (#Derived_gen.10, #Derived_gen.11): +procedure Test.11 (#Derived_gen.3, #Derived_gen.4): joinpoint Test.27 Test.12 #Attr.12: let Test.34 : Int1 = UnionAtIndex (Id 2) (Index 1) #Attr.12; let Test.33 : [, C *self Int1, C *self Int1] = UnionAtIndex (Id 2) (Index 0) #Attr.12; - joinpoint #Derived_gen.14: + joinpoint #Derived_gen.12: joinpoint Test.31 Test.29: let Test.30 : U8 = GetTagId Test.33; switch Test.30: @@ -78,16 +78,16 @@ procedure Test.11 (#Derived_gen.10, #Derived_gen.11): jump Test.31 Test.32; in - let #Derived_gen.15 : Int1 = lowlevel RefCountIsUnique #Attr.12; - if #Derived_gen.15 then + let #Derived_gen.13 : Int1 = lowlevel RefCountIsUnique #Attr.12; + if #Derived_gen.13 then free #Attr.12; - jump #Derived_gen.14; + jump #Derived_gen.12; else inc Test.33; decref #Attr.12; - jump #Derived_gen.14; + jump #Derived_gen.12; in - jump Test.27 #Derived_gen.10 #Derived_gen.11; + jump Test.27 #Derived_gen.3 #Derived_gen.4; procedure Test.2 (Test.13): ret Test.13; @@ -118,7 +118,7 @@ procedure Test.6 (Test.7, Test.8, Test.5): procedure Test.9 (Test.10, #Attr.12): let Test.43 : Int1 = UnionAtIndex (Id 1) (Index 1) #Attr.12; let Test.42 : [, C *self Int1, C *self Int1] = UnionAtIndex (Id 1) (Index 0) #Attr.12; - joinpoint #Derived_gen.12: + joinpoint #Derived_gen.14: let Test.39 : U8 = GetTagId Test.42; joinpoint Test.40 Test.38: switch Test.43: @@ -146,14 +146,14 @@ procedure Test.9 (Test.10, #Attr.12): jump Test.40 Test.41; in - let #Derived_gen.13 : Int1 = lowlevel RefCountIsUnique #Attr.12; - if #Derived_gen.13 then + let #Derived_gen.15 : Int1 = lowlevel RefCountIsUnique #Attr.12; + if #Derived_gen.15 then free #Attr.12; - jump #Derived_gen.12; + jump #Derived_gen.14; else inc Test.42; decref #Attr.12; - jump #Derived_gen.12; + jump #Derived_gen.14; procedure Test.0 (): let Test.45 : Int1 = false; diff --git a/crates/compiler/test_mono/generated/dbg_expr.txt b/crates/compiler/test_mono/generated/dbg_expr.txt new file mode 100644 index 0000000000..638fbca410 --- /dev/null +++ b/crates/compiler/test_mono/generated/dbg_expr.txt @@ -0,0 +1,56 @@ +procedure Inspect.278 (Inspect.279, Inspect.277): + let Inspect.318 : Str = CallByName Num.96 Inspect.277; + let Inspect.317 : Str = CallByName Inspect.63 Inspect.279 Inspect.318; + dec Inspect.318; + ret Inspect.317; + +procedure Inspect.30 (Inspect.147): + ret Inspect.147; + +procedure Inspect.33 (Inspect.152): + let Inspect.305 : Str = CallByName Inspect.5 Inspect.152; + let Inspect.304 : Str = CallByName Inspect.64 Inspect.305; + ret Inspect.304; + +procedure Inspect.39 (Inspect.301): + let Inspect.311 : Str = ""; + ret Inspect.311; + +procedure Inspect.5 (Inspect.150): + let Inspect.312 : I64 = CallByName Inspect.57 Inspect.150; + let Inspect.309 : {} = Struct {}; + let Inspect.308 : Str = CallByName Inspect.39 Inspect.309; + let Inspect.307 : Str = CallByName Inspect.278 Inspect.308 Inspect.312; + ret Inspect.307; + +procedure Inspect.57 (Inspect.277): + let Inspect.313 : I64 = CallByName Inspect.30 Inspect.277; + ret Inspect.313; + +procedure Inspect.63 (Inspect.300, Inspect.296): + let Inspect.320 : Str = CallByName Str.3 Inspect.300 Inspect.296; + ret Inspect.320; + +procedure Inspect.64 (Inspect.302): + ret Inspect.302; + +procedure Num.19 (#Attr.2, #Attr.3): + let Num.281 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.281; + +procedure Num.96 (#Attr.2): + let Num.282 : Str = lowlevel NumToStr #Attr.2; + ret Num.282; + +procedure Str.3 (#Attr.2, #Attr.3): + let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.236; + +procedure Test.0 (): + let Test.5 : I64 = 1i64; + let Test.2 : I64 = 2i64; + let Test.3 : Str = CallByName Inspect.33 Test.2; + dbg Test.3; + dec Test.3; + let Test.4 : I64 = CallByName Num.19 Test.5 Test.2; + ret Test.4; diff --git a/crates/compiler/test_mono/generated/dbg_inside_string.txt b/crates/compiler/test_mono/generated/dbg_inside_string.txt new file mode 100644 index 0000000000..8fa29df576 --- /dev/null +++ b/crates/compiler/test_mono/generated/dbg_inside_string.txt @@ -0,0 +1,58 @@ +procedure Inspect.250 (Inspect.251, Inspect.249): + let Inspect.323 : Str = "\""; + let Inspect.322 : Str = CallByName Inspect.63 Inspect.251 Inspect.323; + dec Inspect.323; + let Inspect.318 : Str = CallByName Inspect.63 Inspect.322 Inspect.249; + let Inspect.319 : Str = "\""; + let Inspect.317 : Str = CallByName Inspect.63 Inspect.318 Inspect.319; + dec Inspect.319; + ret Inspect.317; + +procedure Inspect.30 (Inspect.147): + ret Inspect.147; + +procedure Inspect.33 (Inspect.152): + let Inspect.305 : Str = CallByName Inspect.5 Inspect.152; + let Inspect.304 : Str = CallByName Inspect.64 Inspect.305; + ret Inspect.304; + +procedure Inspect.39 (Inspect.301): + let Inspect.311 : Str = ""; + ret Inspect.311; + +procedure Inspect.47 (Inspect.249): + let Inspect.313 : Str = CallByName Inspect.30 Inspect.249; + ret Inspect.313; + +procedure Inspect.5 (Inspect.150): + let Inspect.312 : Str = CallByName Inspect.47 Inspect.150; + let Inspect.309 : {} = Struct {}; + let Inspect.308 : Str = CallByName Inspect.39 Inspect.309; + let Inspect.307 : Str = CallByName Inspect.250 Inspect.308 Inspect.312; + dec Inspect.312; + ret Inspect.307; + +procedure Inspect.63 (Inspect.300, Inspect.296): + let Inspect.321 : Str = CallByName Str.3 Inspect.300 Inspect.296; + ret Inspect.321; + +procedure Inspect.64 (Inspect.302): + ret Inspect.302; + +procedure Str.3 (#Attr.2, #Attr.3): + let Str.238 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.238; + +procedure Test.0 (): + let Test.5 : Str = "Hello "; + let Test.2 : Str = "world"; + inc Test.2; + let Test.3 : Str = CallByName Inspect.33 Test.2; + dbg Test.3; + dec Test.3; + let Test.8 : Str = "!"; + let Test.6 : Str = CallByName Str.3 Test.2 Test.8; + dec Test.8; + let Test.4 : Str = CallByName Str.3 Test.5 Test.6; + dec Test.6; + ret Test.4; diff --git a/crates/compiler/test_mono/generated/dbg_nested_expr.txt b/crates/compiler/test_mono/generated/dbg_nested_expr.txt new file mode 100644 index 0000000000..d7f35b4d97 --- /dev/null +++ b/crates/compiler/test_mono/generated/dbg_nested_expr.txt @@ -0,0 +1,56 @@ +procedure Inspect.278 (Inspect.279, Inspect.277): + let Inspect.318 : Str = CallByName Num.96 Inspect.277; + let Inspect.317 : Str = CallByName Inspect.63 Inspect.279 Inspect.318; + dec Inspect.318; + ret Inspect.317; + +procedure Inspect.30 (Inspect.147): + ret Inspect.147; + +procedure Inspect.33 (Inspect.152): + let Inspect.324 : Str = CallByName Inspect.5 Inspect.152; + let Inspect.323 : Str = CallByName Inspect.64 Inspect.324; + ret Inspect.323; + +procedure Inspect.39 (Inspect.301): + let Inspect.311 : Str = ""; + ret Inspect.311; + +procedure Inspect.5 (Inspect.150): + let Inspect.312 : I64 = CallByName Inspect.57 Inspect.150; + let Inspect.309 : {} = Struct {}; + let Inspect.308 : Str = CallByName Inspect.39 Inspect.309; + let Inspect.307 : Str = CallByName Inspect.278 Inspect.308 Inspect.312; + ret Inspect.307; + +procedure Inspect.57 (Inspect.277): + let Inspect.313 : I64 = CallByName Inspect.30 Inspect.277; + ret Inspect.313; + +procedure Inspect.63 (Inspect.300, Inspect.296): + let Inspect.320 : Str = CallByName Str.3 Inspect.300 Inspect.296; + ret Inspect.320; + +procedure Inspect.64 (Inspect.302): + ret Inspect.302; + +procedure Num.96 (#Attr.2): + let Num.281 : Str = lowlevel NumToStr #Attr.2; + ret Num.281; + +procedure Str.3 (#Attr.2, #Attr.3): + let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.236; + +procedure Test.0 (): + let Test.6 : I64 = 1i64; + let Test.7 : Str = CallByName Inspect.33 Test.6; + dbg Test.7; + dec Test.7; + let Test.8 : Str = CallByName Inspect.33 Test.6; + dbg Test.8; + dec Test.8; + let Test.9 : Str = CallByName Inspect.33 Test.6; + dbg Test.9; + dec Test.9; + ret Test.6; diff --git a/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt b/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt index 8a049df979..53041c8922 100644 --- a/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt +++ b/crates/compiler/test_mono/generated/encode_derived_tag_one_field_string.txt @@ -76,7 +76,7 @@ procedure List.8 (#Attr.2, #Attr.3): let List.649 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; ret List.649; -procedure List.95 (#Derived_gen.10, #Derived_gen.11, #Derived_gen.12, #Derived_gen.13, #Derived_gen.14): +procedure List.95 (#Derived_gen.13, #Derived_gen.14, #Derived_gen.15, #Derived_gen.16, #Derived_gen.17): joinpoint List.628 List.169 List.170 List.171 List.172 List.173: let List.630 : Int1 = CallByName Num.22 List.172 List.173; if List.630 then @@ -90,8 +90,8 @@ procedure List.95 (#Derived_gen.10, #Derived_gen.11, #Derived_gen.12, #Derived_g dec List.169; ret List.170; in - inc #Derived_gen.10; - jump List.628 #Derived_gen.10 #Derived_gen.11 #Derived_gen.12 #Derived_gen.13 #Derived_gen.14; + inc #Derived_gen.13; + jump List.628 #Derived_gen.13 #Derived_gen.14 #Derived_gen.15 #Derived_gen.16 #Derived_gen.17; procedure Num.127 (#Attr.2): let Num.282 : U8 = lowlevel NumIntCast #Attr.2; diff --git a/crates/compiler/test_mono/generated/inline_return_joinpoints_in_union_lambda_set.txt b/crates/compiler/test_mono/generated/inline_return_joinpoints_in_union_lambda_set.txt index 575a74f1db..8e68099151 100644 --- a/crates/compiler/test_mono/generated/inline_return_joinpoints_in_union_lambda_set.txt +++ b/crates/compiler/test_mono/generated/inline_return_joinpoints_in_union_lambda_set.txt @@ -17,7 +17,7 @@ procedure Test.4 (Test.5, #Attr.12): let Test.16 : I64 = CallByName Num.19 Test.5 Test.17; ret Test.16; -procedure Test.0 (#Derived_gen.0): +procedure Test.0 (#Derived_gen.2): joinpoint Test.7 Test.1: let Test.21 : I64 = 1i64; let Test.9 : I64 = CallByName Num.19 Test.1 Test.21; @@ -33,4 +33,4 @@ procedure Test.0 (#Derived_gen.0): ret Test.8; in - jump Test.7 #Derived_gen.0; + jump Test.7 #Derived_gen.2; diff --git a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt index 59f6e53573..75c3d51b4d 100644 --- a/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt +++ b/crates/compiler/test_mono/generated/issue_4772_weakened_monomorphic_destructure.txt @@ -91,8 +91,8 @@ procedure Test.19 (): let Test.120 : [C Str, C {List U8, I64}] = TagId(0) Test.122; ret Test.120; else - dec Test.92; dec Test.93; + dec Test.92; let Test.128 : Str = "not a number"; let Test.126 : [C Str, C {List U8, I64}] = TagId(0) Test.128; ret Test.126; diff --git a/crates/compiler/test_mono/generated/list_map_take_capturing_or_noncapturing.txt b/crates/compiler/test_mono/generated/list_map_take_capturing_or_noncapturing.txt index 2cde098ebb..7dc3b9c28e 100644 --- a/crates/compiler/test_mono/generated/list_map_take_capturing_or_noncapturing.txt +++ b/crates/compiler/test_mono/generated/list_map_take_capturing_or_noncapturing.txt @@ -108,8 +108,8 @@ procedure Test.0 (): else let Test.22 : Str = "B"; let Test.23 : Int1 = lowlevel Eq Test.22 Test.12; - dec Test.22; dec Test.12; + dec Test.22; if Test.23 then let Test.17 : [C U8, C U8, C ] = TagId(1) Test.2; jump Test.13 Test.17; diff --git a/crates/compiler/test_mono/generated/opaque_as_pattern_in_closure_arg.txt b/crates/compiler/test_mono/generated/opaque_as_pattern_in_closure_arg.txt new file mode 100644 index 0000000000..834b3ee0f5 --- /dev/null +++ b/crates/compiler/test_mono/generated/opaque_as_pattern_in_closure_arg.txt @@ -0,0 +1,18 @@ +procedure Num.21 (#Attr.2, #Attr.3): + let Num.281 : U64 = lowlevel NumMul #Attr.2 #Attr.3; + ret Num.281; + +procedure Test.2 (Test.8): + let Test.14 : U64 = 2i64; + let Test.13 : U64 = CallByName Num.21 Test.8 Test.14; + ret Test.13; + +procedure Test.3 (Test.7): + let Test.12 : U64 = CallByName Test.2 Test.7; + let Test.11 : {U64, U64} = Struct {Test.7, Test.12}; + ret Test.11; + +procedure Test.0 (): + let Test.10 : U64 = 42i64; + let Test.9 : {U64, U64} = CallByName Test.3 Test.10; + ret Test.9; diff --git a/crates/compiler/test_mono/generated/pattern_as_toplevel.txt b/crates/compiler/test_mono/generated/pattern_as_toplevel.txt index 4a70501f51..a124668cf0 100644 --- a/crates/compiler/test_mono/generated/pattern_as_toplevel.txt +++ b/crates/compiler/test_mono/generated/pattern_as_toplevel.txt @@ -20,9 +20,9 @@ procedure Test.0 (): if Test.13 then let Test.6 : {I64, Str} = CallByName Test.1; let Test.5 : Int1 = CallByName Bool.11 Test.6 Test.4; - dec Test.6; let #Derived_gen.0 : Str = StructAtIndex 1 Test.4; dec #Derived_gen.0; + dec Test.6; ret Test.5; else let #Derived_gen.1 : Str = StructAtIndex 1 Test.4; diff --git a/crates/compiler/test_mono/generated/pizza_dbg.txt b/crates/compiler/test_mono/generated/pizza_dbg.txt new file mode 100644 index 0000000000..8f39d447e6 --- /dev/null +++ b/crates/compiler/test_mono/generated/pizza_dbg.txt @@ -0,0 +1,59 @@ +procedure Inspect.278 (Inspect.279, Inspect.277): + let Inspect.318 : Str = CallByName Num.96 Inspect.277; + let Inspect.317 : Str = CallByName Inspect.63 Inspect.279 Inspect.318; + dec Inspect.318; + ret Inspect.317; + +procedure Inspect.30 (Inspect.147): + ret Inspect.147; + +procedure Inspect.33 (Inspect.152): + let Inspect.322 : Str = CallByName Inspect.5 Inspect.152; + let Inspect.321 : Str = CallByName Inspect.64 Inspect.322; + ret Inspect.321; + +procedure Inspect.39 (Inspect.301): + let Inspect.311 : Str = ""; + ret Inspect.311; + +procedure Inspect.5 (Inspect.150): + let Inspect.312 : I64 = CallByName Inspect.57 Inspect.150; + let Inspect.309 : {} = Struct {}; + let Inspect.308 : Str = CallByName Inspect.39 Inspect.309; + let Inspect.307 : Str = CallByName Inspect.278 Inspect.308 Inspect.312; + ret Inspect.307; + +procedure Inspect.57 (Inspect.277): + let Inspect.313 : I64 = CallByName Inspect.30 Inspect.277; + ret Inspect.313; + +procedure Inspect.63 (Inspect.300, Inspect.296): + let Inspect.320 : Str = CallByName Str.3 Inspect.300 Inspect.296; + ret Inspect.320; + +procedure Inspect.64 (Inspect.302): + ret Inspect.302; + +procedure Num.19 (#Attr.2, #Attr.3): + let Num.281 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.281; + +procedure Num.96 (#Attr.2): + let Num.282 : Str = lowlevel NumToStr #Attr.2; + ret Num.282; + +procedure Str.3 (#Attr.2, #Attr.3): + let Str.236 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.236; + +procedure Test.0 (): + let Test.4 : I64 = 1i64; + let Test.5 : Str = CallByName Inspect.33 Test.4; + dbg Test.5; + dec Test.5; + let Test.9 : I64 = 2i64; + let Test.3 : I64 = CallByName Num.19 Test.4 Test.9; + let Test.6 : Str = CallByName Inspect.33 Test.3; + dbg Test.6; + dec Test.6; + ret Test.3; diff --git a/crates/compiler/test_mono/generated/record_as_pattern_in_closure_arg.txt b/crates/compiler/test_mono/generated/record_as_pattern_in_closure_arg.txt new file mode 100644 index 0000000000..5d25c983bf --- /dev/null +++ b/crates/compiler/test_mono/generated/record_as_pattern_in_closure_arg.txt @@ -0,0 +1,31 @@ +procedure Num.19 (#Attr.2, #Attr.3): + let Num.282 : I64 = lowlevel NumAdd #Attr.2 #Attr.3; + ret Num.282; + +procedure Test.1 (Test.12): + let Test.6 : I64 = StructAtIndex 0 Test.12; + let Test.5 : I64 = StructAtIndex 1 Test.12; + let Test.3 : I64 = StructAtIndex 2 Test.12; + let Test.4 : I64 = StructAtIndex 3 Test.12; + let Test.18 : I64 = CallByName Num.19 Test.3 Test.5; + let Test.19 : I64 = CallByName Num.19 Test.4 Test.6; + let Test.17 : {I64, I64} = Struct {Test.18, Test.19}; + ret Test.17; + +procedure Test.2 (Test.9): + let Test.7 : I64 = StructAtIndex 2 Test.9; + let Test.8 : I64 = StructAtIndex 3 Test.9; + let Test.16 : {I64, I64} = CallByName Test.1 Test.9; + let Test.10 : I64 = StructAtIndex 0 Test.16; + let Test.11 : I64 = StructAtIndex 1 Test.16; + let Test.15 : {I64, I64, I64, I64} = Struct {Test.7, Test.8, Test.10, Test.11}; + ret Test.15; + +procedure Test.0 (): + let Test.20 : I64 = 4i64; + let Test.21 : I64 = 3i64; + let Test.22 : I64 = 1i64; + let Test.23 : I64 = 2i64; + let Test.14 : {I64, I64, I64, I64} = Struct {Test.20, Test.21, Test.22, Test.23}; + let Test.13 : {I64, I64, I64, I64} = CallByName Test.2 Test.14; + ret Test.13; diff --git a/crates/compiler/test_mono/generated/recursive_function_and_union_with_inference_hole.txt b/crates/compiler/test_mono/generated/recursive_function_and_union_with_inference_hole.txt index 2931f9146a..d077c2d0f0 100644 --- a/crates/compiler/test_mono/generated/recursive_function_and_union_with_inference_hole.txt +++ b/crates/compiler/test_mono/generated/recursive_function_and_union_with_inference_hole.txt @@ -31,7 +31,7 @@ procedure List.71 (#Attr.2, #Attr.3): let List.643 : List [C List *self] = lowlevel ListAppendUnsafe #Attr.2 #Attr.3; ret List.643; -procedure List.95 (#Derived_gen.0, #Derived_gen.1, #Derived_gen.2, #Derived_gen.3, #Derived_gen.4): +procedure List.95 (#Derived_gen.1, #Derived_gen.2, #Derived_gen.3, #Derived_gen.4, #Derived_gen.5): joinpoint List.631 List.169 List.170 List.171 List.172 List.173: let List.633 : Int1 = CallByName Num.22 List.172 List.173; if List.633 then @@ -45,8 +45,8 @@ procedure List.95 (#Derived_gen.0, #Derived_gen.1, #Derived_gen.2, #Derived_gen. dec List.169; ret List.170; in - inc #Derived_gen.0; - jump List.631 #Derived_gen.0 #Derived_gen.1 #Derived_gen.2 #Derived_gen.3 #Derived_gen.4; + inc #Derived_gen.1; + jump List.631 #Derived_gen.1 #Derived_gen.2 #Derived_gen.3 #Derived_gen.4 #Derived_gen.5; procedure Num.22 (#Attr.2, #Attr.3): let Num.282 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; diff --git a/crates/compiler/test_mono/generated/recursive_lambda_set_resolved_only_upon_specialization.txt b/crates/compiler/test_mono/generated/recursive_lambda_set_resolved_only_upon_specialization.txt index f679a2e5d0..06f48f983f 100644 --- a/crates/compiler/test_mono/generated/recursive_lambda_set_resolved_only_upon_specialization.txt +++ b/crates/compiler/test_mono/generated/recursive_lambda_set_resolved_only_upon_specialization.txt @@ -10,7 +10,7 @@ procedure Num.21 (#Attr.2, #Attr.3): let Num.281 : U8 = lowlevel NumMul #Attr.2 #Attr.3; ret Num.281; -procedure Test.1 (#Derived_gen.0, #Derived_gen.1): +procedure Test.1 (#Derived_gen.2, #Derived_gen.3): joinpoint Test.11 Test.2 Test.3: let Test.26 : U8 = 0i64; let Test.22 : Int1 = CallByName Bool.11 Test.2 Test.26; @@ -33,9 +33,9 @@ procedure Test.1 (#Derived_gen.0, #Derived_gen.1): let Test.14 : [, C *self U8] = TagId(0) Test.3 Test.2; jump Test.11 Test.13 Test.14; in - jump Test.11 #Derived_gen.0 #Derived_gen.1; + jump Test.11 #Derived_gen.2 #Derived_gen.3; -procedure Test.4 (#Derived_gen.2, #Derived_gen.3): +procedure Test.4 (#Derived_gen.0, #Derived_gen.1): joinpoint Test.15 Test.5 #Attr.12: let Test.20 : U8 = UnionAtIndex (Id 0) (Index 1) #Attr.12; let Test.19 : [, C *self U8] = UnionAtIndex (Id 0) (Index 0) #Attr.12; @@ -61,7 +61,7 @@ procedure Test.4 (#Derived_gen.2, #Derived_gen.3): decref #Attr.12; jump #Derived_gen.4; in - jump Test.15 #Derived_gen.2 #Derived_gen.3; + jump Test.15 #Derived_gen.0 #Derived_gen.1; procedure Test.6 (Test.7): ret Test.7; diff --git a/crates/compiler/test_mono/generated/recursively_build_effect.txt b/crates/compiler/test_mono/generated/recursively_build_effect.txt index 87e8e672ac..c90a2ec808 100644 --- a/crates/compiler/test_mono/generated/recursively_build_effect.txt +++ b/crates/compiler/test_mono/generated/recursively_build_effect.txt @@ -8,8 +8,8 @@ procedure Str.3 (#Attr.2, #Attr.3): procedure Test.11 (Test.29, #Attr.12): let Test.32 : {} = UnionAtIndex (Id 0) (Index 0) #Attr.12; - let #Derived_gen.9 : Int1 = lowlevel RefCountIsUnique #Attr.12; - if #Derived_gen.9 then + let #Derived_gen.11 : Int1 = lowlevel RefCountIsUnique #Attr.12; + if #Derived_gen.11 then free #Attr.12; ret Test.32; else @@ -19,11 +19,11 @@ procedure Test.11 (Test.29, #Attr.12): procedure Test.11 (Test.29, Test.10): ret Test.10; -procedure Test.14 (#Derived_gen.7, #Derived_gen.8): +procedure Test.14 (#Derived_gen.0, #Derived_gen.1): joinpoint Test.38 Test.37 #Attr.12: let Test.46 : {} = UnionAtIndex (Id 1) (Index 1) #Attr.12; let Test.45 : I64 = UnionAtIndex (Id 1) (Index 0) #Attr.12; - joinpoint #Derived_gen.10: + joinpoint #Derived_gen.9: let Test.44 : {} = Struct {}; let Test.43 : {} = CallByName Test.11 Test.44 Test.46; let Test.39 : [C {}, C I64 {}] = CallByName Test.9 Test.43 Test.45; @@ -38,15 +38,15 @@ procedure Test.14 (#Derived_gen.7, #Derived_gen.8): jump Test.38 Test.41 Test.39; in - let #Derived_gen.11 : Int1 = lowlevel RefCountIsUnique #Attr.12; - if #Derived_gen.11 then + let #Derived_gen.10 : Int1 = lowlevel RefCountIsUnique #Attr.12; + if #Derived_gen.10 then free #Attr.12; - jump #Derived_gen.10; + jump #Derived_gen.9; else decref #Attr.12; - jump #Derived_gen.10; + jump #Derived_gen.9; in - jump Test.38 #Derived_gen.7 #Derived_gen.8; + jump Test.38 #Derived_gen.0 #Derived_gen.1; procedure Test.2 (): let Test.6 : Str = "Hello"; diff --git a/crates/compiler/test_mono/generated/specialize_after_match.txt b/crates/compiler/test_mono/generated/specialize_after_match.txt index cb95b4a71a..081cbdc840 100644 --- a/crates/compiler/test_mono/generated/specialize_after_match.txt +++ b/crates/compiler/test_mono/generated/specialize_after_match.txt @@ -23,7 +23,7 @@ procedure Test.2 (Test.9, Test.10): let Test.29 : U64 = CallByName Test.3 Test.9; ret Test.29; else - joinpoint #Derived_gen.4: + joinpoint #Derived_gen.1: let Test.13 : Str = UnionAtIndex (Id 0) (Index 0) Test.10; let Test.14 : [, C Str *self] = UnionAtIndex (Id 0) (Index 1) Test.10; let Test.33 : U64 = CallByName Test.3 Test.12; @@ -36,15 +36,15 @@ procedure Test.2 (Test.9, Test.10): else ret Test.16; in - let #Derived_gen.5 : Int1 = lowlevel RefCountIsUnique Test.9; - if #Derived_gen.5 then + let #Derived_gen.2 : Int1 = lowlevel RefCountIsUnique Test.9; + if #Derived_gen.2 then dec Test.11; free Test.9; - jump #Derived_gen.4; + jump #Derived_gen.1; else inc Test.12; decref Test.9; - jump #Derived_gen.4; + jump #Derived_gen.1; procedure Test.3 (Test.17): let Test.26 : U8 = 1i64; @@ -55,22 +55,22 @@ procedure Test.3 (Test.17): ret Test.22; else let Test.18 : [, C Str *self] = UnionAtIndex (Id 0) (Index 1) Test.17; - joinpoint #Derived_gen.1: + joinpoint #Derived_gen.3: let Test.24 : U64 = 1i64; let Test.25 : U64 = CallByName Test.3 Test.18; let Test.23 : U64 = CallByName Num.19 Test.24 Test.25; ret Test.23; in - let #Derived_gen.3 : Int1 = lowlevel RefCountIsUnique Test.17; - if #Derived_gen.3 then - let #Derived_gen.2 : Str = UnionAtIndex (Id 0) (Index 0) Test.17; - dec #Derived_gen.2; + let #Derived_gen.5 : Int1 = lowlevel RefCountIsUnique Test.17; + if #Derived_gen.5 then + let #Derived_gen.4 : Str = UnionAtIndex (Id 0) (Index 0) Test.17; + dec #Derived_gen.4; free Test.17; - jump #Derived_gen.1; + jump #Derived_gen.3; else inc Test.18; decref Test.17; - jump #Derived_gen.1; + jump #Derived_gen.3; procedure Test.0 (): let Test.5 : [, C Str *self] = TagId(1) ; diff --git a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt index 5f6d6112b2..69fbebfa8a 100644 --- a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt +++ b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_does_not_duplicate_identical_concrete_types.txt @@ -65,7 +65,7 @@ procedure List.8 (#Attr.2, #Attr.3): let List.649 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; ret List.649; -procedure List.95 (#Derived_gen.9, #Derived_gen.10, #Derived_gen.11, #Derived_gen.12, #Derived_gen.13): +procedure List.95 (#Derived_gen.12, #Derived_gen.13, #Derived_gen.14, #Derived_gen.15, #Derived_gen.16): joinpoint List.628 List.169 List.170 List.171 List.172 List.173: let List.630 : Int1 = CallByName Num.22 List.172 List.173; if List.630 then @@ -79,8 +79,8 @@ procedure List.95 (#Derived_gen.9, #Derived_gen.10, #Derived_gen.11, #Derived_ge dec List.169; ret List.170; in - inc #Derived_gen.9; - jump List.628 #Derived_gen.9 #Derived_gen.10 #Derived_gen.11 #Derived_gen.12 #Derived_gen.13; + inc #Derived_gen.12; + jump List.628 #Derived_gen.12 #Derived_gen.13 #Derived_gen.14 #Derived_gen.15 #Derived_gen.16; procedure Num.127 (#Attr.2): let Num.282 : U8 = lowlevel NumIntCast #Attr.2; diff --git a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt index 3c8a491dd6..93c97515fc 100644 --- a/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt +++ b/crates/compiler/test_mono/generated/unspecialized_lambda_set_unification_keeps_all_concrete_types_without_unification_of_unifiable.txt @@ -141,7 +141,7 @@ procedure List.8 (#Attr.2, #Attr.3): let List.676 : List U8 = lowlevel ListConcat #Attr.2 #Attr.3; ret List.676; -procedure List.95 (#Derived_gen.26, #Derived_gen.27, #Derived_gen.28, #Derived_gen.29, #Derived_gen.30): +procedure List.95 (#Derived_gen.35, #Derived_gen.36, #Derived_gen.37, #Derived_gen.38, #Derived_gen.39): joinpoint List.628 List.169 List.170 List.171 List.172 List.173: let List.630 : Int1 = CallByName Num.22 List.172 List.173; if List.630 then @@ -155,10 +155,10 @@ procedure List.95 (#Derived_gen.26, #Derived_gen.27, #Derived_gen.28, #Derived_g dec List.169; ret List.170; in - inc #Derived_gen.26; - jump List.628 #Derived_gen.26 #Derived_gen.27 #Derived_gen.28 #Derived_gen.29 #Derived_gen.30; + inc #Derived_gen.35; + jump List.628 #Derived_gen.35 #Derived_gen.36 #Derived_gen.37 #Derived_gen.38 #Derived_gen.39; -procedure List.95 (#Derived_gen.34, #Derived_gen.35, #Derived_gen.36, #Derived_gen.37, #Derived_gen.38): +procedure List.95 (#Derived_gen.49, #Derived_gen.50, #Derived_gen.51, #Derived_gen.52, #Derived_gen.53): joinpoint List.655 List.169 List.170 List.171 List.172 List.173: let List.657 : Int1 = CallByName Num.22 List.172 List.173; if List.657 then @@ -172,8 +172,8 @@ procedure List.95 (#Derived_gen.34, #Derived_gen.35, #Derived_gen.36, #Derived_g dec List.169; ret List.170; in - inc #Derived_gen.34; - jump List.655 #Derived_gen.34 #Derived_gen.35 #Derived_gen.36 #Derived_gen.37 #Derived_gen.38; + inc #Derived_gen.49; + jump List.655 #Derived_gen.49 #Derived_gen.50 #Derived_gen.51 #Derived_gen.52 #Derived_gen.53; procedure Num.127 (#Attr.2): let Num.286 : U8 = lowlevel NumIntCast #Attr.2; diff --git a/crates/compiler/test_mono/src/tests.rs b/crates/compiler/test_mono/src/tests.rs index 5e8e78ed0a..148bb4e417 100644 --- a/crates/compiler/test_mono/src/tests.rs +++ b/crates/compiler/test_mono/src/tests.rs @@ -657,6 +657,31 @@ fn record_optional_field_function_use_default() { " } +#[mono_test] +fn record_as_pattern_in_closure_arg() { + r" + f = \{x, y, w, h} -> (x + w, y + h) + + g = \({ x, y } as box) -> + (right, bottom) = f box + (x, y, right, bottom) + + g { x: 1, y: 2, w: 3, h: 4 } + " +} + +#[mono_test] +fn opaque_as_pattern_in_closure_arg() { + r" + Opaque := U64 + + f = \(@Opaque x) -> x * 2 + g = \(@Opaque x as s) -> (x, f s) + + g (@Opaque 42) + " +} + #[mono_test] fn quicksort_help() { // do we still need with_larger_debug_stack? @@ -3243,6 +3268,45 @@ fn dbg_str_followed_by_number() { ) } +#[mono_test] +fn dbg_expr() { + indoc!( + r#" + 1 + (dbg 2) + "# + ) +} + +#[mono_test] +fn dbg_nested_expr() { + indoc!( + r#" + dbg (dbg (dbg 1)) + "# + ) +} + +#[mono_test] +fn dbg_inside_string() { + indoc!( + r#" + "Hello $(dbg "world")!" + "# + ) +} + +#[mono_test] +fn pizza_dbg() { + indoc!( + r#" + 1 + |> dbg + |> Num.add 2 + |> dbg + "# + ) +} + #[mono_test] fn linked_list_reverse() { indoc!( @@ -3319,9 +3383,9 @@ fn capture_void_layout_task() { Fx a : {} -> a - Task ok err : Fx (Result ok err) + OtherTask ok err : Fx (Result ok err) - succeed : ok -> Task ok * + succeed : ok -> OtherTask ok * succeed = \ok -> \{} -> Ok ok after : Fx a, (a -> Fx b) -> Fx b @@ -3333,7 +3397,7 @@ fn capture_void_layout_task() { afterInner - await : Task a err, (a -> Task b err) -> Task b err + await : OtherTask a err, (a -> OtherTask b err) -> OtherTask b err await = \fx, toNext -> inner = after fx \result -> when result is @@ -3343,12 +3407,12 @@ fn capture_void_layout_task() { Err e -> (\{} -> Err e) inner - forEach : List a, (a -> Task {} err) -> Task {} err + forEach : List a, (a -> OtherTask {} err) -> OtherTask {} err forEach = \list, fromElem -> List.walk list (succeed {}) \task, elem -> await task \{} -> fromElem elem - main : Task {} [] + main : OtherTask {} [] main = forEach [] \_ -> succeed {} "# diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/arg_pattern_as.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/arg_pattern_as.expr.formatted.roc new file mode 100644 index 0000000000..f0657bfe44 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/arg_pattern_as.expr.formatted.roc @@ -0,0 +1,2 @@ +\({ x, y } as point), (@Location inner as outer) -> + crash "" \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/arg_pattern_as.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/arg_pattern_as.expr.result-ast new file mode 100644 index 0000000000..dd339a58e5 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/arg_pattern_as.expr.result-ast @@ -0,0 +1,57 @@ +SpaceAfter( + Closure( + [ + @2-19 As( + @2-10 RecordDestructure( + [ + @4-5 Identifier { + ident: "x", + }, + @7-8 Identifier { + ident: "y", + }, + ], + ), + PatternAs { + spaces_before: [], + identifier: @14-19 "point", + }, + ), + @23-47 As( + @23-38 Apply( + @23-32 OpaqueRef( + "@Location", + ), + [ + @33-38 Identifier { + ident: "inner", + }, + ], + ), + PatternAs { + spaces_before: [], + identifier: @42-47 "outer", + }, + ), + ], + @56-64 SpaceBefore( + Apply( + @56-61 Crash, + [ + @62-64 Str( + PlainLine( + "", + ), + ), + ], + Space, + ), + [ + Newline, + ], + ), + ), + [ + Newline, + ], +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/arg_pattern_as.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/arg_pattern_as.expr.roc new file mode 100644 index 0000000000..33fc763c72 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/arg_pattern_as.expr.roc @@ -0,0 +1,2 @@ +\({ x, y } as point), (@Location inner as outer) -> + crash "" diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.formatted.roc index 44de35e8d2..da3db291a9 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.formatted.roc @@ -1,3 +1 @@ -dbg 1 == 1 - -4 \ No newline at end of file +dbg 1 \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.result-ast index ef6e22eecb..c298fa3337 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.result-ast @@ -1,32 +1,12 @@ -SpaceBefore( - SpaceAfter( - Dbg( - @5-11 BinOps( - [ - ( - @5-6 Num( - "1", - ), - @7-9 Equals, - ), - ], - @10-11 Num( - "1", - ), - ), - @13-14 SpaceBefore( - Num( - "4", - ), - [ - Newline, - Newline, - ], - ), - ), +SpaceAfter( + Apply( + @0-5 Dbg, [ - Newline, + @4-5 Num( + "1", + ), ], + Space, ), [ Newline, diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.roc index fab415486d..d99a2ec629 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg.expr.roc @@ -1,4 +1 @@ - -dbg 1 == 1 - -4 +dbg 1 diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.formatted.roc new file mode 100644 index 0000000000..44de35e8d2 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.formatted.roc @@ -0,0 +1,3 @@ +dbg 1 == 1 + +4 \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.result-ast new file mode 100644 index 0000000000..895d9cf6e3 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.result-ast @@ -0,0 +1,34 @@ +SpaceBefore( + SpaceAfter( + DbgStmt( + @5-11 BinOps( + [ + ( + @5-6 Num( + "1", + ), + @7-9 Equals, + ), + ], + @10-11 Num( + "1", + ), + ), + @13-14 SpaceBefore( + Num( + "4", + ), + [ + Newline, + Newline, + ], + ), + ), + [ + Newline, + ], + ), + [ + Newline, + ], +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.roc new file mode 100644 index 0000000000..fab415486d --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt.expr.roc @@ -0,0 +1,4 @@ + +dbg 1 == 1 + +4 diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_multiline.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_multiline.expr.formatted.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/dbg_multiline.expr.formatted.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_multiline.expr.formatted.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_multiline.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_multiline.expr.result-ast similarity index 97% rename from crates/compiler/test_syntax/tests/snapshots/pass/dbg_multiline.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_multiline.expr.result-ast index 8bb47ecd8d..1592169153 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_multiline.expr.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_multiline.expr.result-ast @@ -1,5 +1,5 @@ SpaceAfter( - Dbg( + DbgStmt( @4-16 Tuple( [ @5-6 Num( diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/dbg_multiline.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_multiline.expr.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/dbg_multiline.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/dbg_stmt_multiline.expr.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_hosted_header.header.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_hosted_header.header.result-ast index f8fd0929b3..3109a99ed4 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_hosted_header.header.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_hosted_header.header.result-ast @@ -22,24 +22,6 @@ SpacesBefore { }, item: [], }, - generates: KeywordItem { - keyword: Spaces { - before: [], - item: GeneratesKeyword, - after: [], - }, - item: UppercaseIdent( - "Bar", - ), - }, - generates_with: KeywordItem { - keyword: Spaces { - before: [], - item: WithKeyword, - after: [], - }, - item: [], - }, }, ), } diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_hosted_header.header.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_hosted_header.header.roc index e43d69f4a1..be42a79bdc 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_hosted_header.header.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_hosted_header.header.roc @@ -1 +1 @@ -hosted Foo exposes [] imports [] generates Bar with [] +hosted Foo exposes [] imports [] diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.formatted.roc index 79d637033e..b6c42141d3 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.formatted.roc @@ -1,6 +1,6 @@ app [main] { pf: - "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br", + "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", } main = diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.result-ast index 3f702b5ba1..1a7a25e93d 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.result-ast @@ -20,7 +20,7 @@ Full( ], platform_marker: None, package_name: @17-132 PackageName( - "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br", + "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", ), }, [ diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.roc b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.roc index 47dba0ce18..dbe57a3635 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/newline_in_packages.full.roc @@ -1,5 +1,5 @@ app [main] { pf: -"https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" +"https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } main = diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.formatted.roc index aa36e23e6a..ef3bf3291b 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.formatted.roc @@ -10,9 +10,3 @@ hosted Foo Blah, Baz.{ stuff, things }, ] - generates Bar with - [ - map, - after, - loop, - ] diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.result-ast index a8b20a24dc..b94ff73ac9 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.result-ast +++ b/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.result-ast @@ -95,58 +95,6 @@ SpacesBefore { ], }, }, - generates: KeywordItem { - keyword: Spaces { - before: [ - Newline, - ], - item: GeneratesKeyword, - after: [], - }, - item: UppercaseIdent( - "Bar", - ), - }, - generates_with: KeywordItem { - keyword: Spaces { - before: [], - item: WithKeyword, - after: [ - Newline, - ], - }, - item: Collection { - items: [ - @239-242 SpaceBefore( - ExposedName( - "map", - ), - [ - Newline, - ], - ), - @256-261 SpaceBefore( - ExposedName( - "after", - ), - [ - Newline, - ], - ), - @275-279 SpaceBefore( - ExposedName( - "loop", - ), - [ - Newline, - ], - ), - ], - final_comments: [ - Newline, - ], - }, - }, }, ), } diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.roc b/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.roc index 2f417ee54b..f555902730 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/nonempty_hosted_header.header.roc @@ -10,9 +10,3 @@ hosted Foo Blah, Baz.{ stuff, things }, ] - generates Bar with - [ - map, - after, - loop, - ] diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/pizza_dbg.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/pizza_dbg.expr.formatted.roc new file mode 100644 index 0000000000..819011df2d --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/pizza_dbg.expr.formatted.roc @@ -0,0 +1 @@ +1 |> dbg \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/pizza_dbg.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/pizza_dbg.expr.result-ast new file mode 100644 index 0000000000..88b96262a7 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/pizza_dbg.expr.result-ast @@ -0,0 +1,16 @@ +SpaceAfter( + BinOps( + [ + ( + @0-1 Num( + "1", + ), + @2-4 Pizza, + ), + ], + @5-8 Dbg, + ), + [ + Newline, + ], +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/pizza_dbg.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/pizza_dbg.expr.roc new file mode 100644 index 0000000000..1addeed52f --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/pizza_dbg.expr.roc @@ -0,0 +1 @@ +1 |> dbg diff --git a/crates/compiler/test_syntax/tests/test_fmt.rs b/crates/compiler/test_syntax/tests/test_fmt.rs index e0f9c8165e..a94d0af359 100644 --- a/crates/compiler/test_syntax/tests/test_fmt.rs +++ b/crates/compiler/test_syntax/tests/test_fmt.rs @@ -5030,10 +5030,10 @@ mod test_fmt { fn single_line_platform() { module_formats_same( "platform \"folkertdev/foo\" \ - requires { Model, Msg } { main : Effect {} } \ + requires { Model, Msg } { main : Task {} [] } \ exposes [] \ packages {} \ - imports [Task.{ Task }] \ + imports [] \ provides [mainForHost]", ); } @@ -5102,7 +5102,7 @@ mod test_fmt { fn single_line_hosted() { module_formats_same(indoc!( r" - hosted Foo exposes [] imports [] generates Bar with []" + hosted Foo exposes [] imports []" )); } @@ -5119,11 +5119,6 @@ mod test_fmt { imports [ Blah, Baz.{ stuff, things }, - ] - generates Bar with [ - map, - after, - loop, ]" )); } diff --git a/crates/compiler/test_syntax/tests/test_snapshots.rs b/crates/compiler/test_syntax/tests/test_snapshots.rs index a41dc389cd..1d3fca8a07 100644 --- a/crates/compiler/test_syntax/tests/test_snapshots.rs +++ b/crates/compiler/test_syntax/tests/test_snapshots.rs @@ -281,6 +281,7 @@ mod test_snapshots { pass/apply_two_args.expr, pass/apply_unary_negation.expr, pass/apply_unary_not.expr, + pass/arg_pattern_as.expr, pass/basic_apply.expr, pass/basic_docs.expr, pass/basic_field.expr, @@ -301,8 +302,9 @@ mod test_snapshots { pass/comment_with_non_ascii.expr, pass/control_characters_in_scalar.expr, pass/crash.expr, + pass/dbg_stmt.expr, + pass/dbg_stmt_multiline.expr, pass/dbg.expr, - pass/dbg_multiline.expr, pass/defs_suffixed_middle_extra_indents.moduledefs, pass/deprecated_interpolated_string.expr, pass/destructure_tag_assignment.expr, @@ -440,6 +442,7 @@ mod test_snapshots { pass/pattern_as_spaces.expr, pass/pattern_with_space_in_parens.expr, // https://github.com/roc-lang/roc/issues/929 pass/pizza_bang.moduledefs, + pass/pizza_dbg.expr, pass/plus_if.expr, pass/plus_when.expr, pass/pos_inf_float.expr, diff --git a/crates/compiler/uitest/tests/recursive_type/fix_recursion_under_alias_issue_4368.txt b/crates/compiler/uitest/tests/recursive_type/fix_recursion_under_alias_issue_4368.txt index 26884b969e..36ab8c74bd 100644 --- a/crates/compiler/uitest/tests/recursive_type/fix_recursion_under_alias_issue_4368.txt +++ b/crates/compiler/uitest/tests/recursive_type/fix_recursion_under_alias_issue_4368.txt @@ -4,10 +4,10 @@ Effect : [ DoIt {} ({} -> Effect), ] -Task := ({} -> Effect) -> Effect +MockTask := ({} -> Effect) -> Effect -doIt : {} -> Task +doIt : {} -> MockTask doIt = \{} -> -#^^^^{-1} {} -[[doIt(0)]]-> Task - @Task \toNext -> +#^^^^{-1} {} -[[doIt(0)]]-> MockTask + @MockTask \toNext -> DoIt {} \{} -> (toNext {}) diff --git a/crates/compiler/uitest/tests/solve/opaque_and_alias_unify.txt b/crates/compiler/uitest/tests/solve/opaque_and_alias_unify.txt index ec2c19b054..de03d2020e 100644 --- a/crates/compiler/uitest/tests/solve/opaque_and_alias_unify.txt +++ b/crates/compiler/uitest/tests/solve/opaque_and_alias_unify.txt @@ -2,8 +2,8 @@ app "test" provides [always] to "./platform" Effect a := {} -> a -Task a err : Effect (Result a err) +MockTask a err : Effect (Result a err) -always : a -> Task a * +always : a -> MockTask a * always = \x -> @Effect (\{} -> Ok x) -#^^^^^^{-1} a -[[always(0)]]-> Task a * +#^^^^^^{-1} a -[[always(0)]]-> MockTask a * diff --git a/crates/compiler/uitest/tests/solve/task_wildcard_wildcard.txt b/crates/compiler/uitest/tests/solve/task_wildcard_wildcard.txt index 38a57c0d70..d9f169c5c4 100644 --- a/crates/compiler/uitest/tests/solve/task_wildcard_wildcard.txt +++ b/crates/compiler/uitest/tests/solve/task_wildcard_wildcard.txt @@ -4,8 +4,8 @@ Effect a := {} -> a eforever : Effect a -> Effect b -Task a err : Effect (Result a err) +OtherTask a err : Effect (Result a err) -tforever : Task val err -> Task * * +tforever : OtherTask val err -> OtherTask * * tforever = \task -> eforever task -#^^^^^^^^{-1} Task val err -[[tforever(0)]]-> Task * * +#^^^^^^^^{-1} OtherTask val err -[[tforever(0)]]-> OtherTask * * diff --git a/crates/glue/src/types.rs b/crates/glue/src/types.rs index d90bd74435..93dbca31f7 100644 --- a/crates/glue/src/types.rs +++ b/crates/glue/src/types.rs @@ -1540,6 +1540,30 @@ fn add_type_help<'a>( type_id } + LayoutRepr::LambdaSet(_lambda_set) if *name == Symbol::TASK_TASK => { + let type_vars = env.subs.get_subs_slice(alias_vars.type_variables()); + debug_assert_eq!(type_vars.len(), 2); + + let ok_var = type_vars[0]; + let ok_layout = env.layout_cache.from_var(env.arena, ok_var, subs).unwrap(); + let ok_id = add_type_help(env, ok_layout, ok_var, None, types); + + let err_var = type_vars[1]; + let err_layout = + env.layout_cache.from_var(env.arena, err_var, subs).unwrap(); + let err_id = add_type_help(env, err_layout, err_var, None, types); + + let type_id = types.add_anonymous( + &env.layout_cache.interner, + RocType::Unsized, + layout, + ); + + types.depends(type_id, ok_id); + types.depends(type_id, err_id); + + type_id + } _ => { unreachable!() } diff --git a/crates/glue/tests/test_glue_cli.rs b/crates/glue/tests/test_glue_cli.rs index 8abf121479..00794274ef 100644 --- a/crates/glue/tests/test_glue_cli.rs +++ b/crates/glue/tests/test_glue_cli.rs @@ -67,7 +67,7 @@ mod glue_cli_run { let test_name_str = stringify!($test_name); // TODO after #5924 is fixed; remove this - let skip_on_linux_surgical_linker = ["closures", "option", "nullable_wrapped", "enumeration", "nested_record", "advanced_recursive_union"]; + let skip_on_linux_surgical_linker = ["closures", "option", "nullable_wrapped", "nullable_unwrapped", "nonnullable_unwrapped", "enumeration", "nested_record", "advanced_recursive_union"]; // Validate linux with the default linker. if !(cfg!(target_os = "linux") && (skip_on_linux_surgical_linker.contains(&test_name_str))) { diff --git a/crates/language_server/src/analysis/tokens.rs b/crates/language_server/src/analysis/tokens.rs index d553da3db1..8a11822af6 100644 --- a/crates/language_server/src/analysis/tokens.rs +++ b/crates/language_server/src/analysis/tokens.rs @@ -305,14 +305,11 @@ impl IterTokens for HostedHeader<'_> { name, exposes, imports, - generates: _, - generates_with, } = self; (name.iter_tokens(arena).into_iter()) .chain(exposes.item.iter_tokens(arena)) .chain(imports.item.iter_tokens(arena)) - .chain(generates_with.item.iter_tokens(arena)) .collect_in(arena) } } @@ -697,7 +694,8 @@ impl IterTokens for Loc> { Expr::Expect(e1, e2) => (e1.iter_tokens(arena).into_iter()) .chain(e2.iter_tokens(arena)) .collect_in(arena), - Expr::Dbg(e1, e2) => (e1.iter_tokens(arena).into_iter()) + Expr::Dbg => onetoken(Token::Keyword, region, arena), + Expr::DbgStmt(e1, e2) => (e1.iter_tokens(arena).into_iter()) .chain(e2.iter_tokens(arena)) .collect_in(arena), Expr::LowLevelDbg(_, e1, e2) => (e1.iter_tokens(arena).into_iter()) diff --git a/crates/reporting/src/error/canonicalize.rs b/crates/reporting/src/error/canonicalize.rs index e6fc13174f..efbd05994f 100644 --- a/crates/reporting/src/error/canonicalize.rs +++ b/crates/reporting/src/error/canonicalize.rs @@ -29,7 +29,6 @@ const WILDCARD_NOT_ALLOWED: &str = "WILDCARD NOT ALLOWED HERE"; const UNDERSCORE_NOT_ALLOWED: &str = "UNDERSCORE NOT ALLOWED HERE"; const UNUSED_ARG: &str = "UNUSED ARGUMENT"; const MISSING_DEFINITION: &str = "MISSING DEFINITION"; -const UNKNOWN_GENERATES_WITH: &str = "UNKNOWN GENERATES FUNCTION"; const DUPLICATE_FIELD_NAME: &str = "DUPLICATE FIELD NAME"; const DUPLICATE_TAG_NAME: &str = "DUPLICATE TAG NAME"; const INVALID_UNICODE: &str = "INVALID UNICODE"; @@ -308,20 +307,6 @@ pub fn can_problem<'b>( title = MISSING_DEFINITION.to_string(); } - Problem::UnknownGeneratesWith(loc_ident) => { - doc = alloc.stack([ - alloc - .reflow("I don't know how to generate the ") - .append(alloc.ident(loc_ident.value)) - .append(alloc.reflow(" function.")), - alloc.region(lines.convert_region(loc_ident.region), severity), - alloc - .reflow("Only specific functions like `after` and `map` can be generated.") - .append(alloc.reflow("Learn more about hosted modules at TODO.")), - ]); - - title = UNKNOWN_GENERATES_WITH.to_string(); - } Problem::UnusedArgument(closure_symbol, is_anonymous, argument_symbol, region) => { let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used."; @@ -1328,6 +1313,34 @@ pub fn can_problem<'b>( ]); title = "OVERAPPLIED CRASH".to_string(); } + Problem::UnappliedDbg { region } => { + doc = alloc.stack([ + alloc.concat([ + alloc.reflow("This "), alloc.keyword("dbg"), alloc.reflow(" doesn't have a value given to it:") + ]), + alloc.region(lines.convert_region(region), severity), + alloc.concat([ + alloc.keyword("dbg"), alloc.reflow(" must be passed a value to print at the exact place it's used. "), + alloc.keyword("dbg"), alloc.reflow(" can't be used as a value that's passed around, like functions can be - it must be applied immediately!"), + ]) + ]); + title = "UNAPPLIED DBG".to_string(); + } + Problem::OverAppliedDbg { region } => { + doc = alloc.stack([ + alloc.concat([ + alloc.reflow("This "), + alloc.keyword("dbg"), + alloc.reflow(" has too many values given to it:"), + ]), + alloc.region(lines.convert_region(region), severity), + alloc.concat([ + alloc.keyword("dbg"), + alloc.reflow(" must be given exactly one value to print."), + ]), + ]); + title = "OVERAPPLIED DBG".to_string(); + } Problem::FileProblem { filename, error } => { let report = to_file_problem_report(alloc, filename, error); doc = report.doc; diff --git a/crates/reporting/src/error/parse.rs b/crates/reporting/src/error/parse.rs index e801da8aab..79e83a7e18 100644 --- a/crates/reporting/src/error/parse.rs +++ b/crates/reporting/src/error/parse.rs @@ -601,19 +601,6 @@ fn to_expr_report<'a>( alloc.region_with_subregion(lines.convert_region(surroundings), region, severity); let doc = match context { - Context::InNode(Node::Dbg, _) => alloc.stack([ - alloc.reflow( - r"I am partway through parsing a dbg statement, but I got stuck here:", - ), - snippet, - alloc.stack([ - alloc.reflow(r"I was expecting a final expression, like so"), - alloc.vcat([ - alloc.parser_suggestion("dbg 42").indent(4), - alloc.parser_suggestion("\"done\"").indent(4), - ]), - ]), - ]), Context::InNode(Node::Expect, _) => alloc.stack([ alloc.reflow( r"I am partway through parsing an expect statement, but I got stuck here:", @@ -3770,98 +3757,6 @@ fn to_header_report<'a>( } EHeader::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos), - EHeader::Generates(_, pos) => { - let surroundings = Region::new(start, *pos); - let region = LineColumnRegion::from_pos(lines.convert_pos(*pos)); - - let doc = alloc.stack([ - alloc.reflow(r"I am partway through parsing a header, but got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), region, severity), - alloc.concat([ - alloc.reflow("I am expecting a type name next, like "), - alloc.parser_suggestion("Effect"), - alloc.reflow(". Type names must start with an uppercase letter."), - ]), - ]); - - Report { - filename, - doc, - title: "WEIRD GENERATED TYPE NAME".to_string(), - severity, - } - } - EHeader::GeneratesWith(generates_with, pos) => { - to_generates_with_report(alloc, lines, filename, generates_with, *pos) - } - } -} - -fn to_generates_with_report<'a>( - alloc: &'a RocDocAllocator<'a>, - lines: &LineInfo, - filename: PathBuf, - parse_problem: &roc_parse::parser::EGeneratesWith, - start: Position, -) -> Report<'a> { - use roc_parse::parser::EGeneratesWith; - - let severity = Severity::RuntimeError; - - match *parse_problem { - EGeneratesWith::ListEnd(pos) | // TODO: give this its own error message - EGeneratesWith::Identifier(pos) => { - let surroundings = Region::new(start, pos); - let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - - let doc = alloc.stack([ - alloc - .reflow(r"I am partway through parsing a provides list, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), region, severity), - alloc.concat([alloc.reflow( - "I was expecting a type name, value name or function name next, like", - )]), - alloc - .parser_suggestion("provides [Animal, default, tame]") - .indent(4), - ]); - - Report { - filename, - doc, - title: "WEIRD GENERATES".to_string(), - severity, - } - } - - EGeneratesWith::With(pos) => { - let surroundings = Region::new(start, pos); - let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - - let doc = alloc.stack([ - alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), - alloc.region_with_subregion(lines.convert_region(surroundings), region, severity), - alloc.concat([ - alloc.reflow("I am expecting the "), - alloc.keyword("with"), - alloc.reflow(" keyword next, like"), - ]), - alloc - .parser_suggestion("with [after, map]") - .indent(4), - ]); - - Report { - filename, - doc, - title: "WEIRD GENERATES".to_string(), - severity, - } - } - - EGeneratesWith::Space(error, pos) => to_space_report(alloc, lines, filename, &error, pos), - - _ => todo!("unhandled parse error {:?}", parse_problem), } } @@ -4280,7 +4175,7 @@ fn to_requires_report<'a>( alloc.reflow(" definition looks like"), ]), alloc - .parser_suggestion("requires {model=>Model, msg=>Msg} {main : Effect {}}") + .parser_suggestion("requires {model=>Model, msg=>Msg} {main : Task {} []}") .indent(4), ]); @@ -4309,7 +4204,7 @@ fn to_requires_report<'a>( alloc.reflow(" definition looks like"), ]), alloc - .parser_suggestion("requires { Model, Msg } {main : Effect {}}") + .parser_suggestion("requires { Model, Msg } {main : Task {} []}") .indent(4), ]); diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index 1c35eccb90..9256289ab9 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -1796,6 +1796,9 @@ fn format_category<'b>( let t = if capitalize_start { "T" } else { "t" }; match category { + Lookup(name) if name.is_generated(alloc.interns) => { + (text!(alloc, "{}his value", t), alloc.text(" is a:")) + } Lookup(name) => ( alloc.concat([ text!(alloc, "{}his ", t), @@ -1804,7 +1807,6 @@ fn format_category<'b>( ]), alloc.text(" is a:"), ), - If => ( alloc.concat([ text!(alloc, "{}his ", t), diff --git a/examples/cli/effects-platform/Effect.roc b/examples/cli/effects-platform/Effect.roc deleted file mode 100644 index a951f756f9..0000000000 --- a/examples/cli/effects-platform/Effect.roc +++ /dev/null @@ -1,8 +0,0 @@ -hosted Effect - exposes [Effect, after, map, always, forever, putLine, getLine] - imports [] - generates Effect with [after, map, always, forever] - -putLine : Str -> Effect {} - -getLine : Effect Str diff --git a/examples/cli/effects-platform/PlatformTasks.roc b/examples/cli/effects-platform/PlatformTasks.roc new file mode 100644 index 0000000000..a5e70d31e9 --- /dev/null +++ b/examples/cli/effects-platform/PlatformTasks.roc @@ -0,0 +1,7 @@ +hosted PlatformTasks + exposes [putLine, getLine] + imports [] + +putLine : Str -> Task {} * + +getLine : Task Str * diff --git a/examples/cli/effects-platform/main.roc b/examples/cli/effects-platform/main.roc index 10da2898cb..109d4150a2 100644 --- a/examples/cli/effects-platform/main.roc +++ b/examples/cli/effects-platform/main.roc @@ -1,9 +1,9 @@ platform "effects" - requires {} { main : Effect.Effect {} } + requires {} { main : Task {} [] } exposes [] packages {} - imports [Effect] + imports [] provides [mainForHost] -mainForHost : Effect.Effect {} +mainForHost : Task {} [] mainForHost = main diff --git a/examples/cli/effects.roc b/examples/cli/effects.roc index 37685cb430..4cadf7b8d8 100644 --- a/examples/cli/effects.roc +++ b/examples/cli/effects.roc @@ -1,16 +1,11 @@ app [main] { pf: platform "effects-platform/main.roc" } -import pf.Effect +import pf.PlatformTasks -main : Effect.Effect {} +main : Task {} [] main = - Effect.after - (Effect.getLine) - \line -> - Effect.after - (Effect.putLine "You entered: $(line)") - \{} -> - Effect.after - (Effect.putLine "It is known") - \{} -> - Effect.always {} + line = PlatformTasks.getLine! + PlatformTasks.putLine! "You entered: $(line)" + PlatformTasks.putLine! "It is known" + + Task.ok {} diff --git a/examples/cli/false-interpreter/Context.roc b/examples/cli/false-interpreter/Context.roc index 1b97242f69..b1eec7541b 100644 --- a/examples/cli/false-interpreter/Context.roc +++ b/examples/cli/false-interpreter/Context.roc @@ -1,7 +1,6 @@ module [Context, Data, with, getChar, Option, pushStack, popStack, toStr, inWhileScope] import pf.File -import pf.Task exposing [Task] import Variable exposing [Variable] Option a : [Some a, None] @@ -73,16 +72,16 @@ getChar = \ctx -> when List.last ctx.scopes is Ok scope -> (T val newScope) = getCharScope! scope - Task.succeed (T val { ctx & scopes: List.set ctx.scopes (List.len ctx.scopes - 1) newScope }) + Task.ok (T val { ctx & scopes: List.set ctx.scopes (List.len ctx.scopes - 1) newScope }) Err ListWasEmpty -> - Task.fail NoScope + Task.err NoScope getCharScope : Scope -> Task [T U8 Scope] [EndOfData, NoScope] getCharScope = \scope -> when List.get scope.buf scope.index is Ok val -> - Task.succeed (T val { scope & index: scope.index + 1 }) + Task.ok (T val { scope & index: scope.index + 1 }) Err OutOfBounds -> when scope.data is @@ -91,13 +90,13 @@ getCharScope = \scope -> when List.first bytes is Ok val -> # This starts at 1 because the first character is already being returned. - Task.succeed (T val { scope & buf: bytes, index: 1 }) + Task.ok (T val { scope & buf: bytes, index: 1 }) Err ListWasEmpty -> - Task.fail EndOfData + Task.err EndOfData None -> - Task.fail EndOfData + Task.err EndOfData inWhileScope : Context -> Bool inWhileScope = \ctx -> diff --git a/examples/cli/false-interpreter/False.roc b/examples/cli/false-interpreter/False.roc index 96fc3bb625..9114c51886 100644 --- a/examples/cli/false-interpreter/False.roc +++ b/examples/cli/false-interpreter/False.roc @@ -1,6 +1,5 @@ app [main] { pf: platform "platform/main.roc" } -import pf.Task exposing [Task] import pf.Stdout import pf.Stdin import Context exposing [Context] @@ -24,7 +23,7 @@ InterpreterErrors : [BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, Invali main : Str -> Task {} [] main = \filename -> interpretFile filename - |> Task.onFail \StringErr e -> Stdout.line "Ran into problem:\n$(e)\n" + |> Task.onErr \StringErr e -> Stdout.line "Ran into problem:\n$(e)\n" interpretFile : Str -> Task {} [StringErr Str] interpretFile = \filename -> @@ -32,43 +31,43 @@ interpretFile = \filename -> result = interpretCtx ctx |> Task.result! when result is Ok _ -> - Task.succeed {} + Task.ok {} Err BadUtf8 -> - Task.fail (StringErr "Failed to convert string from Utf8 bytes") + Task.err (StringErr "Failed to convert string from Utf8 bytes") Err DivByZero -> - Task.fail (StringErr "Division by zero") + Task.err (StringErr "Division by zero") Err EmptyStack -> - Task.fail (StringErr "Tried to pop a value off of the stack when it was empty") + Task.err (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)") + Task.err (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)") + Task.err (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") + Task.err (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") + Task.err (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") + Task.err (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") + Task.err (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") + Task.err (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") + Task.err (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") + Task.err (StringErr "Hit end of data while still parsing something") isDigit : U8 -> Bool isDigit = \char -> @@ -107,26 +106,26 @@ interpretCtxLoop = \ctx -> if n == 0 then newScope = { scope & whileInfo: None } - Task.succeed (Step { popCtx & scopes: List.set ctx.scopes last newScope }) + Task.ok (Step { popCtx & scopes: List.set ctx.scopes last newScope }) else newScope = { scope & whileInfo: Some { state: InBody, body, cond } } - Task.succeed (Step { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } }) + Task.ok (Step { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } }) Err e -> - Task.fail e + Task.err e Some { state: InBody, body, cond } -> # Just rand the body. Run the condition again. newScope = { scope & whileInfo: Some { state: InCond, body, cond } } - Task.succeed (Step { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } }) + Task.ok (Step { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } }) None -> - Task.fail NoScope + Task.err NoScope Err OutOfBounds -> - Task.fail NoScope + Task.err NoScope Executing -> # Stdout.line! (Context.toStr ctx) @@ -134,10 +133,10 @@ interpretCtxLoop = \ctx -> when result is Ok (T val newCtx) -> execCtx = stepExecCtx! newCtx val - Task.succeed (Step execCtx) + Task.ok (Step execCtx) Err NoScope -> - Task.fail NoScope + Task.err NoScope Err EndOfData -> # Computation complete for this scope. @@ -146,9 +145,9 @@ interpretCtxLoop = \ctx -> # If no scopes left, all execution complete. if List.isEmpty dropCtx.scopes then - Task.succeed (Done dropCtx) + Task.ok (Done dropCtx) else - Task.succeed (Step dropCtx) + Task.ok (Step dropCtx) InComment -> result = Context.getChar ctx |> Task.result! @@ -156,15 +155,15 @@ interpretCtxLoop = \ctx -> Ok (T val newCtx) -> if val == 0x7D then # `}` end of comment - Task.succeed (Step { newCtx & state: Executing }) + Task.ok (Step { newCtx & state: Executing }) else - Task.succeed (Step { newCtx & state: InComment }) + Task.ok (Step { newCtx & state: InComment }) Err NoScope -> - Task.fail NoScope + Task.err NoScope Err EndOfData -> - Task.fail UnexpectedEndOfData + Task.err UnexpectedEndOfData InNumber accum -> result = Context.getChar ctx |> Task.result! @@ -177,19 +176,19 @@ interpretCtxLoop = \ctx -> # so this is make i64 mul by 10 then convert back to i32. nextAccum = (10 * Num.intCast accum) + Num.intCast (val - 0x30) - Task.succeed (Step { newCtx & state: InNumber (Num.intCast nextAccum) }) + Task.ok (Step { newCtx & state: InNumber (Num.intCast nextAccum) }) else # outside of number now, this needs to be executed. pushCtx = Context.pushStack newCtx (Number accum) execCtx = stepExecCtx! { pushCtx & state: Executing } val - Task.succeed (Step execCtx) + Task.ok (Step execCtx) Err NoScope -> - Task.fail NoScope + Task.err NoScope Err EndOfData -> - Task.fail UnexpectedEndOfData + Task.err UnexpectedEndOfData InString bytes -> result = Context.getChar ctx |> Task.result! @@ -200,18 +199,18 @@ interpretCtxLoop = \ctx -> when Str.fromUtf8 bytes is Ok str -> Stdout.raw! str - Task.succeed (Step { newCtx & state: Executing }) + Task.ok (Step { newCtx & state: Executing }) Err _ -> - Task.fail BadUtf8 + Task.err BadUtf8 else - Task.succeed (Step { newCtx & state: InString (List.append bytes val) }) + Task.ok (Step { newCtx & state: InString (List.append bytes val) }) Err NoScope -> - Task.fail NoScope + Task.err NoScope Err EndOfData -> - Task.fail UnexpectedEndOfData + Task.err UnexpectedEndOfData InLambda depth bytes -> result = Context.getChar ctx |> Task.result! @@ -219,23 +218,23 @@ interpretCtxLoop = \ctx -> Ok (T val newCtx) -> if val == 0x5B then # start of a nested lambda `[` - Task.succeed (Step { newCtx & state: InLambda (depth + 1) (List.append bytes val) }) + Task.ok (Step { newCtx & state: InLambda (depth + 1) (List.append bytes val) }) else if val == 0x5D then # `]` end of current lambda if depth == 0 then # end of all lambdas - Task.succeed (Step (Context.pushStack { newCtx & state: Executing } (Lambda bytes))) + Task.ok (Step (Context.pushStack { newCtx & state: Executing } (Lambda bytes))) else # end of nested lambda - Task.succeed (Step { newCtx & state: InLambda (depth - 1) (List.append bytes val) }) + Task.ok (Step { newCtx & state: InLambda (depth - 1) (List.append bytes val) }) else - Task.succeed (Step { newCtx & state: InLambda depth (List.append bytes val) }) + Task.ok (Step { newCtx & state: InLambda depth (List.append bytes val) }) Err NoScope -> - Task.fail NoScope + Task.err NoScope Err EndOfData -> - Task.fail UnexpectedEndOfData + Task.err UnexpectedEndOfData InSpecialChar -> result = Context.getChar { ctx & state: Executing } |> Task.result! @@ -254,35 +253,35 @@ interpretCtxLoop = \ctx -> Err OutOfBounds when result2 is - Ok a -> Task.succeed (Step a) - Err e -> Task.fail e + Ok a -> Task.ok (Step a) + Err e -> Task.err e Ok (T 0x9F newCtx) -> # This is supposed to flush io buffers. We don't buffer, so it does nothing - Task.succeed (Step newCtx) + Task.ok (Step newCtx) Ok (T x _) -> data = Num.toStr (Num.intCast x) - Task.fail (InvalidChar data) + Task.err (InvalidChar data) Err NoScope -> - Task.fail NoScope + Task.err NoScope Err EndOfData -> - Task.fail UnexpectedEndOfData + Task.err UnexpectedEndOfData LoadChar -> 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)))) + Task.ok (Step (Context.pushStack newCtx (Number (Num.intCast x)))) Err NoScope -> - Task.fail NoScope + Task.err NoScope Err EndOfData -> - Task.fail UnexpectedEndOfData + Task.err UnexpectedEndOfData # If it weren't for reading stdin or writing to stdout, this could return a result. stepExecCtx : Context, U8 -> Task Context InterpreterErrors @@ -333,15 +332,15 @@ stepExecCtx = \ctx, char -> # Switching this to List.last and changing the error to ListWasEmpty leads to a compiler bug. # Complains about the types eq not matching. when List.get ctx.stack (List.len ctx.stack - 1) is - Ok dupItem -> Task.succeed (Context.pushStack ctx dupItem) - Err OutOfBounds -> Task.fail EmptyStack + Ok dupItem -> Task.ok (Context.pushStack ctx dupItem) + Err OutOfBounds -> Task.err EmptyStack 0x25 -> # `%` drop when Context.popStack ctx is # Dropping with an empty stack, all results here are fine - Ok (T popCtx _) -> Task.succeed popCtx - Err _ -> Task.succeed ctx + Ok (T popCtx _) -> Task.ok popCtx + Err _ -> Task.ok ctx 0x5C -> # `\` swap @@ -352,11 +351,11 @@ stepExecCtx = \ctx, char -> when result2 is Ok a -> - Task.succeed a + Task.ok a # Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack Err EmptyStack -> - Task.fail EmptyStack + Task.err EmptyStack 0x40 -> # `@` rot @@ -368,17 +367,17 @@ stepExecCtx = \ctx, char -> when result2 is Ok a -> - Task.succeed a + Task.ok a # Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack Err EmptyStack -> - Task.fail EmptyStack + Task.err EmptyStack 0xC3 -> # `ΓΈ` pick or `ß` flush # these are actually 2 bytes, 0xC3 0xB8 or 0xC3 0x9F # requires special parsing - Task.succeed { ctx & state: InSpecialChar } + Task.ok { ctx & state: InSpecialChar } 0x4F -> # `O` also treat this as pick for easier script writing @@ -399,11 +398,11 @@ stepExecCtx = \ctx, char -> 0x42 -> # `B` also treat this as flush for easier script writing # This is supposed to flush io buffers. We don't buffer, so it does nothing - Task.succeed ctx + Task.ok ctx 0x27 -> # `'` load next char - Task.succeed { ctx & state: LoadChar } + Task.ok { ctx & state: LoadChar } 0x2B -> # `+` add @@ -473,32 +472,32 @@ stepExecCtx = \ctx, char -> when Str.fromUtf8 [Num.intCast num] is Ok str -> Stdout.raw! str - Task.succeed popCtx + Task.ok popCtx Err _ -> - Task.fail BadUtf8 + Task.err BadUtf8 Err e -> - Task.fail e + Task.err e 0x2E -> # `.` write int when popNumber ctx is Ok (T popCtx num) -> Stdout.raw! (Num.toStr (Num.intCast num)) - Task.succeed popCtx + Task.ok popCtx Err e -> - Task.fail e + Task.err e 0x5E -> # `^` read char as int - in = Stdin.char! + in = Stdin.char! {} if in == 255 then # max char sent on EOF. Change to -1 - Task.succeed (Context.pushStack ctx (Number -1)) + Task.ok (Context.pushStack ctx (Number -1)) else - Task.succeed (Context.pushStack ctx (Number (Num.intCast in))) + Task.ok (Context.pushStack ctx (Number (Num.intCast in))) 0x3A -> # `:` store to variable @@ -521,33 +520,33 @@ stepExecCtx = \ctx, char -> 0x22 -> # `"` string start - Task.succeed { ctx & state: InString [] } + Task.ok { ctx & state: InString [] } 0x5B -> # `"` string start - Task.succeed { ctx & state: InLambda 0 [] } + Task.ok { ctx & state: InLambda 0 [] } 0x7B -> # `{` comment start - Task.succeed { ctx & state: InComment } + Task.ok { ctx & state: InComment } x if isDigit x -> # number start - Task.succeed { ctx & state: InNumber (Num.intCast (x - 0x30)) } + Task.ok { ctx & state: InNumber (Num.intCast (x - 0x30)) } x if isWhitespace x -> - Task.succeed ctx + Task.ok ctx x -> when Variable.fromUtf8 x is # letters are variable names Ok var -> - Task.succeed (Context.pushStack ctx (Var var)) + Task.ok (Context.pushStack ctx (Var var)) Err _ -> data = Num.toStr (Num.intCast x) - Task.fail (InvalidChar data) + Task.err (InvalidChar data) unaryOp : Context, (I32 -> I32) -> Result Context InterpreterErrors unaryOp = \ctx, op -> diff --git a/examples/cli/false-interpreter/platform/Effect.roc b/examples/cli/false-interpreter/platform/Effect.roc deleted file mode 100644 index 532a01be43..0000000000 --- a/examples/cli/false-interpreter/platform/Effect.roc +++ /dev/null @@ -1,22 +0,0 @@ -hosted Effect - exposes [Effect, after, map, always, forever, loop, openFile, closeFile, withFileOpen, getFileLine, getFileBytes, putLine, putRaw, getLine, getChar] - imports [] - generates Effect with [after, map, always, forever, loop] - -openFile : Str -> Effect U64 - -closeFile : U64 -> Effect {} - -withFileOpen : Str, (U64 -> Effect (Result ok err)) -> Effect {} - -getFileLine : U64 -> Effect Str - -getFileBytes : U64 -> Effect (List U8) - -putLine : Str -> Effect {} - -putRaw : Str -> Effect {} - -getLine : Effect Str - -getChar : Effect U8 diff --git a/examples/cli/false-interpreter/platform/File.roc b/examples/cli/false-interpreter/platform/File.roc index d9496bc455..f17ecce8e7 100644 --- a/examples/cli/false-interpreter/platform/File.roc +++ b/examples/cli/false-interpreter/platform/File.roc @@ -1,24 +1,29 @@ -module [line, Handle, withOpen, chunk] +module [line, withOpen, chunk, Handle] -import pf.Effect -import Task exposing [Task] +import pf.PlatformTasks Handle := U64 -line : Handle -> Task.Task Str * -line = \@Handle handle -> Effect.after (Effect.getFileLine handle) Task.succeed +line : Handle -> Task Str * +line = \@Handle handle -> + PlatformTasks.getFileLine handle + |> Task.mapErr \_ -> crash "unreachable File.line" -chunk : Handle -> Task.Task (List U8) * -chunk = \@Handle handle -> Effect.after (Effect.getFileBytes handle) Task.succeed +chunk : Handle -> Task (List U8) * +chunk = \@Handle handle -> + PlatformTasks.getFileBytes handle + |> Task.mapErr \_ -> crash "unreachable File.chunk" -open : Str -> Task.Task Handle * +open : Str -> Task Handle * open = \path -> - Effect.openFile path - |> Effect.map (\id -> @Handle id) - |> Effect.after Task.succeed + PlatformTasks.openFile path + |> Task.mapErr \_ -> crash "unreachable File.open" + |> Task.map @Handle close : Handle -> Task.Task {} * -close = \@Handle handle -> Effect.after (Effect.closeFile handle) Task.succeed +close = \@Handle handle -> + PlatformTasks.closeFile handle + |> Task.mapErr \_ -> crash "unreachable File.close" withOpen : Str, (Handle -> Task {} a) -> Task {} a withOpen = \path, callback -> diff --git a/examples/cli/false-interpreter/platform/PlatformTasks.roc b/examples/cli/false-interpreter/platform/PlatformTasks.roc new file mode 100644 index 0000000000..ada0ab7fb9 --- /dev/null +++ b/examples/cli/false-interpreter/platform/PlatformTasks.roc @@ -0,0 +1,21 @@ +hosted PlatformTasks + exposes [openFile, closeFile, withFileOpen, getFileLine, getFileBytes, putLine, putRaw, getLine, getChar] + imports [] + +openFile : Str -> Task U64 {} + +closeFile : U64 -> Task {} {} + +withFileOpen : Str, (U64 -> Task ok err) -> Task {} {} + +getFileLine : U64 -> Task Str {} + +getFileBytes : U64 -> Task (List U8) {} + +putLine : Str -> Task {} {} + +putRaw : Str -> Task {} {} + +getLine : Task Str {} + +getChar : Task U8 {} diff --git a/examples/cli/false-interpreter/platform/Stdin.roc b/examples/cli/false-interpreter/platform/Stdin.roc index 0f03c1f946..c139724399 100644 --- a/examples/cli/false-interpreter/platform/Stdin.roc +++ b/examples/cli/false-interpreter/platform/Stdin.roc @@ -1,9 +1,16 @@ -module [char] +module [ + line, + char, +] -import pf.Effect -import Task +import pf.PlatformTasks -# line : Task.Task Str * -# line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice -char : Task.Task U8 * -char = Effect.after Effect.getChar Task.succeed # TODO FIXME Effect.getLine should suffice +line : {} -> Task Str * +line = \{} -> + PlatformTasks.getLine + |> Task.mapErr \_ -> crash "unreachable Stdin.line" + +char : {} -> Task U8 * +char = \{} -> + PlatformTasks.getChar + |> Task.mapErr \_ -> crash "unreachable Stdin.char" diff --git a/examples/cli/false-interpreter/platform/Stdout.roc b/examples/cli/false-interpreter/platform/Stdout.roc index 54b45834f5..723dcd606e 100644 --- a/examples/cli/false-interpreter/platform/Stdout.roc +++ b/examples/cli/false-interpreter/platform/Stdout.roc @@ -1,10 +1,13 @@ module [line, raw] -import pf.Effect -import Task exposing [Task] +import pf.PlatformTasks line : Str -> Task {} * -line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {}) +line = \text -> + PlatformTasks.putLine text + |> Task.mapErr \_ -> crash "unreachable Stdout.line" raw : Str -> Task {} * -raw = \str -> Effect.map (Effect.putRaw str) (\_ -> Ok {}) +raw = \text -> + PlatformTasks.putRaw text + |> Task.mapErr \_ -> crash "unreachable Stdout.raw" diff --git a/examples/cli/false-interpreter/platform/Task.roc b/examples/cli/false-interpreter/platform/Task.roc deleted file mode 100644 index 033ce78e41..0000000000 --- a/examples/cli/false-interpreter/platform/Task.roc +++ /dev/null @@ -1,85 +0,0 @@ -module [ - Task, - succeed, - fail, - await, - map, - onFail, - attempt, - result, - fromResult, - loop, -] - -import pf.Effect - -Task ok err : Effect.Effect (Result ok err) - -loop : state, (state -> Task [Step state, Done done] err) -> Task done err -loop = \state, step -> - looper = \current -> - step current - |> Effect.map - \res -> - when res is - Ok (Step newState) -> Step newState - Ok (Done res2) -> Done (Ok res2) - Err e -> Done (Err e) - - Effect.loop state looper - -succeed : val -> Task val * -succeed = \val -> - Effect.always (Ok val) - -fail : err -> Task * err -fail = \val -> - Effect.always (Err val) - -fromResult : Result a e -> Task a e -fromResult = \res -> - when res is - Ok a -> succeed a - Err e -> fail e - -attempt : Task a b, (Result a b -> Task c d) -> Task c d -attempt = \effect, transform -> - Effect.after - effect - \res -> - when res is - Ok ok -> transform (Ok ok) - Err err -> transform (Err 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 - Err err -> Task.fail err - -onFail : Task ok a, (a -> Task ok b) -> Task ok b -onFail = \effect, transform -> - Effect.after - effect - \res -> - when res is - Ok a -> Task.succeed a - Err err -> transform err - -map : Task a err, (a -> b) -> Task b err -map = \effect, transform -> - Effect.after - effect - \res -> - when res 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 - \res -> Task.succeed res diff --git a/examples/cli/false-interpreter/platform/main.roc b/examples/cli/false-interpreter/platform/main.roc index cf97352ce4..1fe4a8baee 100644 --- a/examples/cli/false-interpreter/platform/main.roc +++ b/examples/cli/false-interpreter/platform/main.roc @@ -2,7 +2,7 @@ platform "false-interpreter" requires {} { main : Str -> Task {} [] } exposes [] packages {} - imports [Task.{ Task }] + imports [] provides [mainForHost] mainForHost : Str -> Task {} [] diff --git a/examples/cli/false-interpreter/platform/src/lib.rs b/examples/cli/false-interpreter/platform/src/lib.rs index b889ee3678..33157483ac 100644 --- a/examples/cli/false-interpreter/platform/src/lib.rs +++ b/examples/cli/false-interpreter/platform/src/lib.rs @@ -3,12 +3,21 @@ use core::ffi::c_void; use core::mem::MaybeUninit; use libc; -use roc_std::{RocList, RocStr}; +use roc_std::{RocList, RocResult, RocStr}; +use std::collections::HashMap; use std::env; -use std::ffi::CStr; use std::fs::File; use std::io::{BufRead, BufReader, Read, Write}; -use std::os::raw::c_char; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Mutex, OnceLock}; + +static FILE_ID: AtomicU64 = AtomicU64::new(0); + +fn file_handles() -> &'static Mutex>> { + static FILE_HANDLES: OnceLock>>> = OnceLock::new(); + + FILE_HANDLES.get_or_init(|| Mutex::new(HashMap::default())) +} extern "C" { #[link_name = "roc__mainForHost_1_exposed_generic"] @@ -146,81 +155,89 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { } #[no_mangle] -pub extern "C" fn roc_fx_getLine() -> RocStr { +pub extern "C" fn roc_fx_getLine() -> RocResult { let stdin = std::io::stdin(); let line1 = stdin.lock().lines().next().unwrap().unwrap(); - RocStr::from(line1.as_str()) + RocResult::ok(RocStr::from(line1.as_str())) } #[no_mangle] -pub extern "C" fn roc_fx_getChar() -> u8 { +pub extern "C" fn roc_fx_getChar() -> RocResult { let mut buffer = [0]; if let Err(ioerr) = std::io::stdin().lock().read_exact(&mut buffer[..]) { if ioerr.kind() == std::io::ErrorKind::UnexpectedEof { - u8::MAX + RocResult::ok(u8::MAX) } else { panic!("Got an unexpected error while reading char from stdin"); } } else { - buffer[0] + RocResult::ok(buffer[0]) } } #[no_mangle] -pub extern "C" fn roc_fx_putLine(line: &RocStr) { +pub extern "C" fn roc_fx_putLine(line: &RocStr) -> RocResult<(), ()> { let string = line.as_str(); println!("{}", string); let _ = std::io::stdout().lock().flush(); + + RocResult::ok(()) } #[no_mangle] -pub extern "C" fn roc_fx_putRaw(line: &RocStr) { +pub extern "C" fn roc_fx_putRaw(line: &RocStr) -> RocResult<(), ()> { let string = line.as_str(); print!("{}", string); let _ = std::io::stdout().lock().flush(); + + RocResult::ok(()) } #[no_mangle] -pub extern "C" fn roc_fx_getFileLine(br_ptr: *mut BufReader) -> RocStr { - let br = unsafe { &mut *br_ptr }; +pub extern "C" fn roc_fx_getFileLine(br_id: u64) -> RocResult { + let mut br_map = file_handles().lock().unwrap(); + let br = br_map.get_mut(&br_id).unwrap(); let mut line1 = String::default(); br.read_line(&mut line1) .expect("Failed to read line from file"); - RocStr::from(line1.as_str()) + RocResult::ok(RocStr::from(line1.as_str())) } #[no_mangle] -pub extern "C" fn roc_fx_getFileBytes(br_ptr: *mut BufReader) -> RocList { - let br = unsafe { &mut *br_ptr }; +pub extern "C" fn roc_fx_getFileBytes(br_id: u64) -> RocResult, ()> { + let mut br_map = file_handles().lock().unwrap(); + let br = br_map.get_mut(&br_id).unwrap(); let mut buffer = [0; 0x10 /* This is intentionally small to ensure correct implementation */]; let count = br .read(&mut buffer[..]) .expect("Failed to read bytes from file"); - RocList::from_slice(&buffer[..count]) + RocResult::ok(RocList::from_slice(&buffer[..count])) } #[no_mangle] -pub extern "C" fn roc_fx_closeFile(br_ptr: *mut BufReader) { - unsafe { - let boxed = Box::from_raw(br_ptr); - drop(boxed) - } +pub extern "C" fn roc_fx_closeFile(br_id: u64) -> RocResult<(), ()> { + file_handles().lock().unwrap().remove(&br_id); + + RocResult::ok(()) } #[no_mangle] -pub extern "C" fn roc_fx_openFile(name: &RocStr) -> *mut BufReader { +pub extern "C" fn roc_fx_openFile(name: &RocStr) -> RocResult { let string = name.as_str(); match File::open(string) { Ok(f) => { let br = BufReader::new(f); + let br_id = FILE_ID.fetch_add(1, Ordering::SeqCst); - Box::into_raw(Box::new(br)) + file_handles().lock().unwrap().insert(br_id, br); + + RocResult::ok(br_id) } Err(_) => { panic!("unable to open file {:?}", name) @@ -229,7 +246,7 @@ pub extern "C" fn roc_fx_openFile(name: &RocStr) -> *mut BufReader { } #[no_mangle] -pub extern "C" fn roc_fx_withFileOpen(_name: &RocStr, _buffer: *const u8) { +pub extern "C" fn roc_fx_withFileOpen(_name: &RocStr, _buffer: *const u8) -> RocResult<(), ()> { // TODO: figure out accepting a closure in an fx and passing data to it. // let f = File::open(name.as_str()).expect("Unable to open file"); // let mut br = BufReader::new(f); @@ -238,4 +255,6 @@ pub extern "C" fn roc_fx_withFileOpen(_name: &RocStr, _buffer: *const u8) { // let closure_data_ptr = buffer.offset(8); // call_the_closure(closure_data_ptr); // } + + RocResult::ok(()) } diff --git a/examples/helloWorld.roc b/examples/helloWorld.roc index a53b85353e..622f3ec546 100644 --- a/examples/helloWorld.roc +++ b/examples/helloWorld.roc @@ -1,7 +1,6 @@ -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout -import pf.Task main = Stdout.line! "Hello, World!" diff --git a/examples/inspect-logging.roc b/examples/inspect-logging.roc index 8e4d81c1bf..0b86c1028f 100644 --- a/examples/inspect-logging.roc +++ b/examples/inspect-logging.roc @@ -1,10 +1,9 @@ # # Shows how Roc values can be logged # -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout -import pf.Task import Community main = diff --git a/examples/virtual-dom-wip/platform/Html/Internal/Client.roc b/examples/virtual-dom-wip/platform/Html/Internal/Client.roc index 46ca2549c4..36b566a6f9 100644 --- a/examples/virtual-dom-wip/platform/Html/Internal/Client.roc +++ b/examples/virtual-dom-wip/platform/Html/Internal/Client.roc @@ -4,8 +4,7 @@ module [ dispatchEvent, ] -import Effect exposing [ - Effect, +import PlatformTasks exposing [ NodeId, HandlerId, TagName, @@ -81,19 +80,20 @@ DiffState state : { rendered : RenderedTree state, patches : List Patch } # ------------------------------- # INITIALISATION # ------------------------------- -initClientApp : List U8, App state initData -> Effect (PlatformState state initData) where initData implements Decoding +initClientApp : List U8, App state initData -> Task (PlatformState state initData) * where initData implements Decoding initClientApp = \json, app -> # Initialise the Roc representation of the rendered DOM, and calculate patches (for event listeners) { state, rendered, patches } = initClientAppHelp json app # Call out to JS to patch the DOM, attaching the event listeners - Effect.after (applyPatches patches) \_ -> - Effect.always { - app, - state, - rendered, - } + applyPatches! patches + + Task.ok { + 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 @@ -161,30 +161,28 @@ indexNodes = \{ nodes, siblingIds }, unrendered -> # ------------------------------- # Patches # ------------------------------- -applyPatch : Patch -> Effect {} +applyPatch : Patch -> Task {} * applyPatch = \patch -> when patch is - CreateElement nodeId tagName -> Effect.createElement nodeId tagName - CreateTextNode nodeId content -> Effect.createTextNode nodeId content - UpdateTextNode nodeId content -> Effect.updateTextNode nodeId content - AppendChild parentId childId -> Effect.appendChild parentId childId - RemoveNode id -> Effect.removeNode id - ReplaceNode oldId newId -> Effect.replaceNode oldId newId - SetAttribute nodeId attrName value -> Effect.setAttribute nodeId attrName value - RemoveAttribute nodeId attrName -> Effect.removeAttribute nodeId attrName - SetProperty nodeId propName json -> Effect.setProperty nodeId propName json - RemoveProperty nodeId propName -> Effect.removeProperty nodeId propName - SetStyle nodeId key value -> Effect.setStyle nodeId key value - SetListener nodeId eventType accessorsJson handlerId -> Effect.setListener nodeId eventType accessorsJson handlerId - RemoveListener nodeId handlerId -> Effect.removeListener nodeId handlerId + CreateElement nodeId tagName -> PlatformTasks.createElement nodeId tagName + CreateTextNode nodeId content -> PlatformTasks.createTextNode nodeId content + UpdateTextNode nodeId content -> PlatformTasks.updateTextNode nodeId content + AppendChild parentId childId -> PlatformTasks.appendChild parentId childId + RemoveNode id -> PlatformTasks.removeNode id + ReplaceNode oldId newId -> PlatformTasks.replaceNode oldId newId + SetAttribute nodeId attrName value -> PlatformTasks.setAttribute nodeId attrName value + RemoveAttribute nodeId attrName -> PlatformTasks.removeAttribute nodeId attrName + SetProperty nodeId propName json -> PlatformTasks.setProperty nodeId propName json + RemoveProperty nodeId propName -> PlatformTasks.removeProperty nodeId propName + SetStyle nodeId key value -> PlatformTasks.setStyle nodeId key value + SetListener nodeId eventType accessorsJson handlerId -> PlatformTasks.setListener nodeId eventType accessorsJson handlerId + RemoveListener nodeId handlerId -> PlatformTasks.removeListener nodeId handlerId -walkPatches : Effect {}, Patch -> Effect {} -walkPatches = \previousEffects, patch -> - Effect.after previousEffects \{} -> applyPatch patch - -applyPatches : List Patch -> Effect {} +applyPatches : List Patch -> Task {} * applyPatches = \patches -> - List.walk patches (Effect.always {}) walkPatches + List.walk patches (Task.ok {}) \previousEffects, patch -> + previousEffects! + applyPatch patch # ------------------------------- # EVENT HANDLING @@ -196,7 +194,7 @@ JsEventResult state initData : { } ## Dispatch a JavaScript event to a Roc handler, given the handler ID and some JSON event data. -dispatchEvent : PlatformState state initData, List (List U8), HandlerId -> Effect (JsEventResult state initData) where initData implements Decoding +dispatchEvent : PlatformState state initData, List (List U8), HandlerId -> Task (JsEventResult state initData) * where initData implements Decoding dispatchEvent = \platformState, eventData, handlerId -> { app, state, rendered } = platformState @@ -221,19 +219,19 @@ dispatchEvent = \platformState, eventData, handlerId -> { rendered: newRendered, patches } = diff { rendered, patches: [] } newViewUnrendered - Effect.after (applyPatches patches) \_ -> - Effect.always { - platformState: { - app, - state: newState, - rendered: newRendered, - }, - stopPropagation, - preventDefault, - } + applyPatches! patches + Task.ok { + platformState: { + app, + state: newState, + rendered: newRendered, + }, + stopPropagation, + preventDefault, + } None -> - Effect.always { platformState, stopPropagation, preventDefault } + Task.ok { platformState, stopPropagation, preventDefault } # ------------------------------- # DIFF diff --git a/examples/virtual-dom-wip/platform/Effect.roc b/examples/virtual-dom-wip/platform/PlatformTasks.roc similarity index 69% rename from examples/virtual-dom-wip/platform/Effect.roc rename to examples/virtual-dom-wip/platform/PlatformTasks.roc index d90037bc04..24f57f69cf 100644 --- a/examples/virtual-dom-wip/platform/Effect.roc +++ b/examples/virtual-dom-wip/platform/PlatformTasks.roc @@ -1,14 +1,10 @@ -hosted Effect +hosted PlatformTasks exposes [ - Effect, NodeId, HandlerId, TagName, AttrType, EventType, - after, - always, - map, createElement, createTextNode, updateTextNode, @@ -26,7 +22,6 @@ hosted Effect disableVdomAllocator, ] imports [] - generates Effect with [after, always, map] # TODO: private types NodeId : U64 @@ -39,43 +34,43 @@ AttrType : Str EventType : Str ## createElement tagName -createElement : NodeId, TagName -> Effect {} +createElement : NodeId, TagName -> Task {} * ## createTextNode content -createTextNode : NodeId, Str -> Effect {} +createTextNode : NodeId, Str -> Task {} * ## updateTextNode content -updateTextNode : NodeId, Str -> Effect {} +updateTextNode : NodeId, Str -> Task {} * ## appendChild parentId childId -appendChild : NodeId, NodeId -> Effect {} +appendChild : NodeId, NodeId -> Task {} * ## removeNode id -removeNode : NodeId -> Effect {} +removeNode : NodeId -> Task {} * ## replaceNode oldId newId -replaceNode : NodeId, NodeId -> Effect {} +replaceNode : NodeId, NodeId -> Task {} * ## setAttribute nodeId attrName value -setAttribute : NodeId, AttrType, Str -> Effect {} +setAttribute : NodeId, AttrType, Str -> Task {} * ## removeAttribute nodeId attrName -removeAttribute : NodeId, AttrType -> Effect {} +removeAttribute : NodeId, AttrType -> Task {} * ## setProperty nodeId propName json -setProperty : NodeId, Str, List U8 -> Effect {} +setProperty : NodeId, Str, List U8 -> Task {} * ## removeProperty nodeId propName -removeProperty : NodeId, Str -> Effect {} +removeProperty : NodeId, Str -> Task {} * ## setStyle nodeId key value -setStyle : NodeId, Str, Str -> Effect {} +setStyle : NodeId, Str, Str -> Task {} * ## setListener nodeId eventType accessorsJson handlerId -setListener : NodeId, EventType, List U8, HandlerId -> Effect {} +setListener : NodeId, EventType, List U8, HandlerId -> Task {} * ## removeListener nodeId handlerId -removeListener : NodeId, HandlerId -> Effect {} +removeListener : NodeId, HandlerId -> Task {} * # Enable a special memory allocator for virtual DOM # This consists of two arenas, "even" and "odd", which alternately hold the "old" and "new" VDOM. @@ -83,9 +78,9 @@ removeListener : NodeId, HandlerId -> Effect {} # Danger: Could cause memory unsafety bugs if used incorrectly! Do not expose! # Not suitable for values that have a different lifetime from the virtual DOM! # TODO: actually implement this for real! LOL -enableVdomAllocator : Bool -> Effect {} +enableVdomAllocator : Bool -> Task {} * # Switch back from the virtual DOM allocator to the "normal" # allocator that is safe to use with long-lived values. # At the same time, drop the entire "old" virtual DOM arena. -disableVdomAllocator : Effect {} +disableVdomAllocator : Task {} * diff --git a/examples/virtual-dom-wip/platform/client-side.roc b/examples/virtual-dom-wip/platform/client-side.roc index e24c589643..bf2a61a774 100644 --- a/examples/virtual-dom-wip/platform/client-side.roc +++ b/examples/virtual-dom-wip/platform/client-side.roc @@ -5,7 +5,6 @@ platform "client-side" imports [ Html.Internal.Shared.{ App }, Html.Internal.Client.{ PlatformState, initClientApp, dispatchEvent }, - Effect.{ Effect }, ] provides [main] @@ -27,18 +26,18 @@ ToHost state initData : { # TODO: naming the type variables causes a type 'mismatch' # main : FromHost state initData -> Effect (ToHost state initData) where initData implements Decoding & Encoding -main : FromHost _ _ -> Effect (ToHost _ _) +main : FromHost _ _ -> Task (ToHost _ _) * main = \fromHost -> if fromHost.isInitEvent then initClientApp fromHost.initJson app - |> Effect.map \platformState -> { + |> Task.map \platformState -> { platformState: Box.box platformState, eventPreventDefault: Bool.false, eventStopPropagation: Bool.false, } else dispatchEvent (Box.unbox fromHost.eventPlatformState) fromHost.eventJsonList fromHost.eventHandlerId - |> Effect.map \jsEventResult -> { + |> Task.map \jsEventResult -> { platformState: Box.box jsEventResult.platformState, eventPreventDefault: jsEventResult.preventDefault, eventStopPropagation: jsEventResult.stopPropagation, diff --git a/www/build.sh b/www/build.sh index ca14d7168d..4125395bde 100755 --- a/www/build.sh +++ b/www/build.sh @@ -97,8 +97,9 @@ fi $roc version -echo 'Building site markdown content' -$roc dev www/main.roc -- www/content/ www/build/ +echo 'Generating site markdown content' +$roc build www/main.roc +./www/main www/content/ www/build/ echo "Adding github link to examples' html..." source www/scripts/add-github-link-to-examples.sh diff --git a/www/content/docs.md b/www/content/docs.md index 2766fb4780..1dd26aff1e 100644 --- a/www/content/docs.md +++ b/www/content/docs.md @@ -2,7 +2,7 @@ - [builtins](/builtins) - docs for modules built into the languageβ€”`Str`, `Num`, etc. - [basic-webserver](https://roc-lang.github.io/basic-webserver/) - a platform for making Web servers ([source code](https://github.com/roc-lang/basic-webserver)) -- [basic-cli](/packages/basic-cli/0.14.0) - a platform for making command-line interfaces ([source code](https://github.com/roc-lang/basic-cli)) +- [basic-cli](/packages/basic-cli/0.15.0) - a platform for making command-line interfaces ([source code](https://github.com/roc-lang/basic-cli)) - [plans](/plans) - current plans for future changes to the language In the future, a language reference will be on this page too. diff --git a/www/content/platforms.md b/www/content/platforms.md index 23b7ffadb6..aee5791ca4 100644 --- a/www/content/platforms.md +++ b/www/content/platforms.md @@ -7,7 +7,7 @@ Something that sets Roc apart from other programming languages is its Pronouncing Tuple By the way, there are two common ways to pronounce "tuple"β€”one sounds like "two-pull" and the other rhymes with "supple"β€”and although no clear consensus has emerged in the programming world, people seem generally accepting when others pronounce it differently than they do. @@ -1458,13 +1458,19 @@ Each `.roc` file is a separate module and contains Roc code for different purpos There are several modules that are built into the Roc compiler, which are imported automatically into every Roc module. They are: -1. `Bool` -2. `Str` -3. `Num` -4. `List` -5. `Result` -6. `Dict` -7. `Set` +1. [Str](https://www.roc-lang.org/builtins/Str) +2. [Num](https://www.roc-lang.org/builtins/Num) +3. [Bool](https://www.roc-lang.org/builtins/Bool) +4. [Result](https://www.roc-lang.org/builtins/Result) +5. [List](https://www.roc-lang.org/builtins/List) +6. [Dict](https://www.roc-lang.org/builtins/Dict) +7. [Set](https://www.roc-lang.org/builtins/Set) +8. [Decode](https://www.roc-lang.org/builtins/Decode) +9. [Encode](https://www.roc-lang.org/builtins/Encode) +10. [Hash](https://www.roc-lang.org/builtins/Hash) +11. [Box](https://www.roc-lang.org/builtins/Box) +12. [Inspect](https://www.roc-lang.org/builtins/Inspect) +13. [Task](https://www.roc-lang.org/builtins/Task) You may have noticed that we already used the first five. For example, when we wrote `Str.concat` and `Num.isEven`, we were referencing functions stored in the `Str` and `Num` modules. @@ -1480,7 +1486,7 @@ Besides being built into the compiler, the builtin modules are different from ot Let's take a closer look at the part of `main.roc` above the `main` def: ```roc -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout ``` @@ -1584,7 +1590,7 @@ See the [Ingest Files Example](https://www.roc-lang.org/examples/IngestFiles/REA ## [Tasks](#tasks) {#tasks} -Tasks are not currently part of the Roc builtinsβ€”for now, each platform exposes their own `Task` implementation, but the plan is to standardize them into a builtin `Task` like module like the builtin modules we already have for `List`, `Str`, and so onβ€”but they're an important part of building Roc applications, so let's continue using the [basic-cli](https://github.com/roc-lang/basic-cli) platform we've been using up to this point as an example! +Tasks are provided in a builtin `Task` module like the `List`, `Str` modules. They're an important part of building Roc applications, so let's continue using the [basic-cli](https://github.com/roc-lang/basic-cli) platform we've been using up to this point as an example! In the `basic-cli` platform, here are four operations we can do: @@ -1598,7 +1604,7 @@ We'll use these four operations to learn about tasks. Let's start with a basic "Hello World" program. ```roc -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout @@ -1626,22 +1632,20 @@ Stdin.line : Task [Input Str, End] * Once this task runs, we'll end up with the [tag union](https://www.roc-lang.org/tutorial#tags-with-payloads) `[Input Str, End]`. Then we can check whether we got an `End` or some actual `Input`, and print out a message accordingly. -### [Reading values from tasks](#inspect) {#task-input} +### [Reading values from tasks](#task-input) {#task-input} Let's change `main` to read a line from `stdin`, and then print what we got: ```roc -app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.14.0/dC5ceT962N_4jmoyoffVdphJ_4GlW3YMhAPyGPr-nU0.tar.br" } +app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br" } import pf.Stdout import pf.Stdin -import pf.Task main = Stdout.line! "Type in something and press Enter:" input = Stdin.line! Stdout.line! "Your input was: $(input)" - ``` If you run this program, it will print "Type in something and press Enter:" and then pause. @@ -1672,7 +1676,7 @@ Let's break down what this type is saying: - `Task` tells us this is a `Task` type. Its two type parameters are just like the ones we saw in `Result` earlier: the first type tells us what this task will produce if it succeeds, and the other one tells us what it will produce if it fails. - `{}` tells us that this task always produces an empty record when it succeeds. (That is, it doesn't produce anything useful. Empty records don't have any information in them!) This is because the last task in `main` comes from `Stdout.line`, which doesn't produce anything. (In contrast, the `Stdin` task's first type parameter is a `Str`, because it produces a `Str` if it succeeds.) -- `[Exit I32, StdoutErr Stdout.Err, StdinErr Stdin.Err]` tells us the different ways this task can fail. The `StdoutErr` and `StdinErr` tags are there becase we used `Stdout.line` and `Stdin.line`. We'll talk about `Exit I32` more in a moment. +- `[Exit I32, StdoutErr Stdout.Err, StdinErr Stdin.Err]` tells us the different ways this task can fail. The `StdoutErr` and `StdinErr` tags are there because we used `Stdout.line` and `Stdin.line`. We'll talk about `Exit I32` more in a moment. To understand what the `Exit I32 Str` error means, let's try temporarily commenting out our current `main` and replacing it with this one: diff --git a/www/main.roc b/www/main.roc index 87e95f6316..ffdd8c045e 100644 --- a/www/main.roc +++ b/www/main.roc @@ -1,6 +1,7 @@ -app [main] { pf: platform "https://github.com/lukewilliamboswell/basic-ssg/releases/download/0.1.0/EMH2OFwcXCUEzbwP6gyfeRQu7Phr-slc-vE8FPPreys.tar.br" } +app [main] { + pf: platform "https://github.com/lukewilliamboswell/basic-ssg/releases/download/0.5.0/MlW8VJCTuOFrlKRiW9h-WPOv4_5FqTrqlZZOi5fMqdo.tar.br", +} -import pf.Task exposing [Task] import pf.SSG import pf.Types exposing [Args] import pf.Html exposing [header, nav, div, link, attribute, text, a, span, html, head, body, meta, script, footer, br]