diff --git a/crates/cli/tests/benchmarks/AStar.roc b/crates/cli/tests/benchmarks/AStar.roc index 20856b8d7f7..a9b237ba91f 100644 --- a/crates/cli/tests/benchmarks/AStar.roc +++ b/crates/cli/tests/benchmarks/AStar.roc @@ -14,26 +14,26 @@ Model position : { initial_model : position -> Model position where position implements Hash & Eq initial_model = \start -> { - evaluated: Set.empty({}), + evaluated: Set.empty(), open_set: Set.single(start), costs: Dict.single(start, 0), - came_from: Dict.empty({}), + came_from: Dict.empty(), } -cheapest_open : (position -> F64), Model position -> Result position {} where position implements Hash & Eq +cheapest_open : (position -> F64), Model position -> Result position () where position implements Hash & Eq cheapest_open = \cost_fn, model -> model.open_set |> Set.to_list |> List.keep_oks( \position -> when Dict.get(model.costs, position) is - Err(_) -> Err({}) + Err(_) -> Err() Ok(cost) -> Ok({ cost: cost + cost_fn(position), position }), ) |> Quicksort.sort_by(.cost) |> List.first |> Result.map(.position) - |> Result.map_err(\_ -> {}) + |> Result.map_err(\_ -> ()) reconstruct_path : Dict position position, position -> List position where position implements Hash & Eq reconstruct_path = \came_from, goal -> @@ -70,10 +70,10 @@ update_cost = \current, neighbor, model -> else model -astar : (position, position -> F64), (position -> Set position), position, Model position -> Result (List position) {} where position implements Hash & Eq +astar : (position, position -> F64), (position -> Set position), position, Model position -> Result (List position) () where position implements Hash & Eq astar = \cost_fn, move_fn, goal, model -> when cheapest_open(\source -> cost_fn(source, goal), model) is - Err({}) -> Err({}) + Err() -> Err() Ok(current) -> if current == goal then Ok(reconstruct_path(model.came_from, goal)) diff --git a/crates/cli/tests/benchmarks/cFold.roc b/crates/cli/tests/benchmarks/cFold.roc index ce81b336974..bfe0bdad66b 100644 --- a/crates/cli/tests/benchmarks/cFold.roc +++ b/crates/cli/tests/benchmarks/cFold.roc @@ -3,9 +3,9 @@ app [main!] { pf: platform "platform/main.roc" } import pf.Host # adapted from https://github.com/koka-lang/koka/blob/master/test/bench/haskell/cfold.hs -main! : {} => {} -main! = \{} -> - { value, is_error } = Host.get_int!({}) +main! : () => () +main! = \() -> + { value, is_error } = Host.get_int!() input_result = if is_error then Err(GetIntError) diff --git a/crates/cli/tests/benchmarks/closure.roc b/crates/cli/tests/benchmarks/closure.roc index 49febbab5c8..fbdc489abab 100644 --- a/crates/cli/tests/benchmarks/closure.roc +++ b/crates/cli/tests/benchmarks/closure.roc @@ -1,50 +1,50 @@ app [main!] { pf: platform "platform/main.roc" } -main! : {} => {} -main! = \{} -> - closure1({}) - |> Result.try(closure2) - |> Result.try(closure3) - |> Result.try(closure4) - |> Result.with_default({}) +main! : () => () +main! = \() -> + closure1() + |> closure2()? + |> closure3()? + |> closure4()? + ?? () # --- -closure1 : {} -> Result {} [] +closure1 : () -> Result () [] closure1 = \_ -> Ok(foo(to_unit_borrowed, "a long string such that it's malloced")) - |> Result.map(\_ -> {}) + |> Result.map(\_ -> ()) to_unit_borrowed = \x -> Str.count_utf8_bytes(x) foo = \f, x -> f(x) # --- -closure2 : {} -> Result {} [] +closure2 : () -> Result () [] closure2 = \_ -> x : Str x = "a long string such that it's malloced" - Ok({}) + Ok() |> Result.map(\_ -> x) |> Result.map(to_unit) -to_unit = \_ -> {} +to_unit = \_ -> () # # --- -closure3 : {} -> Result {} [] +closure3 : () -> Result () [] closure3 = \_ -> x : Str x = "a long string such that it's malloced" - Ok({}) - |> Result.try(\_ -> Ok(x) |> Result.map(\_ -> {})) + Ok() + |> Result.try(\_ -> Ok(x) |> Result.map(\_ -> ())) # # --- -closure4 : {} -> Result {} [] +closure4 : () -> Result () [] closure4 = \_ -> x : Str x = "a long string such that it's malloced" - Ok({}) + Ok() |> Result.try(\_ -> Ok(x)) - |> Result.map(\_ -> {}) + |> Result.map(\_ -> ()) diff --git a/crates/cli/tests/benchmarks/deriv.roc b/crates/cli/tests/benchmarks/deriv.roc index 3f0f46fa663..8cf342b818b 100644 --- a/crates/cli/tests/benchmarks/deriv.roc +++ b/crates/cli/tests/benchmarks/deriv.roc @@ -3,9 +3,9 @@ app [main!] { pf: platform "platform/main.roc" } import pf.Host # based on: https://github.com/koka-lang/koka/blob/master/test/bench/haskell/deriv.hs -main! : {} => {} -main! = \{} -> - { value, is_error } = Host.get_int!({}) +main! : () => () +main! = \() -> + { value, is_error } = Host.get_int!() input_result = if is_error then Err(GetIntError) @@ -21,7 +21,8 @@ main! = \{} -> f = pow(x, x) _ = nest!(deriv!, n, f) # original koka n = 10 - {} + + () Err(GetIntError) -> Host.put_line!("Error: Failed to get Integer from stdin.") diff --git a/crates/cli/tests/benchmarks/issue2279.roc b/crates/cli/tests/benchmarks/issue2279.roc index 4e2792cdd9d..58ae609b352 100644 --- a/crates/cli/tests/benchmarks/issue2279.roc +++ b/crates/cli/tests/benchmarks/issue2279.roc @@ -3,7 +3,7 @@ app [main!] { pf: platform "platform/main.roc" } import Issue2279Help import pf.Host -main! = \{} -> +main! = \() -> text = if Bool.true then Issue2279Help.text diff --git a/crates/cli/tests/benchmarks/nQueens.roc b/crates/cli/tests/benchmarks/nQueens.roc index 26336e659ec..63aa90f4f27 100644 --- a/crates/cli/tests/benchmarks/nQueens.roc +++ b/crates/cli/tests/benchmarks/nQueens.roc @@ -2,9 +2,9 @@ app [main!] { pf: platform "platform/main.roc" } import pf.Host -main! : {} => {} -main! = \{} -> - { value, is_error } = Host.get_int!({}) +main! : () => () +main! = \() -> + { value, is_error } = Host.get_int!() input_result = if is_error then Err(GetIntError) diff --git a/crates/cli/tests/benchmarks/platform/Host.roc b/crates/cli/tests/benchmarks/platform/Host.roc index 25019c7c90e..f24f6bbfced 100644 --- a/crates/cli/tests/benchmarks/platform/Host.roc +++ b/crates/cli/tests/benchmarks/platform/Host.roc @@ -2,8 +2,8 @@ hosted Host exposes [put_line!, put_int!, get_int!] imports [] -put_line! : Str => {} +put_line! : Str => () -put_int! : I64 => {} +put_int! : I64 => () -get_int! : {} => { value : I64, is_error : Bool } +get_int! : () => { value : I64, is_error : Bool } diff --git a/crates/cli/tests/benchmarks/platform/app.roc b/crates/cli/tests/benchmarks/platform/app.roc index 26e5c734378..e41fd8a7c91 100644 --- a/crates/cli/tests/benchmarks/platform/app.roc +++ b/crates/cli/tests/benchmarks/platform/app.roc @@ -1,4 +1,4 @@ app [main!] { pf: platform "main.roc" } -main! : {} => {} -main! = \{} -> {} +main! : () => () +main! = \() -> () diff --git a/crates/cli/tests/benchmarks/platform/main.roc b/crates/cli/tests/benchmarks/platform/main.roc index d16225ec963..eebca55bfc1 100644 --- a/crates/cli/tests/benchmarks/platform/main.roc +++ b/crates/cli/tests/benchmarks/platform/main.roc @@ -1,9 +1,9 @@ platform "benchmarks" - requires {} { main! : {} => {} } + requires {} { main! : () => () } exposes [] packages {} imports [] provides [main_for_host!] -main_for_host! : {} => {} -main_for_host! = \{} -> main!({}) +main_for_host! : () => () +main_for_host! = \() -> main!() diff --git a/crates/cli/tests/benchmarks/quicksortApp.roc b/crates/cli/tests/benchmarks/quicksortApp.roc index db856331f87..1a44d3abcae 100644 --- a/crates/cli/tests/benchmarks/quicksortApp.roc +++ b/crates/cli/tests/benchmarks/quicksortApp.roc @@ -3,9 +3,9 @@ app [main!] { pf: platform "platform/main.roc" } import pf.Host import Quicksort -main! : {} => {} -main! = \{} -> - { value, is_error } = Host.get_int!({}) +main! : () => () +main! = \() -> + { value, is_error } = Host.get_int!() input_result = if is_error then Err(GetIntError) diff --git a/crates/cli/tests/benchmarks/rBTreeCk.roc b/crates/cli/tests/benchmarks/rBTreeCk.roc index 8144b81f113..a0ed0ca52a4 100644 --- a/crates/cli/tests/benchmarks/rBTreeCk.roc +++ b/crates/cli/tests/benchmarks/rBTreeCk.roc @@ -37,9 +37,9 @@ fold = \f, tree, b -> Leaf -> b Node(_, l, k, v, r) -> fold(f, r, f(k, v, fold(f, l, b))) -main! : {} => {} -main! = \{} -> - { value, is_error } = Host.get_int!({}) +main! : () => () +main! = \() -> + { value, is_error } = Host.get_int!() input_result = if is_error then Err(GetIntError) diff --git a/crates/cli/tests/benchmarks/rBTreeInsert.roc b/crates/cli/tests/benchmarks/rBTreeInsert.roc index a017501bd50..8b9bdf646f9 100644 --- a/crates/cli/tests/benchmarks/rBTreeInsert.roc +++ b/crates/cli/tests/benchmarks/rBTreeInsert.roc @@ -2,17 +2,17 @@ app [main!] { pf: platform "platform/main.roc" } import pf.Host -main! : {} => {} -main! = \{} -> - tree : RedBlackTree I64 {} - tree = insert(0, {}, Empty) +main! : () => () +main! = \() -> + tree : RedBlackTree I64 () + tree = insert(0, (), Empty) tree |> show |> Host.put_line! -show : RedBlackTree I64 {} -> Str -show = \tree -> show_rb_tree(tree, Num.to_str, \{} -> "{}") +show : RedBlackTree I64 () -> Str +show = \tree -> show_rb_tree(tree, Num.to_str, \() -> "()") show_rb_tree : RedBlackTree k v, (k -> Str), (v -> Str) -> Str show_rb_tree = \tree, show_key, show_value -> diff --git a/crates/cli/tests/benchmarks/testAStar.roc b/crates/cli/tests/benchmarks/testAStar.roc index 23f1d1f9df5..68534a8293d 100644 --- a/crates/cli/tests/benchmarks/testAStar.roc +++ b/crates/cli/tests/benchmarks/testAStar.roc @@ -3,7 +3,7 @@ app [main!] { pf: platform "platform/main.roc" } import pf.Host import AStar -main! = \{} -> +main! = \() -> Host.put_line!(show_bool(test1)) show_bool : Bool -> Str diff --git a/crates/cli/tests/benchmarks/testBase64.roc b/crates/cli/tests/benchmarks/testBase64.roc index 86d76bdcdc7..18cc477e6ea 100644 --- a/crates/cli/tests/benchmarks/testBase64.roc +++ b/crates/cli/tests/benchmarks/testBase64.roc @@ -3,8 +3,8 @@ app [main!] { pf: platform "platform/main.roc" } import Base64 import pf.Host -main! : {} => {} -main! = \{} -> +main! : () => () +main! = \() -> when Base64.from_bytes(Str.to_utf8("Hello World")) is Err(_) -> Host.put_line!("sadness") Ok(encoded) -> diff --git a/crates/cli/tests/test-projects/effectful/Community.roc b/crates/cli/tests/test-projects/effectful/Community.roc index 5fcada82f98..c9ed1725b03 100644 --- a/crates/cli/tests/test-projects/effectful/Community.roc +++ b/crates/cli/tests/test-projects/effectful/Community.roc @@ -36,7 +36,7 @@ empty = @Community({ people: [], friends: [] }) add_person = \@Community({ people, friends }), person -> @Community({ people: List.append(people, @Person(person)), - friends: List.append(friends, Set.empty({})), + friends: List.append(friends, Set.empty()), }) add_friend = \@Community({ people, friends }), from, to -> @@ -66,7 +66,7 @@ walk_friend_names = \@Community({ people, friends }), s0, next_fn -> |> Str.concat(person.last_name) friend_names = - Set.walk(friend_set, Set.empty({}), \friends_set, friend_id -> + Set.walk(friend_set, Set.empty(), \friends_set, friend_id -> @Person(friend) = when List.get(people, friend_id) is Ok(v) -> v diff --git a/crates/cli/tests/test-projects/effectful/echo.roc b/crates/cli/tests/test-projects/effectful/echo.roc index 047d3783dd9..5643f7735df 100644 --- a/crates/cli/tests/test-projects/effectful/echo.roc +++ b/crates/cli/tests/test-projects/effectful/echo.roc @@ -2,11 +2,11 @@ app [main!] { pf: platform "../test-platform-effects-zig/main.roc" } import pf.Effect -main! : {} => {} -main! = \{} -> tick!({}) +main! : () => () +main! = \() -> tick!() -tick! = \{} -> - line = Effect.get_line!({}) +tick! = \() -> + line = Effect.get_line!() if !(Str.is_empty(line)) then Effect.put_line!(echo(line)) diff --git a/crates/cli/tests/test-projects/effectful/for_each_try.roc b/crates/cli/tests/test-projects/effectful/for_each_try.roc index 6431c4d2a51..c37010ee21d 100644 --- a/crates/cli/tests/test-projects/effectful/for_each_try.roc +++ b/crates/cli/tests/test-projects/effectful/for_each_try.roc @@ -2,21 +2,21 @@ app [main!] { pf: platform "../test-platform-effects-zig/main.roc" } import pf.Effect -main! : {} => {} -main! = \{} -> +main! : () => () +main! = \() -> good = [0, 2, 4] |> List.for_each_try!(validate!) - expect good == Ok({}) + expect good == Ok() bad = [6, 8, 9, 10] |> List.for_each_try!(validate!) expect bad == Err(9) - {} + () -validate! : U32 => Result {} U32 +validate! : U32 => Result () U32 validate! = \x -> if Num.is_even(x) then Effect.put_line!("✅ ${Num.to_str(x)}") - Ok({}) + Ok() else Effect.put_line!("${Num.to_str(x)} is not even! ABORT!") Err(x) diff --git a/crates/cli/tests/test-projects/effectful/form.roc b/crates/cli/tests/test-projects/effectful/form.roc index 55d4fe95fc1..c68ee9d80a1 100644 --- a/crates/cli/tests/test-projects/effectful/form.roc +++ b/crates/cli/tests/test-projects/effectful/form.roc @@ -2,8 +2,8 @@ app [main!] { pf: platform "../test-platform-effects-zig/main.roc" } import pf.Effect -main! : {} => {} -main! = \{} -> +main! : () => () +main! = \() -> first = ask!("What's your first name?") last = ask!("What's your last name?") @@ -24,4 +24,4 @@ main! = \{} -> ask! : Str => Str ask! = \question -> Effect.put_line!(question) - Effect.get_line!({}) + Effect.get_line!() diff --git a/crates/cli/tests/test-projects/effectful/hello.roc b/crates/cli/tests/test-projects/effectful/hello.roc index 8a856861502..ceba6696cf5 100644 --- a/crates/cli/tests/test-projects/effectful/hello.roc +++ b/crates/cli/tests/test-projects/effectful/hello.roc @@ -2,6 +2,6 @@ app [main!] { pf: platform "../test-platform-effects-zig/main.roc" } import pf.Effect -main! : {} => {} -main! = \{} -> +main! : () => () +main! = \() -> Effect.put_line!("I'm an effect 👻") diff --git a/crates/cli/tests/test-projects/effectful/ignore_result.roc b/crates/cli/tests/test-projects/effectful/ignore_result.roc index 9d9627fd12f..da11f34e9ce 100644 --- a/crates/cli/tests/test-projects/effectful/ignore_result.roc +++ b/crates/cli/tests/test-projects/effectful/ignore_result.roc @@ -2,7 +2,7 @@ app [main!] { pf: platform "../test-platform-effects-zig/main.roc" } import pf.Effect -main! : {} => {} +main! : () => () main! = \{} -> - _ = Effect.get_line!({}) + _ = Effect.get_line!() Effect.put_line!("I asked for input and I ignored it. Deal with it! 😎") diff --git a/crates/cli/tests/test-projects/effectful/inspect-logging.roc b/crates/cli/tests/test-projects/effectful/inspect-logging.roc index 06380f091e1..b749ef920f9 100644 --- a/crates/cli/tests/test-projects/effectful/inspect-logging.roc +++ b/crates/cli/tests/test-projects/effectful/inspect-logging.roc @@ -6,7 +6,7 @@ app [main!] { pf: platform "../test-platform-effects-zig/main.roc" } import pf.Effect import Community -main! = \{} -> +main! = \() -> Community.empty |> Community.add_person({ first_name: "John", diff --git a/crates/cli/tests/test-projects/effectful/loops.roc b/crates/cli/tests/test-projects/effectful/loops.roc index cb3b77199b1..26e68a070fa 100644 --- a/crates/cli/tests/test-projects/effectful/loops.roc +++ b/crates/cli/tests/test-projects/effectful/loops.roc @@ -2,15 +2,15 @@ app [main!] { pf: platform "../test-platform-effects-zig/main.roc" } import pf.Effect -main! : {} => {} -main! = \{} -> +main! : () => () +main! = \() -> friends = ["Lu", "Marce", "Joaquin", "Chloé", "Mati", "Pedro"] print_all!(friends) -print_all! : List Str => {} +print_all! : List Str => () print_all! = \friends -> when friends is - [] -> {} + [] -> () [first, .. as remaining] -> Effect.put_line!(first) print_all!(remaining) diff --git a/crates/cli/tests/test-projects/effectful/on_err.roc b/crates/cli/tests/test-projects/effectful/on_err.roc index 84370a40cdf..bbd866019e3 100644 --- a/crates/cli/tests/test-projects/effectful/on_err.roc +++ b/crates/cli/tests/test-projects/effectful/on_err.roc @@ -2,21 +2,21 @@ app [main!] { pf: platform "../test-platform-effects-zig/main.roc" } import pf.Effect -main! : {} => {} -main! = \{} -> +main! : () => () +main! = \() -> _ = - authenticate!({}) + authenticate!() |> Result.on_err!(\BadPass -> Effect.put_line!("LOG: Failed login attempt") Ok("Bad password")) - {} + () -authenticate! : {} => Result Str [BadPass] -authenticate! = \{} -> +authenticate! : () => Result Str [BadPass] +authenticate! = \() -> Effect.put_line!("Enter your password:") - password = Effect.get_line!({}) + password = Effect.get_line!() if password == "password" then Ok("You are in") diff --git a/crates/cli/tests/test-projects/effectful/print-line.roc b/crates/cli/tests/test-projects/effectful/print-line.roc index 3f6e049e605..090bcaf3ed7 100644 --- a/crates/cli/tests/test-projects/effectful/print-line.roc +++ b/crates/cli/tests/test-projects/effectful/print-line.roc @@ -2,18 +2,18 @@ app [main!] { pf: platform "../test-platform-effects-zig/main.roc" } import pf.Effect -main! : {} => {} -main! = \{} -> +main! : () => () +main! = \() -> ["Welcome!", "What's your name?"] |> List.for_each!(Effect.put_line!) - line = Effect.get_line!({}) + line = Effect.get_line!() if line == "secret" then Effect.put_line!("You found the secret") Effect.put_line!("Congratulations!") else - {} + () Effect.put_line!("You entered: ${line}") Effect.put_line!("It is known") diff --git a/crates/cli/tests/test-projects/effectful/suffixed_record_field.roc b/crates/cli/tests/test-projects/effectful/suffixed_record_field.roc index e8acfe39f17..ab2c47ac277 100644 --- a/crates/cli/tests/test-projects/effectful/suffixed_record_field.roc +++ b/crates/cli/tests/test-projects/effectful/suffixed_record_field.roc @@ -3,14 +3,14 @@ app [main!] { pf: platform "../test-platform-effects-zig/main.roc" } import pf.Effect Fx : { - get_line! : {} => Str, + get_line! : () => Str, } -main! : {} => {} -main! = \{} -> +main! : () => () +main! = \() -> not_effectful : Fx not_effectful = { - get_line!: \{} -> "hardcoded", + get_line!: \() -> "hardcoded", } effectful : Fx @@ -18,5 +18,5 @@ main! = \{} -> get_line!: Effect.get_line!, } - Effect.put_line!("not_effectful: ${not_effectful.get_line!({})}") - Effect.put_line!("effectful: ${effectful.get_line!({})}") + Effect.put_line!("not_effectful: ${not_effectful.get_line!()}") + Effect.put_line!("effectful: ${effectful.get_line!()}") diff --git a/crates/cli/tests/test-projects/effectful/untyped_passed_fx.roc b/crates/cli/tests/test-projects/effectful/untyped_passed_fx.roc index 707883a98b2..512a56f8c49 100644 --- a/crates/cli/tests/test-projects/effectful/untyped_passed_fx.roc +++ b/crates/cli/tests/test-projects/effectful/untyped_passed_fx.roc @@ -2,11 +2,11 @@ app [main!] { pf: platform "../test-platform-effects-zig/main.roc" } import pf.Effect -main! : {} => {} -main! = \{} -> - logged!("hello", \{} -> Effect.put_line!("Hello, World!")) +main! : () => () +main! = \() -> + logged!("hello", \() -> Effect.put_line!("Hello, World!")) logged! = \name, fx! -> Effect.put_line!("Before ${name}") - fx!({}) + fx!() Effect.put_line!("After ${name}") diff --git a/crates/cli/tests/test-projects/false-interpreter/main.roc b/crates/cli/tests/test-projects/false-interpreter/main.roc index a3527d4bc8b..45c03a7532a 100644 --- a/crates/cli/tests/test-projects/false-interpreter/main.roc +++ b/crates/cli/tests/test-projects/false-interpreter/main.roc @@ -14,22 +14,22 @@ import Variable exposing [Variable] InterpreterErrors : [BadUtf8, DivByZero, EmptyStack, InvalidBooleanValue, InvalidChar Str, MaxInputNumber, NoLambdaOnStack, NoNumberOnStack, NoVariableOnStack, NoScope, OutOfBounds, UnexpectedEndOfData] -main! : Str => {} +main! : Str => () main! = \filename -> when interpret_file!(filename) is - Ok({}) -> - {} + Ok() -> + () Err(StringErr(e)) -> Stdout.line!("Ran into problem:\n${e}\n") -interpret_file! : Str => Result {} [StringErr Str] +interpret_file! : Str => Result () [StringErr Str] interpret_file! = \filename -> Context.with!(filename, \ctx -> result = interpret_ctx!(ctx) when result is Ok(_) -> - Ok({}) + Ok() Err(BadUtf8) -> Err(StringErr("Failed to convert string from Utf8 bytes")) @@ -377,7 +377,7 @@ step_exec_ctx! = \ctx, char -> 0x5E -> # `^` read char as int - in = Stdin.char!({}) + in = Stdin.char!() if in == 255 then # max char sent on EOF. Change to -1 Ok(Context.push_stack(ctx, Number(-1))) diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/File.roc b/crates/cli/tests/test-projects/false-interpreter/platform/File.roc index 8d9ea0db35d..042d89c1a02 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/File.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/File.roc @@ -17,7 +17,7 @@ open! = \path -> Host.open_file!(path) |> @Handle -close! : Handle => {} +close! : Handle => () close! = \@Handle(handle) -> Host.close_file!(handle) diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/Host.roc b/crates/cli/tests/test-projects/false-interpreter/platform/Host.roc index ef6aaaa5d70..12210c2593e 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/Host.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/Host.roc @@ -4,16 +4,16 @@ hosted Host open_file! : Str => U64 -close_file! : U64 => {} +close_file! : U64 => () get_file_line! : U64 => Str get_file_bytes! : U64 => List U8 -put_line! : Str => {} +put_line! : Str => () -put_raw! : Str => {} +put_raw! : Str => () -get_line! : {} => Str +get_line! : () => Str -get_char! : {} => U8 +get_char! : () => U8 diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc b/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc index b9ad5d2eac6..e0a118373d6 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/Stdin.roc @@ -2,10 +2,10 @@ module [line!, char!] import pf.Host -line! : {} => Str -line! = \{} -> - Host.get_line!({}) +line! : () => Str +line! = \() -> + Host.get_line!() -char! : {} => U8 -char! = \{} -> - Host.get_char!({}) +char! : () => U8 +char! = \() -> + Host.get_char!() diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc b/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc index c4c7656b5a5..5fe03189f0a 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/Stdout.roc @@ -2,10 +2,10 @@ module [line!, raw!] import pf.Host -line! : Str => {} +line! : Str => () line! = \text -> Host.put_line!(text) -raw! : Str => {} +raw! : Str => () raw! = \text -> Host.put_raw!(text) diff --git a/crates/cli/tests/test-projects/false-interpreter/platform/main.roc b/crates/cli/tests/test-projects/false-interpreter/platform/main.roc index b39ab41e9fc..11a94a87a95 100644 --- a/crates/cli/tests/test-projects/false-interpreter/platform/main.roc +++ b/crates/cli/tests/test-projects/false-interpreter/platform/main.roc @@ -1,9 +1,9 @@ platform "false-interpreter" - requires {} { main! : Str => {} } + requires {} { main! : Str => () } exposes [] packages {} imports [] provides [main_for_host!] -main_for_host! : Str => {} +main_for_host! : Str => () main_for_host! = \file -> main!(file) diff --git a/crates/cli/tests/test-projects/fixtures/format/formatted.roc b/crates/cli/tests/test-projects/fixtures/format/formatted.roc index 58f252bab69..2d4eb4cbf06 100644 --- a/crates/cli/tests/test-projects/fixtures/format/formatted.roc +++ b/crates/cli/tests/test-projects/fixtures/format/formatted.roc @@ -1,4 +1,4 @@ app [main] { pf: "platform/main.roc" } main : Str -main = Dep1.value1({}) +main = Dep1.value1() diff --git a/crates/cli/tests/test-projects/fixtures/format/formatted_directory/Formatted.roc b/crates/cli/tests/test-projects/fixtures/format/formatted_directory/Formatted.roc index 58f252bab69..2d4eb4cbf06 100644 --- a/crates/cli/tests/test-projects/fixtures/format/formatted_directory/Formatted.roc +++ b/crates/cli/tests/test-projects/fixtures/format/formatted_directory/Formatted.roc @@ -1,4 +1,4 @@ app [main] { pf: "platform/main.roc" } main : Str -main = Dep1.value1({}) +main = Dep1.value1() diff --git a/crates/cli/tests/test-projects/fixtures/format/not-formatted.roc b/crates/cli/tests/test-projects/fixtures/format/not-formatted.roc index 4a82e9b28cb..f84bd919848 100644 --- a/crates/cli/tests/test-projects/fixtures/format/not-formatted.roc +++ b/crates/cli/tests/test-projects/fixtures/format/not-formatted.roc @@ -1,4 +1,4 @@ app [main] { pf: "platform/main.roc" } main : Str -main = Dep1.value1({}) +main = Dep1.value1() diff --git a/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/Dep1.roc b/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/Dep1.roc index bc291f49f3e..516240f03a4 100644 --- a/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/Dep1.roc +++ b/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/Dep1.roc @@ -2,5 +2,5 @@ module [value1] import Dep2 -value1 : {} -> Str -value1 = \_ -> Dep2.value2({}) +value1 : () -> Str +value1 = \_ -> Dep2.value2() diff --git a/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/Dep2.roc b/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/Dep2.roc index c75c645283a..2529ccd62ea 100644 --- a/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/Dep2.roc +++ b/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/Dep2.roc @@ -1,4 +1,4 @@ module [value2] -value2 : {} -> Str +value2 : () -> Str value2 = \_ -> "I am Dep2.value2" diff --git a/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/main.roc b/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/main.roc index 24ea147fc02..b77414b334c 100644 --- a/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/main.roc +++ b/crates/cli/tests/test-projects/fixtures/multi-dep-thunk/main.roc @@ -3,4 +3,4 @@ app [main] { pf: platform "../../test-platform-simple-zig/main.roc" } import Dep1 main : Str -main = Dep1.value1({}) +main = Dep1.value1() diff --git a/crates/cli/tests/test-projects/known_bad/TypeError.roc b/crates/cli/tests/test-projects/known_bad/TypeError.roc index 58899ecff59..cfb275abbe8 100644 --- a/crates/cli/tests/test-projects/known_bad/TypeError.roc +++ b/crates/cli/tests/test-projects/known_bad/TypeError.roc @@ -1,5 +1,5 @@ app [main] { pf: platform "../false-interpreter/platform/main.roc" } -main : Str -> Task {} [] +main : Str -> List () main = \_ -> - "this is a string, not a Task {} [] function like the platform expects." + "this is a string, not a List () function like the platform expects." diff --git a/crates/cli/tests/test-projects/module_params/issue_7116.roc b/crates/cli/tests/test-projects/module_params/issue_7116.roc index dc2d64363bd..bb5c60ecc6a 100644 --- a/crates/cli/tests/test-projects/module_params/issue_7116.roc +++ b/crates/cli/tests/test-projects/module_params/issue_7116.roc @@ -2,10 +2,10 @@ app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.17.0/lZFLstMUCUvd5bjnnpYromZJXkQUrdhbva4xdBInicE.tar.br", } -import Alias { passed: Task.ok({}) } +import Alias { passed: \_ -> Ok() } main = - Task.loop!({}, loop) + Task.loop!((), loop) -loop = \{} -> +loop = \() -> Task.map(Alias.exposed, \x -> Done(x)) diff --git a/crates/cli/tests/test-projects/test-platform-effects-zig/Effect.roc b/crates/cli/tests/test-projects/test-platform-effects-zig/Effect.roc index c719a7886cc..cc8a4b475da 100644 --- a/crates/cli/tests/test-projects/test-platform-effects-zig/Effect.roc +++ b/crates/cli/tests/test-projects/test-platform-effects-zig/Effect.roc @@ -2,6 +2,6 @@ hosted Effect exposes [put_line!, get_line!] imports [] -put_line! : Str => {} +put_line! : Str => () -get_line! : {} => Str +get_line! : () => Str diff --git a/crates/cli/tests/test-projects/test-platform-effects-zig/app-stub.roc b/crates/cli/tests/test-projects/test-platform-effects-zig/app-stub.roc index b5f3d90d2a0..fd7cf7428ee 100644 --- a/crates/cli/tests/test-projects/test-platform-effects-zig/app-stub.roc +++ b/crates/cli/tests/test-projects/test-platform-effects-zig/app-stub.roc @@ -3,8 +3,8 @@ app [main!] { pf: platform "main.roc" } import pf.Effect # just a stubbed app for building the test platform -main! = \{} -> +main! = \() -> Effect.put_line!("") - {} + () diff --git a/crates/cli/tests/test-projects/test-platform-effects-zig/main.roc b/crates/cli/tests/test-projects/test-platform-effects-zig/main.roc index beaf945cd5c..c0b2ca8857a 100644 --- a/crates/cli/tests/test-projects/test-platform-effects-zig/main.roc +++ b/crates/cli/tests/test-projects/test-platform-effects-zig/main.roc @@ -5,5 +5,5 @@ platform "effects" imports [] provides [main_for_host!] -main_for_host! : {} => {} -main_for_host! = \{} -> main!({}) +main_for_host! : () => () +main_for_host! = \() -> main!() diff --git a/crates/cli/tests/test-projects/tui/main.roc b/crates/cli/tests/test-projects/tui/main.roc index 35f3cc08c09..77e38dda34f 100644 --- a/crates/cli/tests/test-projects/tui/main.roc +++ b/crates/cli/tests/test-projects/tui/main.roc @@ -6,7 +6,7 @@ Model : Str main : Program Model main = { - init: \{} -> "Hello World", + init: \() -> "Hello World", update: \model, new -> Str.concat(model, new), view: \model -> Str.concat(model, "!"), } diff --git a/crates/cli/tests/test-projects/tui/platform/Program.roc b/crates/cli/tests/test-projects/tui/platform/Program.roc index b95ec62eb77..3f5fecc0c57 100644 --- a/crates/cli/tests/test-projects/tui/platform/Program.roc +++ b/crates/cli/tests/test-projects/tui/platform/Program.roc @@ -1,7 +1,7 @@ module [Program] Program model : { - init : {} -> model, + init : () -> model, update : model, Str -> model, view : model -> Str, } diff --git a/crates/cli/tests/test-projects/tui/platform/main.roc b/crates/cli/tests/test-projects/tui/platform/main.roc index d7a0c963249..8518762942a 100644 --- a/crates/cli/tests/test-projects/tui/platform/main.roc +++ b/crates/cli/tests/test-projects/tui/platform/main.roc @@ -1,9 +1,9 @@ platform "tui" - requires { Model } { main : { init : {} -> Model, update : Model, Str -> Model, view : Model -> Str } } + requires { Model } { main : { init : () -> Model, update : Model, Str -> Model, view : Model -> Str } } exposes [] packages {} imports [] provides [main_for_host] -main_for_host : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } +main_for_host : { init : (() -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } main_for_host = main diff --git a/crates/compiler/builtins/roc/Decode.roc b/crates/compiler/builtins/roc/Decode.roc index ce7d996af5f..4a9de814669 100644 --- a/crates/compiler/builtins/roc/Decode.roc +++ b/crates/compiler/builtins/roc/Decode.roc @@ -116,7 +116,7 @@ DecoderFormatting implements ## `decode_bool` could be defined as follows; ## ## ```roc -## decode_bool = Decode.custom \bytes, @Json({}) -> +## decode_bool = Decode.custom \bytes, @Json() -> ## when bytes is ## ['f', 'a', 'l', 's', 'e', ..] -> { result: Ok(Bool.false), rest: List.drop_first(bytes, 5) } ## ['t', 'r', 'u', 'e', ..] -> { result: Ok Bool.true, rest: List.drop_first(bytes, 4) } diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc index a061291e783..c023d1f871a 100644 --- a/crates/compiler/builtins/roc/Dict.roc +++ b/crates/compiler/builtins/roc/Dict.roc @@ -53,7 +53,7 @@ import Inspect exposing [Inspect, Inspector, InspectFormatter] ## its population as the associated value. ## ```roc ## population_by_city = -## Dict.empty({}) +## Dict.empty() ## |> Dict.insert("London", 8_961_989) ## |> Dict.insert("Philadelphia", 1_603_797) ## |> Dict.insert("Shanghai", 24_870_895) @@ -140,10 +140,10 @@ to_inspector_dict = \dict -> ## Return an empty dictionary. ## ```roc -## empty_dict = Dict.empty({}) +## empty_dict = Dict.empty() ## ``` -empty : {} -> Dict * * -empty = \{} -> +empty : () -> Dict * * +empty = \() -> @Dict( { buckets: [], @@ -159,7 +159,7 @@ empty = \{} -> ## inserted. with_capacity : U64 -> Dict * * with_capacity = \requested -> - empty({}) + empty() |> reserve(requested) ## Enlarge the dictionary for at least capacity additional elements @@ -212,7 +212,7 @@ release_excess_capacity = \@Dict({ buckets, data, max_bucket_capacity: original_ ## Returns the max number of elements the dictionary can hold before requiring a rehash. ## ```roc ## food_dict = -## Dict.empty({}) +## Dict.empty() ## |> Dict.insert("apple", "fruit") ## ## capacity_of_dict = Dict.capacity(food_dict) @@ -225,11 +225,11 @@ capacity = \@Dict({ max_bucket_capacity }) -> ## ```roc ## expect ## Dict.single("A", "B") -## |> Bool.is_eq(Dict.empty({}) |> Dict.insert("A", "B")) +## |> Bool.is_eq(Dict.empty() |> Dict.insert("A", "B")) ## ``` single : k, v -> Dict k v single = \k, v -> - insert(empty({}), k, v) + insert(empty(), k, v) ## Returns dictionary with the keys and values specified by the input [List]. ## ```roc @@ -248,12 +248,12 @@ single = \k, v -> ## with the same capacity of the list and walk it calling [Dict.insert] from_list : List (k, v) -> Dict k v from_list = \data -> - List.walk(data, empty({}), \dict, (k, v) -> insert(dict, k, v)) + List.walk(data, empty(), \dict, (k, v) -> insert(dict, k, v)) ## Returns the number of values in the dictionary. ## ```roc ## expect -## Dict.empty({}) +## Dict.empty() ## |> Dict.insert("One", "A Song") ## |> Dict.insert("Two", "Candy Canes") ## |> Dict.insert("Three", "Boughs of Holly") @@ -266,9 +266,9 @@ len = \@Dict({ data }) -> ## Check if the dictionary is empty. ## ```roc -## Dict.is_empty(Dict.empty({}) |> Dict.insert("key", 42)) +## Dict.is_empty(Dict.empty() |> Dict.insert("key", 42)) ## -## Dict.is_empty(Dict.empty({})) +## Dict.is_empty(Dict.empty()) ## ``` is_empty : Dict * * -> Bool is_empty = \@Dict({ data }) -> @@ -277,7 +277,7 @@ is_empty = \@Dict({ data }) -> ## Clears all elements from a dictionary keeping around the allocation if it isn't huge. ## ```roc ## songs = -## Dict.empty({}) +## Dict.empty() ## |> Dict.insert("One", "A Song") ## |> Dict.insert("Two", "Candy Canes") ## |> Dict.insert("Three", "Boughs of Holly") @@ -334,7 +334,7 @@ join_map = \dict, transform -> ## initial `state` value provided for the first call. ## ```roc ## expect -## Dict.empty({}) +## Dict.empty() ## |> Dict.insert("Apples", 12) ## |> Dict.insert("Orange", 24) ## |> Dict.walk(0, (\count, _, qty -> count + qty)) @@ -357,7 +357,7 @@ walk = \@Dict({ data }), initial_state, transform -> ## if returning `Break` earlier than the last element is expected to be common. ## ```roc ## people = -## Dict.empty({}) +## Dict.empty() ## |> Dict.insert("Alice", 17) ## |> Dict.insert("Bob", 18) ## |> Dict.insert("Charlie", 19) @@ -379,7 +379,7 @@ walk_until = \@Dict({ data }), initial_state, transform -> ## Run the given function on each key-value pair of a dictionary, and return ## a dictionary with just the pairs for which the function returned `Bool.true`. ## ```roc -## expect Dict.empty({}) +## expect Dict.empty() ## |> Dict.insert("Alice", 17) ## |> Dict.insert("Bob", 18) ## |> Dict.insert("Charlie", 19) @@ -405,7 +405,7 @@ keep_if_help = \@Dict(dict), predicate, index, length -> ## Run the given function on each key-value pair of a dictionary, and return ## a dictionary with just the pairs for which the function returned `Bool.false`. ## ```roc -## expect Dict.empty({}) +## expect Dict.empty() ## |> Dict.insert("Alice", 17) ## |> Dict.insert("Bob", 18) ## |> Dict.insert("Charlie", 19) @@ -421,7 +421,7 @@ drop_if = \dict, predicate -> ## will return [Ok value], otherwise return [Err KeyNotFound]. ## ```roc ## dictionary = -## Dict.empty({}) +## Dict.empty() ## |> Dict.insert(1,s "Apple") ## |> Dict.insert(2,s "Orange") ## @@ -436,7 +436,7 @@ get = \dict, key -> ## Check if the dictionary has a value for a specified key. ## ```roc ## expect -## Dict.empty({}) +## Dict.empty() ## |> Dict.insert(1234, "5678") ## |> Dict.contains(1234) ## |> Bool.is_eq(Bool.true) @@ -450,7 +450,7 @@ contains = \dict, key -> ## Insert a value into the dictionary at a specified key. ## ```roc ## expect -## Dict.empty({}) +## Dict.empty() ## |> Dict.insert("Apples", 12) ## |> Dict.get("Apples") ## |> Bool.is_eq(Ok(12)) @@ -495,7 +495,7 @@ insert_helper = \buckets0, data0, bucket_index0, dist_and_fingerprint0, key, val ## Remove a value from the dictionary for a specified key. ## ```roc ## expect -## Dict.empty({}) +## Dict.empty() ## |> Dict.insert("Some", "Value") ## |> Dict.remove("Some") ## |> Dict.len @@ -538,9 +538,9 @@ remove_helper = \buckets, bucket_index, dist_and_fingerprint, data, key -> ## Err Missing -> Ok(Bool.false) ## Ok value -> if value then Err(Missing) else Ok(Bool.true) ## -## expect Dict.update(Dict.empty({}), "a", alter_value) == Dict.single("a", Bool.false) +## expect Dict.update(Dict.empty(), "a", alter_value) == Dict.single("a", Bool.false) ## expect Dict.update(Dict.single("a", Bool.false), "a", alter_value) == Dict.single("a", Bool.true) -## expect Dict.update(Dict.single("a", Bool.true), "a", alter_value) == Dict.empty({}) +## expect Dict.update(Dict.single("a", Bool.true), "a", alter_value) == Dict.empty() ## ``` update : Dict k v, k, (Result v [Missing] -> Result v [Missing]) -> Dict k v update = \@Dict({ buckets, data, max_bucket_capacity, max_load_factor, shifts }), key, alter -> @@ -987,7 +987,7 @@ bucket_index_from_hash = \hash, shifts -> expect val = - empty({}) + empty() |> insert("foo", "bar") |> get("foo") @@ -995,12 +995,12 @@ expect expect dict1 = - empty({}) + empty() |> insert(1, "bar") |> insert(2, "baz") dict2 = - empty({}) + empty() |> insert(2, "baz") |> insert(1, "bar") @@ -1008,12 +1008,12 @@ expect expect dict1 = - empty({}) + empty() |> insert(1, "bar") |> insert(2, "baz") dict2 = - empty({}) + empty() |> insert(1, "bar") |> insert(2, "baz!") @@ -1021,17 +1021,17 @@ expect expect inner1 = - empty({}) + empty() |> insert(1, "bar") |> insert(2, "baz") inner2 = - empty({}) + empty() |> insert(2, "baz") |> insert(1, "bar") outer = - empty({}) + empty() |> insert(inner1, "wrong") |> insert(inner2, "right") @@ -1039,28 +1039,28 @@ expect expect inner1 = - empty({}) + empty() |> insert(1, "bar") |> insert(2, "baz") inner2 = - empty({}) + empty() |> insert(2, "baz") |> insert(1, "bar") outer1 = - empty({}) + empty() |> insert(inner1, "val") outer2 = - empty({}) + empty() |> insert(inner2, "val") outer1 == outer2 expect val = - empty({}) + empty() |> insert("foo", "bar") |> insert("foo", "baz") |> get("foo") @@ -1069,23 +1069,23 @@ expect expect val = - empty({}) + empty() |> insert("foo", "bar") |> get("bar") val == Err(KeyNotFound) expect - empty({}) - |> insert("foo", {}) + empty() + |> insert("foo", ()) |> contains("foo") expect dict = - empty({}) - |> insert("foo", {}) - |> insert("bar", {}) - |> insert("baz", {}) + empty() + |> insert("foo", ()) + |> insert("bar", ()) + |> insert("baz", ()) contains(dict, "baz") && !(contains(dict, "other")) @@ -1110,7 +1110,7 @@ expect # Reach capacity, no rehash. expect val = - empty({}) + empty() |> insert("a", 0) |> insert("b", 1) |> insert("c", 2) @@ -1130,7 +1130,7 @@ expect # Reach capacity, all elements still exist expect dict = - empty({}) + empty() |> insert("a", 0) |> insert("b", 1) |> insert("c", 2) @@ -1160,7 +1160,7 @@ expect # Force rehash. expect val = - empty({}) + empty() |> insert("a", 0) |> insert("b", 1) |> insert("c", 2) @@ -1181,7 +1181,7 @@ expect # Force rehash, all elements still exist expect dict = - empty({}) + empty() |> insert("a", 0) |> insert("b", 1) |> insert("c", 2) @@ -1211,7 +1211,7 @@ expect && (get(dict, "m") == Ok(12)) expect - empty({}) + empty() |> insert("Some", "Value") |> remove("Some") |> len @@ -1249,7 +1249,7 @@ expect dict = List.walk( bad_keys, - Dict.empty({}), + Dict.empty(), \acc, k -> Dict.update( acc, @@ -1296,13 +1296,13 @@ LowLevelHasher := { initialized_seed : U64, state : U64 } implements [ # Returns a application specific pseudo random seed for Dict. # This avoids trivial DOS attacks. -pseudo_seed : {} -> U64 +pseudo_seed : () -> U64 create_low_level_hasher : [PseudoRandSeed, WithSeed U64] -> LowLevelHasher create_low_level_hasher = \seed_opt -> seed = when seed_opt is - PseudoRandSeed -> pseudo_seed({}) + PseudoRandSeed -> pseudo_seed() WithSeed(s) -> s @LowLevelHasher({ initialized_seed: init_seed(seed), state: seed }) @@ -1686,12 +1686,12 @@ expect hash1 != hash2 expect - empty({}) + empty() |> len |> Bool.is_eq(0) expect - empty({}) + empty() |> insert("One", "A Song") |> insert("Two", "Candy Canes") |> insert("Three", "Boughs of Holly") @@ -1700,7 +1700,7 @@ expect |> Bool.is_eq(0) expect - Dict.empty({}) + Dict.empty() |> Dict.insert("Alice", 17) |> Dict.insert("Bob", 18) |> Dict.insert("Charlie", 19) @@ -1709,14 +1709,14 @@ expect expect d1 = - Dict.empty({}) + Dict.empty() |> Dict.insert("Alice", 17) |> Dict.insert("Bob", 18) |> Dict.insert("Charlie", 19) |> Dict.keep_if(\(_k, v) -> v >= 18) d2 = - Dict.empty({}) + Dict.empty() |> Dict.insert("Bob", 18) |> Dict.insert("Charlie", 19) @@ -1724,14 +1724,14 @@ expect expect d1 = - Dict.empty({}) + Dict.empty() |> Dict.insert("Alice", 17) |> Dict.insert("Bob", 18) |> Dict.insert("Charlie", 19) |> Dict.keep_if(\(k, _v) -> Str.ends_with(k, "e")) d2 = - Dict.empty({}) + Dict.empty() |> Dict.insert("Alice", 17) |> Dict.insert("Charlie", 19) @@ -1740,7 +1740,7 @@ expect expect keys_to_delete = [1, 2] d1 = - Dict.empty({}) + Dict.empty() |> Dict.insert(0, 0) |> Dict.insert(1, 1) |> Dict.insert(2, 2) @@ -1749,7 +1749,7 @@ expect |> Dict.keep_if(\(k, _v) -> !(List.contains(keys_to_delete, k))) d2 = - Dict.empty({}) + Dict.empty() |> Dict.insert(0, 0) |> Dict.insert(3, 3) |> Dict.insert(4, 4) @@ -1759,7 +1759,7 @@ expect expect keys_to_delete = [2, 4] d1 = - Dict.empty({}) + Dict.empty() |> Dict.insert(0, 0) |> Dict.insert(1, 1) |> Dict.insert(2, 2) @@ -1768,7 +1768,7 @@ expect |> Dict.keep_if(\(k, _v) -> !(List.contains(keys_to_delete, k))) d2 = - Dict.empty({}) + Dict.empty() |> Dict.insert(0, 0) |> Dict.insert(1, 1) |> Dict.insert(3, 3) diff --git a/crates/compiler/builtins/roc/Inspect.roc b/crates/compiler/builtins/roc/Inspect.roc index f8839b33ca0..e1419a048c4 100644 --- a/crates/compiler/builtins/roc/Inspect.roc +++ b/crates/compiler/builtins/roc/Inspect.roc @@ -44,7 +44,7 @@ KeyValWalker state collection key val : collection, state, (state, key, val -> s ElemWalker state collection elem : collection, state, (state, elem -> state) -> state InspectFormatter implements - init : {} -> f where f implements InspectFormatter + init : () -> f where f implements InspectFormatter tag : Str, List (Inspector f) -> Inspector f where f implements InspectFormatter tuple : List (Inspector f) -> Inspector f where f implements InspectFormatter @@ -92,7 +92,7 @@ Inspect implements inspect : val -> f where val implements Inspect, f implements InspectFormatter inspect = \val -> @Inspector(val_fn) = to_inspector(val) - val_fn(init({})) + val_fn(init()) to_str : val -> Str where val implements Inspect to_str = \val -> @@ -133,8 +133,8 @@ DbgFormatter := { data : Str } }, ] -dbg_init : {} -> DbgFormatter -dbg_init = \{} -> @DbgFormatter({ data: "" }) +dbg_init : () -> DbgFormatter +dbg_init = \() -> @DbgFormatter({ data: "" }) dbg_list : list, ElemWalker (DbgFormatter, Bool) list elem, (elem -> Inspector DbgFormatter) -> Inspector DbgFormatter dbg_list = \content, walk_fn, to_dbg_inspector -> diff --git a/crates/compiler/builtins/roc/List.roc b/crates/compiler/builtins/roc/List.roc index c7aa005c5b0..7f605377356 100644 --- a/crates/compiler/builtins/roc/List.roc +++ b/crates/compiler/builtins/roc/List.roc @@ -617,29 +617,29 @@ product = \list -> ## any of the elements satisfy it. any : List a, (a -> Bool) -> Bool any = \list, predicate -> - looper = \{}, element -> + looper = \(), element -> if predicate(element) then - Break({}) + Break() else - Continue({}) + Continue() - when List.iterate(list, {}, looper) is - Continue({}) -> Bool.false - Break({}) -> Bool.true + when List.iterate(list, (), looper) is + Continue() -> Bool.false + Break() -> Bool.true ## Run the given predicate on each element of the list, returning `Bool.true` if ## all of the elements satisfy it. all : List a, (a -> Bool) -> Bool all = \list, predicate -> - looper = \{}, element -> + looper = \(), element -> if predicate(element) then - Continue({}) + Continue() else - Break({}) + Break() - when List.iterate(list, {}, looper) is - Continue({}) -> Bool.true - Break({}) -> Bool.false + when List.iterate(list, (), looper) is + Continue() -> Bool.true + Break() -> Bool.false ## Run the given function on each element of a list, and return all the ## elements for which the function returned `Bool.true`. @@ -1140,10 +1140,10 @@ find_first = \list, pred -> if pred(elem) then Break(elem) else - Continue({}) + Continue() - when List.iterate(list, {}, callback) is - Continue({}) -> Err(NotFound) + when List.iterate(list, (), callback) is + Continue() -> Err(NotFound) Break(found) -> Ok(found) ## Returns the last element of the list satisfying a predicate function. @@ -1154,10 +1154,10 @@ find_last = \list, pred -> if pred(elem) then Break(elem) else - Continue({}) + Continue() - when List.iterate_backwards(list, {}, callback) is - Continue({}) -> Err(NotFound) + when List.iterate_backwards(list, (), callback) is + Continue() -> Err(NotFound) Break(found) -> Ok(found) ## Returns the index at which the first element in the list @@ -1467,11 +1467,11 @@ expect (List.concat_utf8([1, 2, 3, 4], "🐦")) == [1, 2, 3, 4, 240, 159, 144, 1 ## ``` ## ## If the function might fail or you need to return early, use [for_each_try!]. -for_each! : List a, (a => {}) => {} +for_each! : List a, (a => ()) => () for_each! = \list, func! -> when list is [] -> - {} + () [elem, .. as rest] -> func!(elem) @@ -1488,15 +1488,15 @@ for_each! = \list, func! -> ## Stdout.line!("${path} deleted") ## ) ## ``` -for_each_try! : List a, (a => Result {} err) => Result {} err +for_each_try! : List a, (a => Result () err) => Result () err for_each_try! = \list, func! -> when list is [] -> - Ok({}) + Ok() [elem, .. as rest] -> when func!(elem) is - Ok({}) -> + Ok() -> for_each_try!(rest, func!) Err(err) -> @@ -1506,7 +1506,7 @@ for_each_try! = \list, func! -> ## ## ```roc ## now_multiples = List.walk!([1, 2, 3], [], \nums, i -> -## now = Utc.now!({}) |> Utc.to_millis_since_epoch +## now = Utc.now!() |> Utc.to_millis_since_epoch ## List.append(nums, now * i) ## ) ## ``` @@ -1533,7 +1533,7 @@ walk! = \list, state, func! -> ## [], ## \accumulator, which -> ## Stdout.write!("${which} name: ")? -## name = Stdin.line!({})? +## name = Stdin.line!()? ## Ok(List.append(accumulator, name)), ## )? ## ``` diff --git a/crates/compiler/builtins/roc/Set.roc b/crates/compiler/builtins/roc/Set.roc index 63d00a60133..ec1182a19ed 100644 --- a/crates/compiler/builtins/roc/Set.roc +++ b/crates/compiler/builtins/roc/Set.roc @@ -33,7 +33,7 @@ import Inspect exposing [Inspect, Inspector, InspectFormatter] ## Provides a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type)) ## type which stores a collection of unique values, without any ordering -Set k := Dict.Dict k {} where k implements Hash & Eq +Set k := Dict.Dict k () where k implements Hash & Eq implements [ Eq { is_eq, @@ -73,13 +73,13 @@ to_inspector_set = \set -> ## Creates a new empty `Set`. ## ```roc -## empty_set = Set.empty({}) +## empty_set = Set.empty() ## count_values = Set.len(empty_set) ## ## expect count_values == 0 ## ``` -empty : {} -> Set * -empty = \{} -> @Set(Dict.empty({})) +empty : () -> Set * +empty = \() -> @Set(Dict.empty()) ## Return a set with space allocated for a number of entries. This ## may provide a performance optimization if you know how many entries will be @@ -109,12 +109,12 @@ release_excess_capacity = \@Set(dict) -> ## ``` single : k -> Set k single = \key -> - Dict.single(key, {}) |> @Set + Dict.single(key, ()) |> @Set ## Insert a value into a `Set`. ## ```roc ## few_item_set = -## Set.empty({}) +## Set.empty() ## |> Set.insert("Apple") ## |> Set.insert("Pear") ## |> Set.insert("Banana") @@ -125,19 +125,19 @@ single = \key -> ## ``` insert : Set k, k -> Set k insert = \@Set(dict), key -> - Dict.insert(dict, key, {}) |> @Set + Dict.insert(dict, key, ()) |> @Set # Inserting a duplicate key has no effect. expect actual = - empty({}) + empty() |> insert("foo") |> insert("bar") |> insert("foo") |> insert("baz") expected = - empty({}) + empty() |> insert("foo") |> insert("bar") |> insert("baz") @@ -147,7 +147,7 @@ expect ## Counts the number of values in a given `Set`. ## ```roc ## few_item_set = -## Set.empty({}) +## Set.empty() ## |> Set.insert("Apple") ## |> Set.insert("Pear") ## |> Set.insert("Banana") @@ -163,7 +163,7 @@ len = \@Set(dict) -> ## Returns the max number of elements the set can hold before requiring a rehash. ## ```roc ## food_set = -## Set.empty({}) +## Set.empty() ## |> Set.insert("apple") ## ## capacity_of_set = Set.capacity(food_set) @@ -174,9 +174,9 @@ capacity = \@Set(dict) -> ## Check if the set is empty. ## ```roc -## Set.is_empty(Set.empty({}) |> Set.insert(42)) +## Set.is_empty(Set.empty() |> Set.insert(42)) ## -## Set.is_empty(Set.empty({})) +## Set.is_empty(Set.empty()) ## ``` is_empty : Set * -> Bool is_empty = \@Set(dict) -> @@ -185,7 +185,7 @@ is_empty = \@Set(dict) -> # Inserting a duplicate key has no effect on length. expect actual = - empty({}) + empty() |> insert("foo") |> insert("bar") |> insert("foo") @@ -197,7 +197,7 @@ expect ## Removes the value from the given `Set`. ## ```roc ## numbers = -## Set.empty({}) +## Set.empty() ## |> Set.insert(10) ## |> Set.insert(20) ## |> Set.remove(10) @@ -247,7 +247,7 @@ to_list = \@Set(dict) -> ## Create a `Set` from a `List` of values. ## ```roc ## values = -## Set.empty({}) +## Set.empty() ## |> Set.insert(Banana) ## |> Set.insert(Apple) ## |> Set.insert(Pear) @@ -257,7 +257,7 @@ to_list = \@Set(dict) -> from_list : List k -> Set k from_list = \list -> list - |> List.map(\k -> (k, {})) + |> List.map(\k -> (k, ())) |> Dict.from_list |> @Set diff --git a/crates/compiler/can/src/annotation.rs b/crates/compiler/can/src/annotation.rs index 49959fb387d..e638444afed 100644 --- a/crates/compiler/can/src/annotation.rs +++ b/crates/compiler/can/src/annotation.rs @@ -901,7 +901,7 @@ fn can_annotation_help( local_aliases, references, ext, - roc_problem::can::ExtensionTypeKind::Record, + roc_problem::can::ExtensionTypeKind::Tuple, ); debug_assert!( @@ -910,24 +910,36 @@ fn can_annotation_help( ); if elems.is_empty() { - env.problem(roc_problem::can::Problem::EmptyTupleType(region)); - } + match ext { + Some(_) => { + // just `a` does not mean the same as `()a`, so even + // if there are no fields, still make this a `Tuple`, + // not an EmptyTuple + Type::Tuple( + Default::default(), + TypeExtension::from_type(ext_type, is_implicit_openness), + ) + } - let elem_types = can_assigned_tuple_elems( - env, - pol, - &elems.items, - scope, - var_store, - introduced_variables, - local_aliases, - references, - ); + None => Type::EmptyTuple, + } + } else { + let elem_types = can_assigned_tuple_elems( + env, + pol, + &elems.items, + scope, + var_store, + introduced_variables, + local_aliases, + references, + ); - Type::Tuple( - elem_types, - TypeExtension::from_type(ext_type, is_implicit_openness), - ) + Type::Tuple( + elem_types, + TypeExtension::from_type(ext_type, is_implicit_openness), + ) + } } Record { fields, ext } => { let (ext_type, is_implicit_openness) = can_extension_type( @@ -939,7 +951,7 @@ fn can_annotation_help( local_aliases, references, ext, - roc_problem::can::ExtensionTypeKind::Record, + roc_problem::can::ExtensionTypeKind::Tuple, ); debug_assert!( @@ -1163,6 +1175,13 @@ fn can_extension_type( Type::EmptyRec | Type::Record(..) | Type::Variable(..) | Type::Error ) } + fn valid_tuple_ext_type(typ: &Type) -> bool { + // Include erroneous types so that we don't overreport errors. + matches!( + typ, + Type::EmptyTuple | Type::Tuple(..) | Type::Variable(..) | Type::Error + ) + } fn valid_tag_ext_type(typ: &Type) -> bool { matches!( typ, @@ -1173,6 +1192,7 @@ fn can_extension_type( use roc_problem::can::ExtensionTypeKind; let valid_extension_type: fn(&Type) -> bool = match ext_problem_kind { + ExtensionTypeKind::Tuple => valid_tuple_ext_type, ExtensionTypeKind::Record => valid_record_ext_type, ExtensionTypeKind::TagUnion => valid_tag_ext_type, }; @@ -1228,6 +1248,7 @@ fn can_extension_type( } None => match ext_problem_kind { ExtensionTypeKind::Record => (Type::EmptyRec, ExtImplicitOpenness::No), + ExtensionTypeKind::Tuple => (Type::EmptyTuple, ExtImplicitOpenness::No), ExtensionTypeKind::TagUnion => { // In negative positions a missing extension variable forces a closed tag union; // otherwise, open-in-output-position means we give the tag an inference variable. diff --git a/crates/compiler/can/src/copy.rs b/crates/compiler/can/src/copy.rs index ddaad2b01a2..268708a2297 100644 --- a/crates/compiler/can/src/copy.rs +++ b/crates/compiler/can/src/copy.rs @@ -535,6 +535,8 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr .collect(), }, + EmptyTuple => EmptyTuple, + RecordAccess { record_var, ext_var, @@ -990,7 +992,7 @@ fn deep_copy_type_vars( // Everything else is a mechanical descent. Structure(flat_type) => match flat_type { - EmptyRecord | EmptyTagUnion | EffectfulFunc => Structure(flat_type), + EmptyRecord | EmptyTuple | EmptyTagUnion | EffectfulFunc => Structure(flat_type), Apply(symbol, arguments) => { descend_slice!(arguments); diff --git a/crates/compiler/can/src/debug/pretty_print.rs b/crates/compiler/can/src/debug/pretty_print.rs index 5e21e5b52f0..3ef92b47ef5 100644 --- a/crates/compiler/can/src/debug/pretty_print.rs +++ b/crates/compiler/can/src/debug/pretty_print.rs @@ -366,6 +366,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, .append(f.text(")")) .group(), EmptyRecord => f.text("{}"), + EmptyTuple => f.text("()"), RecordAccess { loc_expr, field, .. } => expr(c, AppArg, f, &loc_expr.value) diff --git a/crates/compiler/can/src/desugar.rs b/crates/compiler/can/src/desugar.rs index 30bf100503b..8df68a4716c 100644 --- a/crates/compiler/can/src/desugar.rs +++ b/crates/compiler/can/src/desugar.rs @@ -1026,6 +1026,24 @@ pub fn desugar_expr<'a>( Expr::LowLevelTry(result_expr, ResultTryKind::KeywordPrefix), )) } + PncApply(loc_fn, pnc_args @ Collection { items: &[], .. }) => { + let mut args = Vec::with_capacity_in(1, env.arena); + args.push( + &*env + .arena + .alloc(Loc::at(loc_fn.region, Expr::Tuple(Collection::empty()))), + ); + let args = Collection::with_items_and_comments( + env.arena, + args.into_bump_slice(), + pnc_args.final_comments(), + ); + + env.arena.alloc(Loc { + value: PncApply(desugar_expr(env, scope, loc_fn), args), + region: loc_expr.region, + }) + } PncApply(loc_fn, loc_args) => { let mut desugared_args = Vec::with_capacity_in(loc_args.len(), env.arena); @@ -1360,6 +1378,17 @@ fn desugar_pattern<'a>(env: &mut Env<'a>, scope: &mut Scope, pattern: Pattern<'a Apply(tag, desugared_arg_patterns.into_bump_slice()) } + PncApply(tag, arg_patterns) if arg_patterns.is_empty() => { + let mut args = Vec::with_capacity_in(1, env.arena); + args.push(Loc::at(tag.region, Pattern::Tuple(Collection::empty()))); + let arg_patterns = Collection::with_items_and_comments( + env.arena, + args.into_bump_slice(), + arg_patterns.final_comments(), + ); + + PncApply(tag, arg_patterns) + } PncApply(tag, arg_patterns) => { // Skip desugaring the tag, it should either be a Tag or OpaqueRef let mut desugared_arg_patterns = Vec::with_capacity_in(arg_patterns.len(), env.arena); diff --git a/crates/compiler/can/src/exhaustive.rs b/crates/compiler/can/src/exhaustive.rs index 621dc407d5a..c6d92a7f00c 100644 --- a/crates/compiler/can/src/exhaustive.rs +++ b/crates/compiler/can/src/exhaustive.rs @@ -240,6 +240,16 @@ fn index_var( }; return Ok(std::iter::repeat(Variable::NULL).take(num_fields).collect()); } + FlatType::EmptyTuple => { + debug_assert!(matches!(ctor, IndexCtor::Tuple)); + debug_assert_eq!( + render_as, + &RenderAs::Tuple, + "tuple constructors must always be rendered as tuples" + ); + + return Ok(Vec::new()); + } FlatType::EmptyTagUnion => { internal_error!("empty tag unions are not indexable") } diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs index 8308f8a06bd..b89d70d3b34 100644 --- a/crates/compiler/can/src/expr.rs +++ b/crates/compiler/can/src/expr.rs @@ -236,6 +236,9 @@ pub enum Expr { elems: Vec<(Variable, Box>)>, }, + /// Empty tuple constant + EmptyTuple, + /// Module params expression in import ImportParams(ModuleId, Region, Option<(Variable, Box)>), @@ -389,6 +392,7 @@ impl Expr { Self::ForeignCall { .. } => Category::ForeignCall, Self::Closure(..) => Category::Lambda, Self::Tuple { .. } => Category::Tuple, + Self::EmptyTuple => Category::Tuple, Self::Record { .. } => Category::Record, Self::EmptyRecord => Category::Record, Self::RecordAccess { field, .. } => Category::RecordAccess(field.clone()), @@ -436,6 +440,7 @@ impl Expr { | Self::ParamsVar { .. } | Self::Closure(..) | Self::EmptyRecord + | Self::EmptyTuple | Self::RecordAccessor(_) | Self::ZeroArgumentTag { .. } | Self::OpaqueWrapFunction(_) @@ -994,27 +999,31 @@ pub fn canonicalize_expr<'a>( let mut can_elems = Vec::with_capacity(fields.len()); let mut references = References::new(); - for loc_elem in fields.iter() { - let (can_expr, elem_out) = - canonicalize_expr(env, var_store, scope, loc_elem.region, &loc_elem.value); + if fields.is_empty() { + (EmptyTuple, Output::default()) + } else { + for loc_elem in fields.iter() { + let (can_expr, elem_out) = + canonicalize_expr(env, var_store, scope, loc_elem.region, &loc_elem.value); - references.union_mut(&elem_out.references); + references.union_mut(&elem_out.references); - can_elems.push((var_store.fresh(), Box::new(can_expr))); - } + can_elems.push((var_store.fresh(), Box::new(can_expr))); + } - let output = Output { - references, - ..Default::default() - }; + let output = Output { + references, + ..Default::default() + }; - ( - Tuple { - tuple_var: var_store.fresh(), - elems: can_elems, - }, - output, - ) + ( + Tuple { + tuple_var: var_store.fresh(), + elems: can_elems, + }, + output, + ) + } } ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal), @@ -3162,6 +3171,7 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec { | Expr::RecordAccessor(_) | Expr::SingleQuote(..) | Expr::EmptyRecord + | Expr::EmptyTuple | Expr::RuntimeError(_) | Expr::ImportParams(_, _, None) | Expr::OpaqueWrapFunction(_) => {} diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs index fd61d0682b5..a7fc08eebb6 100644 --- a/crates/compiler/can/src/module.rs +++ b/crates/compiler/can/src/module.rs @@ -1072,6 +1072,7 @@ fn fix_values_captured_in_closure_expr( | ParamsVar { .. } | AbilityMember(..) | EmptyRecord + | EmptyTuple | RuntimeError(_) | ZeroArgumentTag { .. } | RecordAccessor { .. } => {} diff --git a/crates/compiler/can/src/pattern.rs b/crates/compiler/can/src/pattern.rs index 6df8e56bc80..a23e8cb3232 100644 --- a/crates/compiler/can/src/pattern.rs +++ b/crates/compiler/can/src/pattern.rs @@ -181,6 +181,7 @@ impl Pattern { UnwrappedOpaque { opaque, .. } => C::Opaque(*opaque), RecordDestructure { destructs, .. } if destructs.is_empty() => C::EmptyRecord, RecordDestructure { .. } => C::Record, + TupleDestructure { destructs, .. } if destructs.is_empty() => C::EmptyTuple, TupleDestructure { .. } => C::Tuple, List { .. } => C::List, NumLiteral(..) => C::Num, diff --git a/crates/compiler/can/src/traverse.rs b/crates/compiler/can/src/traverse.rs index f01e8ed2cbd..23100974656 100644 --- a/crates/compiler/can/src/traverse.rs +++ b/crates/compiler/can/src/traverse.rs @@ -317,7 +317,7 @@ pub fn walk_expr(visitor: &mut V, expr: &Expr, var: Variable) { } => elems .iter() .for_each(|(var, elem)| visitor.visit_expr(&elem.value, elem.region, *var)), - Expr::EmptyRecord => { /* terminal */ } + Expr::EmptyRecord | Expr::EmptyTuple => { /* terminal */ } Expr::ImportParams(_, region, Some((_, expr))) => visitor.visit_expr(expr, *region, var), Expr::ImportParams(_, _, None) => { /* terminal */ } Expr::RecordAccess { diff --git a/crates/compiler/checkmate/schema.json b/crates/compiler/checkmate/schema.json index 23d33cc889a..64a4056ccfa 100644 --- a/crates/compiler/checkmate/schema.json +++ b/crates/compiler/checkmate/schema.json @@ -500,6 +500,20 @@ } } }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "EmptyTuple" + ] + } + } + }, { "type": "object", "required": [ diff --git a/crates/compiler/checkmate/src/convert.rs b/crates/compiler/checkmate/src/convert.rs index 566d24b94cf..e121d622065 100644 --- a/crates/compiler/checkmate/src/convert.rs +++ b/crates/compiler/checkmate/src/convert.rs @@ -126,6 +126,7 @@ impl AsSchema for subs::FlatType { ext.as_schema(subs), ), subs::FlatType::EmptyRecord => Content::EmptyRecord(), + subs::FlatType::EmptyTuple => Content::EmptyTuple(), subs::FlatType::EmptyTagUnion => Content::EmptyTagUnion(), subs::FlatType::EffectfulFunc => Content::EffectfulFunc(), } diff --git a/crates/compiler/checkmate_schema/src/lib.rs b/crates/compiler/checkmate_schema/src/lib.rs index 4bb5af6faea..0e422c97f4d 100644 --- a/crates/compiler/checkmate_schema/src/lib.rs +++ b/crates/compiler/checkmate_schema/src/lib.rs @@ -94,6 +94,7 @@ impl_content! { extension: TagUnionExtension, }, EmptyRecord {}, + EmptyTuple {}, EmptyTagUnion {}, EffectfulFunc {}, RangedNumber { diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index f628ba42b13..4dcdd22d9e7 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -307,6 +307,7 @@ pub fn constrain_expr( float_literal(types, constraints, var, precision, expected, region, bound) } EmptyRecord => constrain_empty_record(types, constraints, region, expected), + Expr::EmptyTuple => constrain_empty_tuple(types, constraints, region, expected), Expr::Record { record_var, fields } => { if fields.is_empty() { constrain_empty_record(types, constraints, region, expected) @@ -361,49 +362,53 @@ pub fn constrain_expr( } } Expr::Tuple { tuple_var, elems } => { - let mut elem_types = VecMap::with_capacity(elems.len()); - let mut elem_vars = Vec::with_capacity(elems.len()); + if elems.is_empty() { + constrain_empty_tuple(types, constraints, region, expected) + } else { + let mut elem_types = VecMap::with_capacity(elems.len()); + let mut elem_vars = Vec::with_capacity(elems.len()); - // Constraints need capacity for each elem - // + 1 for the tuple itself + 1 for tuple var - let mut tuple_constraints = Vec::with_capacity(2 + elems.len()); + // Constraints need capacity for each elem + // + 1 for the tuple itself + 1 for tuple var + let mut tuple_constraints = Vec::with_capacity(2 + elems.len()); - for (i, (elem_var, loc_expr)) in elems.iter().enumerate() { - let elem_type = constraints.push_variable(*elem_var); - let elem_expected = constraints.push_expected_type(NoExpectation(elem_type)); - let elem_con = constrain_expr( - types, - constraints, - env, - loc_expr.region, - &loc_expr.value, - elem_expected, - ); + for (i, (elem_var, loc_expr)) in elems.iter().enumerate() { + let elem_type = constraints.push_variable(*elem_var); + let elem_expected = constraints.push_expected_type(NoExpectation(elem_type)); + let elem_con = constrain_expr( + types, + constraints, + env, + loc_expr.region, + &loc_expr.value, + elem_expected, + ); - elem_vars.push(*elem_var); - elem_types.insert(i, Variable(*elem_var)); + elem_vars.push(*elem_var); + elem_types.insert(i, Variable(*elem_var)); - tuple_constraints.push(elem_con); - } + tuple_constraints.push(elem_con); + } - let tuple_type = { - let typ = types.from_old_type(&Type::Tuple(elem_types, TypeExtension::Closed)); - constraints.push_type(types, typ) - }; + let tuple_type = { + let typ = types.from_old_type(&Type::Tuple(elem_types, TypeExtension::Closed)); + constraints.push_type(types, typ) + }; - let tuple_con = constraints.equal_types_with_storage( - tuple_type, - expected, - Category::Tuple, - region, - *tuple_var, - ); + let tuple_con = constraints.equal_types_with_storage( + tuple_type, + expected, + Category::Tuple, + region, + *tuple_var, + ); - tuple_constraints.push(tuple_con); - elem_vars.push(*tuple_var); + tuple_constraints.push(tuple_con); + elem_vars.push(*tuple_var); - let and_constraint = constraints.and_constraint(tuple_constraints); - constraints.exists(elem_vars, and_constraint) + let and_constraint = constraints.and_constraint(tuple_constraints); + constraints.exists(elem_vars, and_constraint) + } } RecordUpdate { record_var, @@ -2692,6 +2697,17 @@ fn constrain_empty_record( constraints.equal_types(record_type_index, expected, Category::Record, region) } +#[inline(always)] +fn constrain_empty_tuple( + types: &mut Types, + constraints: &mut Constraints, + region: Region, + expected: ExpectedTypeIndex, +) -> Constraint { + let tuple_type_index = constraints.push_type(types, Types::EMPTY_TUPLE); + constraints.equal_types(tuple_type_index, expected, Category::Tuple, region) +} + fn add_host_annotation( types: &mut Types, constraints: &mut Constraints, @@ -2960,7 +2976,7 @@ fn constrain_typed_def( // instead of the more generic "something is wrong with the body of `f`" match (&def.loc_expr.value, types[signature]) { ( - Closure(ClosureData { + Expr::Closure(ClosureData { function_type: fn_var, closure_type: closure_var, return_type: ret_var, @@ -3559,18 +3575,20 @@ fn constrain_stmt_def( let loc_fn_expr = &boxed.1; match loc_fn_expr.value { - Var(symbol, _) | ParamsVar { symbol, .. } => (Some(symbol), loc_fn_expr.region), + Expr::Var(symbol, _) | Expr::ParamsVar { symbol, .. } => { + (Some(symbol), loc_fn_expr.region) + } _ => (None, def.loc_expr.region), } } else { (None, def.loc_expr.region) }; - // Statement expressions must return an empty record - let empty_record_index = constraints.push_type(types, Types::EMPTY_RECORD); - let expect_empty_record = constraints.push_expected_type(ForReason( + // Statement expressions must return an empty tuple + let empty_tuple_index = constraints.push_type(types, Types::EMPTY_TUPLE); + let expect_empty_tuple = constraints.push_expected_type(ForReason( Reason::Stmt(fn_name), - empty_record_index, + empty_tuple_index, error_region, )); @@ -3581,7 +3599,7 @@ fn constrain_stmt_def( env, region, &def.loc_expr.value, - expect_empty_record, + expect_empty_tuple, ) }); @@ -4451,7 +4469,9 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool { return true; } RuntimeError(roc_problem::can::RuntimeError::NoImplementation) - | RuntimeError(roc_problem::can::RuntimeError::NoImplementationNamed { .. }) => { + | RuntimeError(roc_problem::can::RuntimeError::NoImplementationNamed { + .. + }) => { // Allow generalization of signatures with no implementation return true; } @@ -4470,6 +4490,7 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool { | RunLowLevel { .. } | ForeignCall { .. } | EmptyRecord + | Expr::EmptyTuple { .. } | Expr::Record { .. } | Expr::Tuple { .. } | Crash { .. } @@ -4599,7 +4620,7 @@ fn rec_defs_help( // instead of the more generic "something is wrong with the body of `f`" match (&def.loc_expr.value, types[signature]) { ( - Closure(ClosureData { + Expr::Closure(ClosureData { function_type: fn_var, closure_type: closure_var, return_type: ret_var, diff --git a/crates/compiler/constrain/src/pattern.rs b/crates/compiler/constrain/src/pattern.rs index 8c2d06c08c1..e6ff173df41 100644 --- a/crates/compiler/constrain/src/pattern.rs +++ b/crates/compiler/constrain/src/pattern.rs @@ -131,8 +131,12 @@ fn headers_from_annotation_help( } } - TupleDestructure { destructs: _, .. } => { - todo!(); + TupleDestructure { destructs, .. } => { + let dealiased = types.shallow_dealias(annotation.value); + match types[dealiased] { + TypeTag::EmptyTuple => destructs.is_empty(), + _ => todo!(), + } } List { patterns, .. } => { @@ -571,24 +575,24 @@ pub fn constrain_pattern_help( }; let whole_var_index = constraints.push_variable(*whole_var); - let expected_record = + let expected_tuple = constraints.push_expected_type(Expected::NoExpectation(tuple_type)); let whole_con = constraints.equal_types( whole_var_index, - expected_record, + expected_tuple, Category::Storage(std::file!(), std::line!()), region, ); - let record_con = constraints.pattern_presence( + let tuple_con = constraints.pattern_presence( whole_var_index, expected, - PatternCategory::Record, + PatternCategory::Tuple, region, ); state.constraints.push(whole_con); - state.constraints.push(record_con); + state.constraints.push(tuple_con); } RecordDestructure { @@ -966,7 +970,10 @@ fn could_be_a_tag_union(types: &Types, typ: TypeOrVar) -> bool { match typ.split() { Ok(typ_index) => !matches!( types[typ_index], - TypeTag::Apply { .. } | TypeTag::Function(..) | TypeTag::Record(..) + TypeTag::Apply { .. } + | TypeTag::Function(..) + | TypeTag::Tuple(..) + | TypeTag::Record(..) ), Err(_) => { // Variables are opaque at this point, assume yes diff --git a/crates/compiler/derive_key/src/decoding.rs b/crates/compiler/derive_key/src/decoding.rs index c151ce061a3..ae03d93c0a6 100644 --- a/crates/compiler/derive_key/src/decoding.rs +++ b/crates/compiler/derive_key/src/decoding.rs @@ -66,7 +66,9 @@ impl FlatDecodable { FlatType::Tuple(elems, ext) => { let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext); - check_derivable_ext_var(subs, ext, |_| false)?; + check_derivable_ext_var(subs, ext, |ext| { + matches!(ext, Content::Structure(FlatType::EmptyTuple)) + })?; Ok(Key(FlatDecodableKey::Tuple(elems_iter.count() as _))) } @@ -77,6 +79,7 @@ impl FlatDecodable { Err(Underivable) // yet } FlatType::EmptyRecord => Ok(Key(FlatDecodableKey::Record(vec![]))), + FlatType::EmptyTuple => Ok(Key(FlatDecodableKey::Tuple(0))), FlatType::EmptyTagUnion => { Err(Underivable) // yet } diff --git a/crates/compiler/derive_key/src/encoding.rs b/crates/compiler/derive_key/src/encoding.rs index f30d3788ab9..45c51926d50 100644 --- a/crates/compiler/derive_key/src/encoding.rs +++ b/crates/compiler/derive_key/src/encoding.rs @@ -73,7 +73,9 @@ impl FlatEncodable { let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext); // TODO someday we can put #[cfg(debug_assertions)] around this, but for now let's always do it. - check_derivable_ext_var(subs, ext, |_| false)?; + check_derivable_ext_var(subs, ext, |ext| { + matches!(ext, Content::Structure(FlatType::EmptyTuple)) + })?; Ok(Key(FlatEncodableKey::Tuple(elems_iter.count() as _))) } @@ -116,6 +118,7 @@ impl FlatEncodable { ))) } FlatType::EmptyRecord => Ok(Key(FlatEncodableKey::Record(vec![]))), + FlatType::EmptyTuple => Ok(Key(FlatEncodableKey::Tuple(0))), FlatType::EmptyTagUnion => Ok(Key(FlatEncodableKey::TagUnion(vec![]))), FlatType::Func(..) | FlatType::EffectfulFunc => Err(Underivable), }, diff --git a/crates/compiler/derive_key/src/hash.rs b/crates/compiler/derive_key/src/hash.rs index c196ecd1513..aa146d24f27 100644 --- a/crates/compiler/derive_key/src/hash.rs +++ b/crates/compiler/derive_key/src/hash.rs @@ -70,7 +70,9 @@ impl FlatHash { FlatType::Tuple(elems, ext) => { let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext); - check_derivable_ext_var(subs, ext, |_| false)?; + check_derivable_ext_var(subs, ext, |ext| { + matches!(ext, Content::Structure(FlatType::EmptyTuple)) + })?; Ok(Key(FlatHashKey::Tuple(elems_iter.count() as _))) } @@ -110,8 +112,9 @@ impl FlatHash { .collect(), ))), FlatType::EmptyRecord => Ok(Key(FlatHashKey::Record(vec![]))), + FlatType::EmptyTuple => Ok(Key(FlatHashKey::Tuple(0))), FlatType::EmptyTagUnion => Ok(Key(FlatHashKey::TagUnion(vec![]))), - // + FlatType::Func(..) | FlatType::EffectfulFunc => Err(Underivable), }, Content::Alias(sym, _, real_var, _) => match builtin_symbol_to_hash_lambda(sym) { diff --git a/crates/compiler/derive_key/src/inspect.rs b/crates/compiler/derive_key/src/inspect.rs index 3bf22c9177e..16d6a317960 100644 --- a/crates/compiler/derive_key/src/inspect.rs +++ b/crates/compiler/derive_key/src/inspect.rs @@ -82,8 +82,8 @@ impl FlatInspectable { let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext); // TODO someday we can put #[cfg(debug_assertions)] around this, but for now let's always do it. - check_derivable_ext_var(subs, ext, |_| { - false + check_derivable_ext_var(subs, ext, |ext| { + matches!(ext, Content::Structure(FlatType::EmptyTuple)) }).expect("Compiler error: unexpected nonempty ext var when deriving Inspect for tuple"); Key(FlatInspectableKey::Tuple(elems_iter.count() as _)) @@ -127,6 +127,7 @@ impl FlatInspectable { )) } FlatType::EmptyRecord => Key(FlatInspectableKey::Record(Vec::new())), + FlatType::EmptyTuple => Key(FlatInspectableKey::Tuple(0)), FlatType::EmptyTagUnion => Key(FlatInspectableKey::TagUnion(Vec::new())), FlatType::Func(..) => Immediate(Symbol::INSPECT_FUNCTION), FlatType::EffectfulFunc => { diff --git a/crates/compiler/load/tests/platform.roc b/crates/compiler/load/tests/platform.roc index 18d29729ee2..1208600c812 100644 --- a/crates/compiler/load/tests/platform.roc +++ b/crates/compiler/load/tests/platform.roc @@ -5,5 +5,5 @@ platform "test-platform" imports [] provides [main_for_host] -main_for_host : {} -> {} -main_for_host = \{} -> {} +main_for_host : () -> () +main_for_host = \() -> () diff --git a/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/AStar.roc b/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/AStar.roc index 385ca3a0307..57c763d71c7 100644 --- a/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/AStar.roc +++ b/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/AStar.roc @@ -11,7 +11,7 @@ Model position : { initial_model : position -> Model position initial_model = \start -> { - evaluated: Set.empty({}), + evaluated: Set.empty(), open_set: Set.single(start), costs: Dict.single(start, 0.0), came_from: Map.empty, diff --git a/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/Primary.roc b/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/Primary.roc index 6512dab3f35..5ed9525ccf0 100644 --- a/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/Primary.roc +++ b/crates/compiler/load_internal/tests/fixtures/build/app_with_deps/Primary.roc @@ -14,20 +14,20 @@ always_three = \_ -> Dep1.three identity = \a -> a -z = identity(always_three({})) +z = identity(always_three()) -w : Dep1.Identity {} -w = Identity({}) +w : Dep1.Identity () +w = Identity() succeed : a -> Dep1.Identity a succeed = \x -> Identity(x) with_default = Res.with_default -yay : Res.Res {} err +yay : Res.Res () err yay = ok = Ok("foo") - f = \_ -> {} + f = \_ -> () Res.map(ok, f) diff --git a/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/AStar.roc b/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/AStar.roc index 43e240a8905..d6722b50f20 100644 --- a/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/AStar.roc +++ b/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/AStar.roc @@ -11,10 +11,10 @@ Model position : { initial_model : position -> Model position where position implements Hash & Eq initial_model = \start -> { - evaluated: Set.empty({}), + evaluated: Set.empty(), open_set: Set.single(start), costs: Dict.single(start, 0.0), - came_from: Dict.empty({}), + came_from: Dict.empty(), } cheapest_open : (position -> F64), Model position -> Result position [KeyNotFound] where position implements Hash & Eq diff --git a/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/Primary.roc b/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/Primary.roc index f47f972474c..34e7c9ac75a 100644 --- a/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/Primary.roc +++ b/crates/compiler/load_internal/tests/fixtures/build/module_with_deps/Primary.roc @@ -14,20 +14,20 @@ always_three = \_ -> Dep1.three identity = \a -> a -z = identity(always_three({})) +z = identity(always_three()) -w : Dep1.Identity {} -w = Identity({}) +w : Dep1.Identity () +w = Identity() succeed : a -> Dep1.Identity a succeed = \x -> Identity(x) with_default = Res.with_default -yay : Res.Res {} err +yay : Res.Res () err yay = ok = Ok("foo") - f = \_ -> {} + f = \_ -> () Res.map(ok, f) diff --git a/crates/compiler/lower_params/src/lower.rs b/crates/compiler/lower_params/src/lower.rs index 4c5399d6c06..b0caaa27d4f 100644 --- a/crates/compiler/lower_params/src/lower.rs +++ b/crates/compiler/lower_params/src/lower.rs @@ -400,6 +400,7 @@ impl<'a> LowerParams<'a> { } | OpaqueWrapFunction(_) | EmptyRecord + | EmptyTuple | RuntimeError(_) | Num(_, _, _, _) | Int(_, _, _, _, _) diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 01c19f9a008..8a7bc7eb87f 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -4619,7 +4619,7 @@ pub fn with_hole<'a>( ) } - EmptyRecord => let_empty_struct(assigned, hole), + EmptyRecord | EmptyTuple => let_empty_struct(assigned, hole), Expect { .. } => unreachable!("I think this is unreachable"), Dbg { @@ -10141,6 +10141,7 @@ fn find_lambda_sets_help( } } FlatType::EmptyRecord => {} + FlatType::EmptyTuple => {} FlatType::EmptyTagUnion => {} FlatType::EffectfulFunc => {} }, diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index f24c42d83d8..257c1f6628c 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -2192,7 +2192,10 @@ fn lambda_set_size(subs: &Subs, var: Variable) -> (usize, usize, usize) { } stack.push((ext.var(), depth_any + 1, depth_lset)); } - FlatType::EmptyRecord | FlatType::EmptyTagUnion | FlatType::EffectfulFunc => {} + FlatType::EmptyRecord + | FlatType::EmptyTuple + | FlatType::EmptyTagUnion + | FlatType::EffectfulFunc => {} }, Content::FlexVar(_) | Content::RigidVar(_) @@ -3465,6 +3468,7 @@ fn layout_from_flat_type<'a>( } EmptyTagUnion => cacheable(Ok(Layout::VOID)), EmptyRecord => cacheable(Ok(Layout::UNIT)), + EmptyTuple => cacheable(Ok(Layout::UNIT)), EffectfulFunc => { internal_error!("Cannot create a layout for an unconstrained EffectfulFunc") } diff --git a/crates/compiler/parse/src/expr.rs b/crates/compiler/parse/src/expr.rs index 898b6f17b4f..5753c6ff803 100644 --- a/crates/compiler/parse/src/expr.rs +++ b/crates/compiler/parse/src/expr.rs @@ -88,14 +88,12 @@ fn loc_expr_in_parens_help<'a>() -> impl Parser<'a, Loc>, EInParens<'a> let elements = loc_elements.value; let region = loc_elements.region; - if elements.len() > 1 { + if elements.len() != 1 { Ok(( MadeProgress, Loc::at(region, Expr::Tuple(elements.ptrify_items(arena))), state, )) - } else if elements.is_empty() { - Err((NoProgress, EInParens::Empty(state.pos()))) } else { // TODO: don't discard comments before/after // (stored in the Collection) diff --git a/crates/compiler/parse/src/normalize.rs b/crates/compiler/parse/src/normalize.rs index 3ae1b348b3d..1a17762fc0b 100644 --- a/crates/compiler/parse/src/normalize.rs +++ b/crates/compiler/parse/src/normalize.rs @@ -1333,6 +1333,7 @@ impl<'a> Normalize<'a> for EType<'a> { } EType::TBadTypeVariable(_) => EType::TBadTypeVariable(Position::zero()), EType::TWildcard(_) => EType::TWildcard(Position::zero()), + EType::TEmptyTuple(_) => EType::TEmptyTuple(Position::zero()), EType::TInferred(_) => EType::TInferred(Position::zero()), EType::TStart(_) => EType::TStart(Position::zero()), EType::TEnd(_) => EType::TEnd(Position::zero()), @@ -1447,7 +1448,6 @@ impl<'a> Normalize<'a> for ETypeApply { impl<'a> Normalize<'a> for ETypeInParens<'a> { fn normalize(&self, arena: &'a Bump) -> Self { match self { - ETypeInParens::Empty(_) => ETypeInParens::Empty(Position::zero()), ETypeInParens::End(_) => ETypeInParens::End(Position::zero()), ETypeInParens::Open(_) => ETypeInParens::Open(Position::zero()), ETypeInParens::Type(inner_err, _) => { diff --git a/crates/compiler/parse/src/parser.rs b/crates/compiler/parse/src/parser.rs index 500be5bc846..52d77175aba 100644 --- a/crates/compiler/parse/src/parser.rs +++ b/crates/compiler/parse/src/parser.rs @@ -1199,6 +1199,7 @@ pub enum EType<'a> { TInlineAlias(ETypeInlineAlias, Position), TBadTypeVariable(Position), TWildcard(Position), + TEmptyTuple(Position), TInferred(Position), /// TStart(Position), @@ -1228,6 +1229,7 @@ impl<'a> EType<'a> { | EType::UnderscoreSpacing(p) | EType::TBadTypeVariable(p) | EType::TWildcard(p) + | EType::TEmptyTuple(p) | EType::TInferred(p) | EType::TStart(p) | EType::TEnd(p) @@ -1308,9 +1310,6 @@ impl<'a> ETypeTagUnion<'a> { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ETypeInParens<'a> { - /// e.g. (), which isn't a valid type - Empty(Position), - End(Position), Open(Position), /// @@ -1330,8 +1329,7 @@ impl<'a> ETypeInParens<'a> { ETypeInParens::Type(type_expr, _) => type_expr.get_region(), // Cases with Position values - ETypeInParens::Empty(p) - | ETypeInParens::End(p) + ETypeInParens::End(p) | ETypeInParens::Open(p) | ETypeInParens::Space(_, p) | ETypeInParens::IndentOpen(p) diff --git a/crates/compiler/parse/src/pattern.rs b/crates/compiler/parse/src/pattern.rs index 2afa5cb940a..24b74519c85 100644 --- a/crates/compiler/parse/src/pattern.rs +++ b/crates/compiler/parse/src/pattern.rs @@ -204,14 +204,12 @@ fn loc_pattern_in_parens_help<'a>() -> impl Parser<'a, Loc>, PInPare let elements = loc_elements.value; let region = loc_elements.region; - if elements.len() > 1 { + if elements.len() != 1 { Ok(( MadeProgress, Loc::at(region, Pattern::Tuple(elements)), state, )) - } else if elements.is_empty() { - Err((NoProgress, PInParens::Empty(state.pos()))) } else { // TODO: don't discard comments before/after // (stored in the Collection) diff --git a/crates/compiler/parse/src/type_annotation.rs b/crates/compiler/parse/src/type_annotation.rs index 9044d13e70e..19fa18c4d8f 100644 --- a/crates/compiler/parse/src/type_annotation.rs +++ b/crates/compiler/parse/src/type_annotation.rs @@ -460,7 +460,7 @@ fn loc_type_in_parens<'a>( Collection::with_items_and_comments(arena, fields.into_bump_slice(), final_comments); // Optionally parse the extension - let (progress, ext, state) = optional(allocated(specialize_err_ref( + let (_progress, ext, state) = optional(allocated(specialize_err_ref( ETypeInParens::Type, term(stop_at_surface_has), ))) @@ -469,13 +469,10 @@ fn loc_type_in_parens<'a>( let end = state.pos(); // Determine the result based on the parsed fields and extension - let result = if fields.len() > 1 || ext.is_some() { + let result = if fields.len() != 1 || ext.is_some() { TypeAnnotation::Tuple { elems: fields, ext } - } else if fields.len() == 1 { - return Ok((MadeProgress, fields.items[0], state)); } else { - debug_assert!(fields.is_empty()); - return Err((progress, ETypeInParens::Empty(state.pos()))); + return Ok((MadeProgress, fields.items[0], state)); }; let region = Region::between(start, end); diff --git a/crates/compiler/problem/src/can.rs b/crates/compiler/problem/src/can.rs index c75dbdd2772..ec11184b312 100644 --- a/crates/compiler/problem/src/can.rs +++ b/crates/compiler/problem/src/can.rs @@ -480,6 +480,7 @@ impl Problem { #[derive(Clone, Debug, PartialEq, Eq)] pub enum ExtensionTypeKind { + Tuple, Record, TagUnion, } diff --git a/crates/compiler/solve/src/ability.rs b/crates/compiler/solve/src/ability.rs index 728e1afca0b..fdcee83b0b6 100644 --- a/crates/compiler/solve/src/ability.rs +++ b/crates/compiler/solve/src/ability.rs @@ -612,6 +612,14 @@ trait DerivableVisitor { }) } + #[inline(always)] + fn visit_empty_tuple(var: Variable) -> Result<(), NotDerivable> { + Err(NotDerivable { + var, + context: NotDerivableContext::NoContext, + }) + } + #[inline(always)] fn visit_empty_tag_union(var: Variable) -> Result<(), NotDerivable> { Err(NotDerivable { @@ -779,6 +787,7 @@ trait DerivableVisitor { } } EmptyRecord => Self::visit_empty_record(var)?, + EmptyTuple => Self::visit_empty_tuple(var)?, EmptyTagUnion => Self::visit_empty_tag_union(var)?, EffectfulFunc => { return Err(NotDerivable { @@ -1010,6 +1019,11 @@ impl DerivableVisitor for DeriveEncoding { Ok(()) } + #[inline(always)] + fn visit_empty_tuple(_var: Variable) -> Result<(), NotDerivable> { + Ok(()) + } + #[inline(always)] fn visit_empty_tag_union(_var: Variable) -> Result<(), NotDerivable> { Ok(()) @@ -1114,6 +1128,11 @@ impl DerivableVisitor for DeriveDecoding { Ok(()) } + #[inline(always)] + fn visit_empty_tuple(_var: Variable) -> Result<(), NotDerivable> { + Ok(()) + } + #[inline(always)] fn visit_empty_tag_union(_var: Variable) -> Result<(), NotDerivable> { Ok(()) @@ -1218,6 +1237,11 @@ impl DerivableVisitor for DeriveHash { Ok(()) } + #[inline(always)] + fn visit_empty_tuple(_var: Variable) -> Result<(), NotDerivable> { + Ok(()) + } + #[inline(always)] fn visit_empty_tag_union(_var: Variable) -> Result<(), NotDerivable> { Ok(()) @@ -1332,6 +1356,11 @@ impl DerivableVisitor for DeriveEq { Ok(()) } + #[inline(always)] + fn visit_empty_tuple(_var: Variable) -> Result<(), NotDerivable> { + Ok(()) + } + #[inline(always)] fn visit_empty_tag_union(_var: Variable) -> Result<(), NotDerivable> { Ok(()) diff --git a/crates/compiler/solve/src/deep_copy.rs b/crates/compiler/solve/src/deep_copy.rs index c2a75a29b2c..8424accfd6d 100644 --- a/crates/compiler/solve/src/deep_copy.rs +++ b/crates/compiler/solve/src/deep_copy.rs @@ -187,7 +187,7 @@ fn deep_copy_var_help( Func(new_arguments, new_closure_var, new_ret_var, new_fx_var) } - same @ (EmptyRecord | EmptyTagUnion | EffectfulFunc) => same, + same @ (EmptyRecord | EmptyTuple | EmptyTagUnion | EffectfulFunc) => same, Record(fields, ext_var) => { let record_fields = { diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index 20f3e00d666..44092391b75 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -2418,6 +2418,8 @@ fn adjust_rank_content( // THEORY: an empty tag never needs to get generalized EmptyTagUnion => Rank::toplevel(), + EmptyTuple => Rank::toplevel(), + EffectfulFunc => Rank::toplevel(), Record(fields, ext_var) => { diff --git a/crates/compiler/solve/src/to_var.rs b/crates/compiler/solve/src/to_var.rs index eed99a1fabd..b30b3fbb158 100644 --- a/crates/compiler/solve/src/to_var.rs +++ b/crates/compiler/solve/src/to_var.rs @@ -71,7 +71,7 @@ pub(crate) fn either_type_index_to_var( matches!(types[type_index], TypeTag::Variable(v) if v == var) || matches!( types[type_index], - TypeTag::EmptyRecord | TypeTag::EmptyTagUnion + TypeTag::EmptyTuple | TypeTag::EmptyRecord | TypeTag::EmptyTagUnion ), "different variable was returned for type index variable cell!" ); @@ -140,6 +140,7 @@ impl RegisterVariable { match types[typ] { TypeTag::Variable(var) => Direct(var), TypeTag::EmptyRecord => Direct(Variable::EMPTY_RECORD), + TypeTag::EmptyTuple => Direct(Variable::EMPTY_TUPLE), TypeTag::EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION), TypeTag::DelayedAlias { shared } | TypeTag::StructuralAlias { shared, .. } @@ -316,7 +317,7 @@ pub(crate) fn type_to_var_help( { use TypeTag::*; match typ { - Variable(_) | EmptyRecord | EmptyTagUnion => { + Variable(_) | EmptyRecord | EmptyTuple | EmptyTagUnion => { unreachable!("This variant should never be deferred!",) } RangedNumber(range) => { diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.formatted.roc index 20e58cb41e4..00ac13d4b2a 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.formatted.roc @@ -1,4 +1,4 @@ -\{} -> +\() -> echo "Welcome to the DMV!" age = readInt @@ -6,6 +6,6 @@ echo "You're too young to drive!" exit 1 else - {} + () - echo "Let's get started on your driver's license application." \ No newline at end of file + echo "Let's get started on your driver's license application." diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.roc index 861fc0b97ed..00ac13d4b2a 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/effectful_closure_statements.expr.roc @@ -1,4 +1,4 @@ -\{} -> +\() -> echo "Welcome to the DMV!" age = readInt @@ -6,6 +6,6 @@ echo "You're too young to drive!" exit 1 else - {} + () echo "Let's get started on your driver's license application." diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_platform_header.header.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_platform_header.header.roc index e8bedcb5bab..40dac8a7a23 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_platform_header.header.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_platform_header.header.roc @@ -1 +1 @@ -platform "rtfeldman/blah" requires {} { init : {}, update : {} } exposes [] packages {} imports [] provides [] +platform "rtfeldman/blah" requires {} { init : (), update : () } exposes [] packages {} imports [] provides [] diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record.expr.roc deleted file mode 100644 index 9e26dfeeb6e..00000000000 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record.expr.roc +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_implements.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_implements.expr.formatted.roc deleted file mode 100644 index 33294efc365..00000000000 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_implements.expr.formatted.roc +++ /dev/null @@ -1,2 +0,0 @@ -O {} (implements) -a \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_implements.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_implements.expr.roc deleted file mode 100644 index 9180d639abc..00000000000 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_implements.expr.roc +++ /dev/null @@ -1,2 +0,0 @@ -{}=O{}implements -a diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_tag.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_tag.expr.roc deleted file mode 100644 index cb7e4f54946..00000000000 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_tag.expr.roc +++ /dev/null @@ -1,3 +0,0 @@ -{}= - P -O \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment.expr.roc deleted file mode 100644 index baee7b3f9fe..00000000000 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment.expr.roc +++ /dev/null @@ -1,2 +0,0 @@ -{}=B -I diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_newline_assign.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_newline_assign.expr.formatted.roc deleted file mode 100644 index 8df9830e9a8..00000000000 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_newline_assign.expr.formatted.roc +++ /dev/null @@ -1,2 +0,0 @@ -{} -I \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_newline_assign.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_newline_assign.expr.roc deleted file mode 100644 index 582200fa592..00000000000 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_newline_assign.expr.roc +++ /dev/null @@ -1,4 +0,0 @@ - -{} -={} -I \ No newline at end of file diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple.expr.roc new file mode 100644 index 00000000000..6a452c185a8 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple.expr.roc @@ -0,0 +1 @@ +() diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_dbg.expr.formatted.roc similarity index 69% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.formatted.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_dbg.expr.formatted.roc index 9f4f4e3faaa..c4b0f65130f 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_dbg.expr.formatted.roc @@ -1,4 +1,4 @@ -{} = +() = dbg c c -e \ No newline at end of file +e diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_dbg.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_dbg.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_dbg.expr.roc similarity index 75% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_dbg.expr.roc index 331930722f0..17e0b1be4fa 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_dbg.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_dbg.expr.roc @@ -1,4 +1,4 @@ -{}= +()= dbg c c e diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_implements.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_implements.expr.formatted.roc new file mode 100644 index 00000000000..80fde012588 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_implements.expr.formatted.roc @@ -0,0 +1,2 @@ +O () (implements) +a diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_implements.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_implements.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_implements.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_implements.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_implements.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_implements.expr.roc new file mode 100644 index 00000000000..8c78724693b --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_implements.expr.roc @@ -0,0 +1,2 @@ +()=O()implements +a diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_return.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_return.expr.formatted.roc similarity index 65% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_return.expr.formatted.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_return.expr.formatted.roc index 204dc524bfc..da87ff68c74 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_return.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_return.expr.formatted.roc @@ -1,3 +1,3 @@ -{} = +() = return f -d \ No newline at end of file +d diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_return.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_return.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_return.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_return.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_return.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_return.expr.roc similarity index 73% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_return.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_return.expr.roc index 2cd99847297..50c7c9e6766 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_return.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_return.expr.roc @@ -1,3 +1,3 @@ -{}= +()= return f d diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_tag.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_tag.expr.formatted.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_tag.expr.formatted.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_tag.expr.formatted.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_tag.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_tag.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assign_tag.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_tag.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_tag.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_tag.expr.roc new file mode 100644 index 00000000000..a142fb656d4 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assign_tag.expr.roc @@ -0,0 +1,3 @@ +()= + P +O diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assignment.expr.formatted.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment.expr.formatted.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assignment.expr.formatted.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assignment.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_assignment.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assignment.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assignment.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assignment.expr.roc new file mode 100644 index 00000000000..c2830702aac --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_assignment.expr.roc @@ -0,0 +1,2 @@ +()=B +I diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_dbg.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_dbg.expr.formatted.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_dbg.expr.formatted.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_dbg.expr.formatted.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_dbg.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_dbg.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_dbg.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_dbg.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_dbg.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_dbg.expr.roc similarity index 53% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_dbg.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_dbg.expr.roc index 33ec0420d1a..a8a04895843 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_dbg.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_dbg.expr.roc @@ -1,4 +1,4 @@ -{ -} +( +) =dbg n -d \ No newline at end of file +d diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_newlines_doubleeq.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_newlines_doubleeq.expr.formatted.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_newlines_doubleeq.expr.formatted.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_newlines_doubleeq.expr.formatted.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_newlines_doubleeq.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_newlines_doubleeq.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_newlines_doubleeq.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_newlines_doubleeq.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_newlines_doubleeq.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_newlines_doubleeq.expr.roc similarity index 57% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_newlines_doubleeq.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_newlines_doubleeq.expr.roc index c6205c776b2..0c19eb83f5c 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_eq_newlines_doubleeq.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_eq_newlines_doubleeq.expr.roc @@ -1,5 +1,5 @@ -{ -} +( +) =d== g -d \ No newline at end of file +d diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_newline_assign.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_newline_assign.expr.formatted.roc new file mode 100644 index 00000000000..ace0fff216d --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_newline_assign.expr.formatted.roc @@ -0,0 +1,2 @@ +() +I diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_record_newline_assign.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_newline_assign.expr.result-ast similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/pass/empty_record_newline_assign.expr.result-ast rename to crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_newline_assign.expr.result-ast diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_newline_assign.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_newline_assign.expr.roc new file mode 100644 index 00000000000..6f0682e111e --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/empty_tuple_newline_assign.expr.roc @@ -0,0 +1,4 @@ + +() +=() +I diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/expect_defs.moduledefs.roc b/crates/compiler/test_syntax/tests/snapshots/pass/expect_defs.moduledefs.roc index 713be555b0d..33bccfc47c8 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/expect_defs.moduledefs.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/expect_defs.moduledefs.roc @@ -1,5 +1,5 @@ expect - html : Html {} + html : Html () html = Element "a" 43 [HtmlAttr "href" "https://www.roc-lang.org/"] [Text "Roc"] diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/function_effect_types.header.roc b/crates/compiler/test_syntax/tests/snapshots/pass/function_effect_types.header.roc index 4b365822fee..7e94f530ac0 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/function_effect_types.header.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/function_effect_types.header.roc @@ -1,5 +1,5 @@ platform "cli" - requires {}{ main! : {} => Result {} [] } # TODO FIXME + requires {}{ main! : () => Result () [] } exposes [] packages {} imports [ Foo.{ Foo } ] diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/list_patterns.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/list_patterns.expr.roc index 9953b43d37a..98c52ddae8f 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/list_patterns.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/list_patterns.expr.roc @@ -1,9 +1,9 @@ when [] is - [] -> {} - [..] -> {} - [_, .., _, ..] -> {} - [a, b, c, d] -> {} - [a, b, ..] -> {} - [.., c, d] -> {} - [[A], [..], [a]] -> {} - [[[], []], [[], x]] -> {} + [] -> () + [..] -> () + [_, .., _, ..] -> () + [a, b, c, d] -> () + [a, b, ..] -> () + [.., c, d] -> () + [[A], [..], [a]] -> () + [[[], []], [[], x]] -> () diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature.expr.roc index 0ec71af0427..06a4f8d5955 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/multiline_type_signature.expr.roc @@ -1,4 +1,4 @@ f : - {} + () -42 \ No newline at end of file +42 diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_has_abilities.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_has_abilities.expr.roc index 0a329cbbdcc..01faca0f209 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/opaque_has_abilities.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/opaque_has_abilities.expr.roc @@ -18,6 +18,6 @@ A := U8 implements [] A := a where a implements Other implements [Eq {eq}, Hash {hash}] -A := U8 implements [Eq {}] +A := U8 implements [Eq ()] 0 diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/pattern_as_spaces.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/pattern_as_spaces.expr.roc index 6538e3bcdba..24dfc0a3fed 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/pattern_as_spaces.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/pattern_as_spaces.expr.roc @@ -1,4 +1,4 @@ when 0 is 0 # foobar as # barfoo - n -> {} + n -> () diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/pattern_with_as_parens.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/pattern_with_as_parens.expr.formatted.roc index ad2414510b1..9d2c5e9f8ed 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/pattern_with_as_parens.expr.formatted.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/pattern_with_as_parens.expr.formatted.roc @@ -1,2 +1,2 @@ when t is - Ok ({} as d) -> S \ No newline at end of file + Ok (() as d) -> S diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/pattern_with_as_parens.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/pattern_with_as_parens.expr.roc index a864b5730f6..d4196b6b49d 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/pattern_with_as_parens.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/pattern_with_as_parens.expr.roc @@ -1,2 +1,2 @@ when t is - Ok ({} as d)->S + Ok (() as d)->S diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/record_literal_field_bang.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/record_literal_field_bang.expr.roc index 716fb8ed3e8..d530cead956 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/record_literal_field_bang.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/record_literal_field_bang.expr.roc @@ -1,4 +1,4 @@ { answer: 42, - launchTheNukes!: \{} -> boom + launchTheNukes!: \() -> boom } diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/record_type_with_function.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/record_type_with_function.expr.roc index 3ce22137205..e28da718e21 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/record_type_with_function.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/record_type_with_function.expr.roc @@ -1,3 +1,3 @@ -x : { init : {} -> Model, update : Model, Str -> Model, view : Model -> Str } +x : { init : () -> Model, update : Model, Str -> Model, view : Model -> Str } 42 diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/tag_union_functions_as.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/tag_union_functions_as.expr.roc index 9bc27a7b13e..31c42c0809b 100644 --- a/crates/compiler/test_syntax/tests/snapshots/pass/tag_union_functions_as.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/tag_union_functions_as.expr.roc @@ -1,3 +1,3 @@ -main_for_host : [StdoutWrite Str ({} -> Op), StderrWrite Str ({} -> Op), Done] as Op +main_for_host : [StdoutWrite Str (() -> Op), StderrWrite Str (() -> Op), Done] as Op main_for_host = main 42 diff --git a/crates/compiler/types/src/pretty_print.rs b/crates/compiler/types/src/pretty_print.rs index b10073e3602..e8c53d33472 100644 --- a/crates/compiler/types/src/pretty_print.rs +++ b/crates/compiler/types/src/pretty_print.rs @@ -14,6 +14,7 @@ use roc_module::symbol::{Interns, ModuleId, Symbol}; pub static WILDCARD: &str = "*"; static EMPTY_RECORD: &str = "{}"; +static EMPTY_TUPLE: &str = "()"; static EMPTY_TAG_UNION: &str = "[]"; static EFFECTFUL_FUNC: &str = "! : ... => ?"; @@ -412,6 +413,7 @@ fn find_names_needed( } Error | Structure(EmptyRecord) + | Structure(EmptyTuple) | Structure(EmptyTagUnion) | Pure | Effectful @@ -986,6 +988,7 @@ impl ExtContent { match content { Content::Structure(FlatType::EmptyTagUnion) => ExtContent::Empty, Content::Structure(FlatType::EmptyRecord) => ExtContent::Empty, + Content::Structure(FlatType::EmptyTuple) => ExtContent::Empty, Content::FlexVar(None) | Content::FlexAbleVar(None, _) if pol.is_pos() && !debug_flags.ignore_polarity => @@ -1128,6 +1131,7 @@ fn write_flat_type<'a>( pol, ), EmptyRecord => buf.push_str(EMPTY_RECORD), + EmptyTuple => buf.push_str(EMPTY_TUPLE), EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION), Func(args, closure, ret, fx) => write_fn( env, @@ -1208,40 +1212,51 @@ fn write_flat_type<'a>( .expect("Something ended up weird in this record type"); let ext_var = ext; - buf.push_str("( "); + if sorted_elems.is_empty() { + buf.push_str("()"); + } else { + buf.push_str("( "); - let mut any_written_yet = false; - let mut expected_next_index = 0; + let mut any_written_yet = false; + let mut expected_next_index = 0; - for (index, var) in sorted_elems { - if any_written_yet { - buf.push_str(", "); - } else { - any_written_yet = true; - } + for (index, var) in sorted_elems { + if any_written_yet { + buf.push_str(", "); + } else { + any_written_yet = true; + } - if index - expected_next_index > 4 { - // Don't write out a large number of _'s - just write out a count - buf.push_str(&format!("... {} omitted, ", index - expected_next_index)); - } else if index - expected_next_index > 1 { - // Write out a bunch of _'s - for _ in expected_next_index..index { - buf.push_str("_, "); + if index - expected_next_index > 4 { + // Don't write out a large number of _'s - just write out a count + buf.push_str(&format!("... {} omitted, ", index - expected_next_index)); + } else if index - expected_next_index > 1 { + // Write out a bunch of _'s + for _ in expected_next_index..index { + buf.push_str("_, "); + } } + expected_next_index = index + 1; + + write_content(env, ctx, var, subs, buf, Parens::Unnecessary, pol); } - expected_next_index = index + 1; - write_content(env, ctx, var, subs, buf, Parens::Unnecessary, pol); + buf.push_str(" )"); } - buf.push_str(" )"); - - // This is an open tuple, so print the variable - // right after the ')' - // - // e.g. the "*" at the end of `( I64, I64 )*` - // or the "r" at the end of `( I64, I64 )r` - write_content(env, ctx, ext_var, subs, buf, parens, pol) + match subs.get_content_without_compacting(ext_var) { + Content::Structure(EmptyTuple) => { + // This is a closed tuple. We're done! + } + _ => { + // This is an open tuple, so print the variable + // right after the ')' + // + // e.g. the "*" at the end of `( I64, I64 )*` + // or the "r" at the end of `( I64, I64 )r` + write_content(env, ctx, ext_var, subs, buf, parens, pol) + } + } } TagUnion(tags, ext_var) => { buf.push('['); diff --git a/crates/compiler/types/src/subs.rs b/crates/compiler/types/src/subs.rs index f350c2f6090..3914fe89a73 100644 --- a/crates/compiler/types/src/subs.rs +++ b/crates/compiler/types/src/subs.rs @@ -832,6 +832,7 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f write!(f, "]<{new_ext:?}> as <{rec:?}>") } FlatType::EmptyRecord => write!(f, "EmptyRecord"), + FlatType::EmptyTuple => write!(f, "EmptyTuple"), FlatType::EmptyTagUnion => write!(f, "EmptyTagUnion"), FlatType::EffectfulFunc => write!(f, "EffectfulFunc"), } @@ -1601,6 +1602,10 @@ impl Subs { Variable::EMPTY_RECORD, Content::Structure(FlatType::EmptyRecord), ); + subs.set_content( + Variable::EMPTY_TUPLE, + Content::Structure(FlatType::EmptyTuple), + ); subs.set_content( Variable::EMPTY_TAG_UNION, Content::Structure(FlatType::EmptyTagUnion), @@ -2598,6 +2603,7 @@ pub enum FlatType { RecursiveTagUnion(Variable, UnionTags, TagExt), EmptyRecord, + EmptyTuple, EmptyTagUnion, } @@ -3495,7 +3501,7 @@ fn occurs( short_circuit_help(subs, root_var, ctx, ext_var) } - EmptyRecord | EmptyTagUnion | EffectfulFunc => Ok(()), + EmptyRecord | EmptyTuple | EmptyTagUnion | EffectfulFunc => Ok(()), }, Alias(_, args, _, _) => { // THEORY: we only need to explore the args, as that is the surface of all @@ -3689,7 +3695,7 @@ fn explicit_substitute( subs.set_content(in_var, Structure(Tuple(vars_by_elem, new_ext))); } - EmptyRecord | EmptyTagUnion | EffectfulFunc => {} + EmptyRecord | EmptyTuple | EmptyTagUnion | EffectfulFunc => {} } in_var @@ -3875,9 +3881,10 @@ fn get_var_names( accum } - FlatType::EmptyRecord | FlatType::EmptyTagUnion | FlatType::EffectfulFunc => { - taken_names - } + FlatType::EmptyRecord + | FlatType::EmptyTuple + | FlatType::EmptyTagUnion + | FlatType::EffectfulFunc => taken_names, FlatType::Record(vars_by_field, ext) => { let mut accum = get_var_names(subs, ext, taken_names); @@ -4243,6 +4250,7 @@ fn flat_type_to_err_type( EffectfulFunc => ErrorType::EffectfulFunc, EmptyRecord => ErrorType::Record(SendMap::default(), TypeExt::Closed), + EmptyTuple => ErrorType::Tuple(Vec::new(), TypeExt::Closed), EmptyTagUnion => ErrorType::TagUnion(SendMap::default(), TypeExt::Closed, pol), Record(vars_by_field, ext) => { @@ -4699,6 +4707,7 @@ impl StorageSubs { ext.map(|v| Self::offset_variable(offsets, v)), ), FlatType::EmptyRecord => FlatType::EmptyRecord, + FlatType::EmptyTuple => FlatType::EmptyTuple, FlatType::EmptyTagUnion => FlatType::EmptyTagUnion, FlatType::EffectfulFunc => FlatType::EffectfulFunc, } @@ -4998,7 +5007,7 @@ fn storage_copy_var_to_help(env: &mut StorageCopyVarToEnv<'_>, var: Variable) -> Func(new_arguments, new_closure_var, new_ret_var, new_fx_var) } - same @ EmptyRecord | same @ EmptyTagUnion => same, + same @ (EmptyRecord | EmptyTuple | EmptyTagUnion) => same, Record(fields, ext) => { let record_fields = { @@ -5344,7 +5353,9 @@ fn is_registered(content: &Content) -> bool { | Content::RigidVar(_) | Content::FlexAbleVar(..) | Content::RigidAbleVar(..) => false, - Content::Structure(FlatType::EmptyRecord | FlatType::EmptyTagUnion) => false, + Content::Structure( + FlatType::EmptyRecord | FlatType::EmptyTuple | FlatType::EmptyTagUnion, + ) => false, Content::ErasedLambda => false, Content::Pure | Content::Effectful => false, @@ -5469,7 +5480,7 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl Func(new_arguments, new_closure_var, new_ret_var, new_fx_var) } - same @ EmptyRecord | same @ EmptyTagUnion => same, + same @ (EmptyRecord | EmptyTuple | EmptyTagUnion) => same, Record(fields, ext) => { let record_fields = { @@ -5855,7 +5866,7 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { stack.push(fx_var); } - EmptyRecord | EmptyTagUnion => (), + EmptyRecord | EmptyTuple | EmptyTagUnion => (), Record(fields, ext) => { let fields = *fields; @@ -6009,7 +6020,10 @@ pub fn get_member_lambda_sets_at_region(subs: &Subs, var: Variable, target_regio ); stack.push(ext.var()); } - FlatType::EffectfulFunc | FlatType::EmptyRecord | FlatType::EmptyTagUnion => {} + FlatType::EffectfulFunc + | FlatType::EmptyRecord + | FlatType::EmptyTuple + | FlatType::EmptyTagUnion => {} }, Content::Alias(_, _, real_var, _) => { stack.push(*real_var); @@ -6088,6 +6102,7 @@ fn is_inhabited(subs: &Subs, var: Variable) -> bool { FlatType::EffectfulFunc => {} FlatType::FunctionOrTagUnion(_, _, _) => {} FlatType::EmptyRecord => {} + FlatType::EmptyTuple => {} FlatType::EmptyTagUnion => { return false; } diff --git a/crates/compiler/types/src/types.rs b/crates/compiler/types/src/types.rs index ce413149623..32dd8ed17b9 100644 --- a/crates/compiler/types/src/types.rs +++ b/crates/compiler/types/src/types.rs @@ -369,6 +369,7 @@ pub struct AliasShared { #[derive(Debug, Clone, Copy)] pub enum TypeTag { EmptyRecord, + EmptyTuple, EmptyTagUnion, /// The arguments are implicit Function( @@ -496,11 +497,15 @@ impl Types { const EMPTY_RECORD_TAG: TypeTag = TypeTag::Variable(Variable::EMPTY_RECORD); const EMPTY_RECORD_ARGS: Slice = Slice::empty(); - pub const EMPTY_TAG_UNION: Index = Index::new(1); + pub const EMPTY_TUPLE: Index = Index::new(1); + const EMPTY_TUPLE_TAG: TypeTag = TypeTag::Variable(Variable::EMPTY_TUPLE); + const EMPTY_TUPLE_ARGS: Slice = Slice::empty(); + + pub const EMPTY_TAG_UNION: Index = Index::new(2); const EMPTY_TAG_UNION_TAG: TypeTag = TypeTag::Variable(Variable::EMPTY_TAG_UNION); const EMPTY_TAG_UNION_ARGS: Slice = Slice::empty(); - pub const STR: Index = Index::new(2); + pub const STR: Index = Index::new(3); const STR_TAG: TypeTag = TypeTag::Variable(Variable::STR); const STR_ARGS: Slice = Slice::empty(); @@ -509,11 +514,13 @@ impl Types { // tags.len() == tags_slices.len() tags: vec![ Self::EMPTY_RECORD_TAG, + Self::EMPTY_TUPLE_TAG, Self::EMPTY_TAG_UNION_TAG, Self::STR_TAG, ], tags_slices: vec![ Self::EMPTY_RECORD_ARGS, + Self::EMPTY_TUPLE_ARGS, Self::EMPTY_TAG_UNION_ARGS, Self::STR_ARGS, ], @@ -751,6 +758,7 @@ impl Types { fn from_old_type_at(&mut self, index: Index, old: &Type) { match old { Type::EmptyRec => self.set_type_tag(index, TypeTag::EmptyRecord, Slice::default()), + Type::EmptyTuple => self.set_type_tag(index, TypeTag::EmptyTuple, Slice::default()), Type::EmptyTagUnion => { self.set_type_tag(index, TypeTag::EmptyTagUnion, Slice::default()) } @@ -1101,6 +1109,7 @@ impl Types { let (tag, args) = match self[typ] { Variable(v) => (Variable(subst!(v)), Default::default()), EmptyRecord => (EmptyRecord, Default::default()), + EmptyTuple => (EmptyTuple, Default::default()), EmptyTagUnion => (EmptyTagUnion, Default::default()), Function(clos, ret, fx) => { let args = self.get_type_arguments(typ); @@ -1339,6 +1348,7 @@ mod debug_types { use TPrec::*; let group = match types[tag] { TypeTag::EmptyRecord => f.text("{}"), + TypeTag::EmptyTuple => f.text("()"), TypeTag::EmptyTagUnion => f.text("[]"), TypeTag::Function(clos, ret, fx) => { let args = types.get_type_arguments(tag); @@ -1668,6 +1678,7 @@ impl std::ops::Index> for Types { #[derive(PartialEq, Eq)] pub enum Type { EmptyRec, + EmptyTuple, EmptyTagUnion, /// A function. The types of its arguments, size of its closure, its return value, then the fx type. Function(Vec, Box, Box, Box), @@ -1748,6 +1759,7 @@ impl Clone for Type { match self { Self::EmptyRec => Self::EmptyRec, + Self::EmptyTuple => Self::EmptyTuple, Self::EmptyTagUnion => Self::EmptyTagUnion, Self::Function(arg0, arg1, arg2, arg3) => { Self::Function(arg0.clone(), arg1.clone(), arg2.clone(), arg3.clone()) @@ -1829,7 +1841,7 @@ impl TypeExtension { #[inline(always)] pub fn from_type(typ: Type, is_implicit_openness: ExtImplicitOpenness) -> Self { match typ { - Type::EmptyTagUnion | Type::EmptyRec => Self::Closed, + Type::EmptyTagUnion | Type::EmptyRec | Type::EmptyTuple => Self::Closed, _ => Self::Open(Box::new(typ), is_implicit_openness), } } @@ -1837,7 +1849,7 @@ impl TypeExtension { #[inline(always)] pub fn from_non_annotation_type(typ: Type) -> Self { match typ { - Type::EmptyTagUnion | Type::EmptyRec => Self::Closed, + Type::EmptyTagUnion | Type::EmptyRec | Type::EmptyTuple => Self::Closed, _ => Self::Open(Box::new(typ), ExtImplicitOpenness::No), } } @@ -1906,6 +1918,7 @@ impl fmt::Debug for Type { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Type::EmptyRec => write!(f, "{{}}"), + Type::EmptyTuple => write!(f, "()"), Type::EmptyTagUnion => write!(f, "[]"), Type::Function(args, closure, ret, fx) => { write!(f, "Fn(")?; @@ -2181,10 +2194,6 @@ impl Type { matches!(self, Type::EmptyTagUnion) } - pub fn is_empty_record(&self) -> bool { - matches!(self, Type::EmptyRec) - } - pub fn variables(&self) -> ImSet { let mut result = ImSet::default(); variables_help(self, &mut result); @@ -2324,7 +2333,7 @@ impl Type { ); } - EmptyRec | EmptyTagUnion | Error | Pure | Effectful => {} + EmptyRec | EmptyTuple | EmptyTagUnion | Error | Pure | Effectful => {} } } } @@ -2447,7 +2456,7 @@ impl Type { ); } - EmptyRec | EmptyTagUnion | Error | Pure | Effectful => {} + EmptyRec | EmptyTuple | EmptyTagUnion | Error | Pure | Effectful => {} } } } @@ -2564,6 +2573,7 @@ impl Type { RangedNumber(_) => Ok(()), UnspecializedLambdaSet { .. } => Ok(()), EmptyRec + | EmptyTuple | EmptyTagUnion | ClosureTag { .. } | Error @@ -2633,6 +2643,7 @@ impl Type { unspecialized: Uls(_, sym, _), } => *sym == rep_symbol, EmptyRec + | EmptyTuple | EmptyTagUnion | ClosureTag { .. } | Error @@ -2700,7 +2711,7 @@ impl Type { .iter() .any(|arg| arg.value.contains_variable(rep_variable)), RangedNumber(_) => false, - EmptyRec | EmptyTagUnion | Error | Pure | Effectful => false, + EmptyRec | EmptyTuple | EmptyTagUnion | Error | Pure | Effectful => false, } } @@ -3009,7 +3020,14 @@ fn instantiate_aliases<'a, F>( } RangedNumber(_) => {} UnspecializedLambdaSet { .. } => {} - EmptyRec | EmptyTagUnion | ClosureTag { .. } | Error | Variable(_) | Pure | Effectful => {} + EmptyRec + | EmptyTuple + | EmptyTagUnion + | ClosureTag { .. } + | Error + | Variable(_) + | Pure + | Effectful => {} } } @@ -3072,6 +3090,7 @@ fn symbols_help(initial: &Type) -> Vec { // ignore the member symbol because unspecialized lambda sets are internal-only } EmptyRec + | EmptyTuple | EmptyTagUnion | ClosureTag { .. } | Error @@ -3091,7 +3110,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { use Type::*; match tipe { - EmptyRec | EmptyTagUnion | Error => (), + EmptyRec | EmptyTuple | EmptyTagUnion | Error => (), Variable(v) => { accum.insert(*v); @@ -3222,7 +3241,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { use Type::*; match tipe { - EmptyRec | EmptyTagUnion | Error => (), + EmptyRec | EmptyTuple | EmptyTagUnion | Error => (), Variable(v) => { accum.type_variables.insert(*v); @@ -3554,6 +3573,7 @@ pub enum PatternCategory { Tuple, List, EmptyRecord, + EmptyTuple, PatternGuard, PatternDefault, Set, @@ -4298,6 +4318,7 @@ pub fn gather_tuple_elems_unsorted_iter( var = *actual_var; } + Structure(EmptyTuple) => break, FlexVar(_) | FlexAbleVar(..) => break, // TODO investigate apparently this one pops up in the reporting tests! @@ -4501,6 +4522,7 @@ fn instantiate_lambda_sets_as_unspecialized( while let Some(typ) = stack.pop() { match typ { Type::EmptyRec => {} + Type::EmptyTuple => {} Type::EmptyTagUnion => {} Type::Function(args, lambda_set, ret, fx) => { debug_assert!( @@ -4598,12 +4620,12 @@ mod test { let fx2 = Box::new(Type::Variable(var_store.fresh())); let fx3 = Box::new(Type::Variable(var_store.fresh())); let mut typ = Type::Function( - vec![Type::Function(vec![], l2, Box::new(Type::EmptyRec), fx1)], + vec![Type::Function(vec![], l2, Box::new(Type::EmptyTuple), fx1)], l1, Box::new(Type::TagUnion( vec![( TagName("A".into()), - vec![Type::Function(vec![], l3, Box::new(Type::EmptyRec), fx2)], + vec![Type::Function(vec![], l3, Box::new(Type::EmptyTuple), fx2)], )], TypeExtension::Closed, )), @@ -4635,7 +4657,7 @@ mod test { [Type::Function(args, l2, ret, _fx)] => { check_uls!(**l2, 2); assert!(args.is_empty()); - assert!(matches!(**ret, Type::EmptyRec)); + assert!(matches!(**ret, Type::EmptyTuple)); } _ => panic!(), } @@ -4648,7 +4670,7 @@ mod test { [Type::Function(args, l3, ret, _fx)] => { check_uls!(**l3, 3); assert!(args.is_empty()); - assert!(matches!(**ret, Type::EmptyRec)); + assert!(matches!(**ret, Type::EmptyTuple)); } _ => panic!(), } diff --git a/crates/compiler/unify/src/fix.rs b/crates/compiler/unify/src/fix.rs index 829fb1277be..3cd2c2e990f 100644 --- a/crates/compiler/unify/src/fix.rs +++ b/crates/compiler/unify/src/fix.rs @@ -344,6 +344,7 @@ fn find_chain(subs: &Subs, left: Variable, right: Variable) -> impl Iterator Err(()), _ => internal_error!( "structures {:?} and {:?} do not unify; they should never have been involved in fixing!", diff --git a/crates/compiler/unify/src/unify.rs b/crates/compiler/unify/src/unify.rs index 2b4e1dea286..189ed879685 100644 --- a/crates/compiler/unify/src/unify.rs +++ b/crates/compiler/unify/src/unify.rs @@ -3247,6 +3247,16 @@ fn unify_flat_type( unify_record(env, pool, ctx, *fields1, *ext1, *fields2, *ext2) } + (EmptyTuple, EmptyTuple) => merge(env, ctx, Structure(*left)), + + (Tuple(elems, ext), EmptyTuple) if elems.is_empty() => { + unify_pool(env, pool, *ext, ctx.second, ctx.mode) + } + + (EmptyTuple, Tuple(elems, ext)) if elems.is_empty() => { + unify_pool(env, pool, ctx.first, *ext, ctx.mode) + } + (Tuple(elems1, ext1), Tuple(elems2, ext2)) => { unify_tuple(env, pool, ctx, *elems1, *ext1, *elems2, *ext2) } diff --git a/crates/glue/src/RustGlue.roc b/crates/glue/src/RustGlue.roc index a3761dcfcfb..52eee1b4343 100644 --- a/crates/glue/src/RustGlue.roc +++ b/crates/glue/src/RustGlue.roc @@ -1916,7 +1916,7 @@ cannot_support_default = \types, type -> List.any(fields, \{ id } -> cannot_support_default(types, Types.shape(types, id))) has_float = \types, type -> - has_float_help(types, type, Set.empty({})) + has_float_help(types, type, Set.empty()) has_float_help = \types, type, do_not_recurse -> # TODO: is doNotRecurse problematic? Do we need an updated doNotRecurse for calls up the tree? @@ -2142,7 +2142,7 @@ generate_roc_refcounted_named_fields = \types, fields, mode, wrapper -> # If a value or any data in it must be refcounted. contains_refcounted = \types, type -> - contains_refcounted_help(types, type, Set.empty({})) + contains_refcounted_help(types, type, Set.empty()) contains_refcounted_help = \types, type, do_not_recurse -> # TODO: is doNotRecurse problematic? Do we need an updated doNotRecurse for calls up the tree? diff --git a/crates/glue/src/load.rs b/crates/glue/src/load.rs index 4401a04b75d..cf7e8f284d1 100644 --- a/crates/glue/src/load.rs +++ b/crates/glue/src/load.rs @@ -325,6 +325,7 @@ fn number_lambda_sets(subs: &Subs, initial: Variable) -> Vec { } EmptyRecord => (), + EmptyTuple => (), EmptyTagUnion => (), EffectfulFunc => internal_error!(), diff --git a/crates/glue/src/types.rs b/crates/glue/src/types.rs index 3f6c95f4cd2..4c549a4307a 100644 --- a/crates/glue/src/types.rs +++ b/crates/glue/src/types.rs @@ -1425,6 +1425,9 @@ fn add_type_help<'a>( Content::Structure(FlatType::EmptyRecord) => { types.add_anonymous(&env.layout_cache.interner, RocType::Unit, layout) } + Content::Structure(FlatType::EmptyTuple) => { + types.add_anonymous(&env.layout_cache.interner, RocType::Unit, layout) + } Content::Structure(FlatType::EmptyTagUnion) => { types.add_anonymous(&env.layout_cache.interner, RocType::EmptyTagUnion, layout) } diff --git a/crates/glue/tests/fixtures/rust/basic-recursive-union/platform.roc b/crates/glue/tests/fixtures/rust/basic-recursive-union/platform.roc index 861e6b4b96c..744da1c579d 100644 --- a/crates/glue/tests/fixtures/rust/basic-recursive-union/platform.roc +++ b/crates/glue/tests/fixtures/rust/basic-recursive-union/platform.roc @@ -7,5 +7,5 @@ platform "test-platform" Expr : [String Str, Concat Expr Expr] -main_for_host : {} -> Expr -main_for_host = \{} -> main +main_for_host : () -> Expr +main_for_host = \() -> main diff --git a/crates/glue/tests/fixtures/rust/closures/app.roc b/crates/glue/tests/fixtures/rust/closures/app.roc index f7211f27a37..4eac4158f2c 100644 --- a/crates/glue/tests/fixtures/rust/closures/app.roc +++ b/crates/glue/tests/fixtures/rust/closures/app.roc @@ -1,7 +1,7 @@ app [main] { pf: platform "platform.roc" } -main : I64 -> ({} -> I64) +main : I64 -> (() -> I64) main = \x -> capture1 = 2 capture2 = 8 - \{} -> capture1 * capture2 * x + \() -> capture1 * capture2 * x diff --git a/crates/glue/tests/fixtures/rust/closures/platform.roc b/crates/glue/tests/fixtures/rust/closures/platform.roc index 9d74d98bbdd..6ec2f8ce34e 100644 --- a/crates/glue/tests/fixtures/rust/closures/platform.roc +++ b/crates/glue/tests/fixtures/rust/closures/platform.roc @@ -1,9 +1,9 @@ platform "test-platform" - requires {} { main : I64 -> ({} -> I64) } + requires {} { main : I64 -> (() -> I64) } exposes [] packages {} imports [] provides [main_for_host] -main_for_host : I64 -> ({} -> I64) +main_for_host : I64 -> (() -> I64) main_for_host = \x -> main(x) diff --git a/crates/glue/tests/fixtures/rust/nullable-wrapped/platform.roc b/crates/glue/tests/fixtures/rust/nullable-wrapped/platform.roc index a689b0c1c47..e7f0b0f7e1c 100644 --- a/crates/glue/tests/fixtures/rust/nullable-wrapped/platform.roc +++ b/crates/glue/tests/fixtures/rust/nullable-wrapped/platform.roc @@ -7,5 +7,5 @@ platform "test-platform" StrFingerTree : [Empty, Single Str, More Str StrFingerTree] -main_for_host : {} -> StrFingerTree -main_for_host = \{} -> main +main_for_host : () -> StrFingerTree +main_for_host = \() -> main diff --git a/crates/repl_eval/src/eval.rs b/crates/repl_eval/src/eval.rs index cc853c7f527..34e2122a040 100644 --- a/crates/repl_eval/src/eval.rs +++ b/crates/repl_eval/src/eval.rs @@ -469,6 +469,9 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>( Content::Structure(FlatType::Tuple(elems, _)) => { struct_to_ast_tuple(env, mem, addr, *elems) } + Content::Structure(FlatType::EmptyTuple) => { + struct_to_ast_tuple(env, mem, addr, TupleElems::empty()) + } Content::Structure(FlatType::TagUnion(tags, _)) => { let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags); @@ -674,6 +677,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>( Content::Structure(FlatType::EmptyRecord) => { struct_to_ast(env, mem, addr, RecordFields::empty()) } + Content::Structure(FlatType::EmptyTuple) => { + struct_to_ast_tuple(env, mem, addr, TupleElems::empty()) + } other => { unreachable!( "Something had a Struct layout, but instead of a Record type, it had: {:?}", @@ -1210,8 +1216,6 @@ fn struct_to_ast_tuple<'a, M: ReplAppMemory>( let subs = env.subs; let mut output = Vec::with_capacity_in(tuple_elems.len(), arena); - debug_assert!(tuple_elems.len() > 1); - // We'll advance this as we iterate through the fields let mut field_addr = addr; diff --git a/crates/reporting/src/error/canonicalize.rs b/crates/reporting/src/error/canonicalize.rs index 4cd9ae774e8..5fa5520a793 100644 --- a/crates/reporting/src/error/canonicalize.rs +++ b/crates/reporting/src/error/canonicalize.rs @@ -806,6 +806,7 @@ pub fn can_problem<'b>( Problem::InvalidExtensionType { region, kind } => { let (kind_str, can_only_contain) = match kind { + ExtensionTypeKind::Tuple => ("tuple", "a type variable or another tuple"), ExtensionTypeKind::Record => ("record", "a type variable or another record"), ExtensionTypeKind::TagUnion => { ("tag union", "a type variable or another tag union") diff --git a/crates/reporting/src/error/parse.rs b/crates/reporting/src/error/parse.rs index e8b2fabe9b7..5be27df4686 100644 --- a/crates/reporting/src/error/parse.rs +++ b/crates/reporting/src/error/parse.rs @@ -2802,6 +2802,7 @@ fn to_type_report<'a>( EType::Space(_, pos) | EType::UnderscoreSpacing(pos) | EType::TWildcard(pos) + | EType::TEmptyTuple(pos) | EType::TInferred(pos) | EType::TEnd(pos) | EType::TWhereBar(pos) @@ -3304,27 +3305,6 @@ fn to_tinparens_report<'a>( } } - ETypeInParens::Empty(pos) => { - let surroundings = Region::new(start, pos); - let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); - - let doc = alloc.stack([ - alloc.reflow("I am partway through parsing a parenthesized type:"), - alloc.region_with_subregion(lines.convert_region(surroundings), region, severity), - alloc.concat([ - alloc.reflow(r"I was expecting to see an expression next."), - alloc.reflow(r"Note, Roc doesn't use '()' as a null type."), - ]), - ]); - - Report { - filename, - doc, - title: "EMPTY PARENTHESES".to_string(), - severity, - } - } - ETypeInParens::End(pos) => { let surroundings = Region::new(start, pos); let region = LineColumnRegion::from_pos(lines.convert_pos(pos)); @@ -4288,7 +4268,7 @@ fn to_requires_report<'a>( alloc.reflow(" keyword next, like"), ]), alloc - .parser_suggestion("requires { main! : {} => Result I64 Str }") + .parser_suggestion("requires { main! : () => Result I64 Str }") .indent(4), ]); @@ -4315,7 +4295,7 @@ fn to_requires_report<'a>( alloc.reflow(" keyword next, like"), ]), alloc - .parser_suggestion("requires { main! : {} => Result I64 Str }") + .parser_suggestion("requires { main! : () => Result I64 Str }") .indent(4), ]); @@ -4344,7 +4324,7 @@ fn to_requires_report<'a>( alloc.reflow(" definition looks like"), ]), alloc - .parser_suggestion("requires { Model, Msg } { main! : {} => Result {} [] }") + .parser_suggestion("requires { Model, Msg } { main! : () => Result () [] }") .indent(4), ]); @@ -4373,7 +4353,7 @@ fn to_requires_report<'a>( alloc.reflow(" definition looks like"), ]), alloc - .parser_suggestion("requires { Model, Msg } { main! : {} => Result {} [] }") + .parser_suggestion("requires { Model, Msg } { main! : () => Result () [] }") .indent(4), ]); diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index 6c9831f9c7e..3f48ff310bb 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -358,7 +358,7 @@ pub fn type_problem<'b>( alloc.tip(), alloc.reflow("If you don't need any arguments, use an empty record:"), ]), - alloc.parser_suggestion(" askName! : {} => Str\n askName! = \\{} ->\n Stdout.line! \"What's your name?\"\n Stdin.line! {}"), + alloc.parser_suggestion(" ask_name! : () => Str\n ask_name! = \\() ->\n Stdout.line!(\"What's your name?\")\n Stdin.line!()"), alloc.reflow("This will allow the caller to control when the effects run."), ]; @@ -1890,7 +1890,7 @@ fn to_expr_report<'b>( ]), }, alloc.region(lines.convert_region(region), severity), - alloc.reflow("Standalone statements are required to produce an empty record, but the type of this one is:"), + alloc.reflow("Standalone statements are required to produce an empty tuple, but the type of this one is:"), alloc.type_block(type_with_able_vars(alloc, diff.left, diff.left_able)), match found { ErrorType::EffectfulFunc | ErrorType::Function(_, _, _, _) => { @@ -2560,6 +2560,7 @@ fn add_pattern_category<'b>( Record => alloc.reflow(" record values of type:"), Tuple => alloc.reflow(" tuple values of type:"), EmptyRecord => alloc.reflow(" an empty record:"), + EmptyTuple => alloc.reflow(" an empty tuple:"), PatternGuard => alloc.reflow(" a pattern guard of type:"), PatternDefault => alloc.reflow(" an optional field of type:"), Set => alloc.reflow(" sets of type:"), @@ -5566,16 +5567,20 @@ fn pattern_to_doc_help<'b>( .append(" }") } RenderAs::Tuple => { - let mut arg_docs = Vec::with_capacity(args.len()); + if args.is_empty() { + alloc.text("()") + } else { + let mut arg_docs = Vec::with_capacity(args.len()); - for v in args.into_iter() { - arg_docs.push(pattern_to_doc_help(alloc, v, false)); - } + for v in args.into_iter() { + arg_docs.push(pattern_to_doc_help(alloc, v, false)); + } - alloc - .text("( ") - .append(alloc.intersperse(arg_docs, alloc.reflow(", "))) - .append(" )") + alloc + .text("( ") + .append(alloc.intersperse(arg_docs, alloc.reflow(", "))) + .append(" )") + } } RenderAs::Tag | RenderAs::Opaque => { let ctor = &union.alternatives[tag_id.0 as usize]; diff --git a/crates/test_utils/src/TagLenEncoderFmt.roc b/crates/test_utils/src/TagLenEncoderFmt.roc index bd156b578d6..608cd427732 100644 --- a/crates/test_utils/src/TagLenEncoderFmt.roc +++ b/crates/test_utils/src/TagLenEncoderFmt.roc @@ -12,7 +12,7 @@ # tag_len_fmt, # ] -TagLenFmt := {} +TagLenFmt := () implements [ EncoderFormatting { u8: encode_u8, @@ -57,7 +57,7 @@ TagLenFmt := {} }, ] -tag_len_fmt = @TagLenFmt {} +tag_len_fmt = @TagLenFmt () # ENCODE @@ -65,7 +65,7 @@ append_pre_len = \bytes, pre, len -> List.append bytes (Num.to_u8 pre) |> List.concat (Num.to_str len |> Str.to_utf8) |> List.append ' ' -encode_num = \n -> Encode.custom \bytes, @TagLenFmt {} -> append_pre_len bytes 'n' n +encode_num = \n -> Encode.custom \bytes, @TagLenFmt () -> append_pre_len bytes 'n' n encode_u8 = encode_num encode_u16 = encode_num @@ -92,7 +92,7 @@ expect actual = Encode.to_bytes Bool.true tag_len_fmt actual == (Str.to_utf8 "n1 ") -encode_string = \str -> Encode.custom \bytes, @TagLenFmt {} -> +encode_string = \str -> Encode.custom \bytes, @TagLenFmt () -> append_pre_len bytes 's' (Str.count_utf8_bytes str) |> List.concat (Str.to_utf8 str) |> List.append ' ' @@ -101,21 +101,21 @@ expect actual = Encode.to_bytes "hey" tag_len_fmt actual == (Str.to_utf8 "s3 hey ") -encode_list = \lst, encode_elem -> Encode.custom \bytes, @TagLenFmt {} -> +encode_list = \lst, encode_elem -> Encode.custom \bytes, @TagLenFmt () -> bytes_pre = append_pre_len bytes 'l' (List.len lst) List.walk lst bytes_pre \buf, elem -> - Encode.append_with buf (encode_elem elem) (@TagLenFmt {}) + Encode.append_with buf (encode_elem elem) (@TagLenFmt ()) expect actual = Encode.to_bytes [1, 2, 3] tag_len_fmt actual == (Str.to_utf8 "l3 n1 n2 n3 ") -encode_record = \fields -> Encode.custom \bytes, @TagLenFmt {} -> +encode_record = \fields -> Encode.custom \bytes, @TagLenFmt () -> bytes_pre = append_pre_len bytes 'r' (List.len fields) List.walk fields bytes_pre \buf, { key, value } -> - Encode.append_with buf (encode_string key) (@TagLenFmt {}) - |> Encode.append_with value (@TagLenFmt {}) + Encode.append_with buf (encode_string key) (@TagLenFmt ()) + |> Encode.append_with value (@TagLenFmt ()) expect actual = Encode.to_bytes { foo: "foo", bar: Bool.true } tag_len_fmt @@ -125,7 +125,7 @@ encode_tuple = \elems -> encode_list elems (\e -> e) encode_tag = \name, payload -> encode_tuple (List.prepend payload (encode_string name)) expect - actual = Encode.to_bytes (1, "foo", {}) tag_len_fmt + actual = Encode.to_bytes (1, "foo", ()) tag_len_fmt actual == (Str.to_utf8 "l3 n1 s3 foo r0 ") # DECODE @@ -147,7 +147,7 @@ decode_num_pre = \bytes, pre, to_num -> _ -> { result: Err TooShort, rest: bytes } -decode_num = \to_num -> Decode.custom \bytes, @TagLenFmt {} -> decode_num_pre bytes 'n' to_num +decode_num = \to_num -> Decode.custom \bytes, @TagLenFmt () -> decode_num_pre bytes 'n' to_num decode_u8 = decode_num Str.to_u8 decode_u16 = decode_num Str.to_u16 @@ -162,8 +162,8 @@ decode_i128 = decode_num Str.to_i128 decode_f32 = decode_num Str.to_f32 decode_f64 = decode_num Str.to_f64 decode_dec = decode_num Str.to_dec -decode_bool = Decode.custom \bytes, @TagLenFmt {} -> - { result: num_result, rest } = Decode.decode_with bytes decode_u8 (@TagLenFmt {}) +decode_bool = Decode.custom \bytes, @TagLenFmt () -> + { result: num_result, rest } = Decode.decode_with bytes decode_u8 (@TagLenFmt ()) when num_result is Ok 1 -> { result: Ok Bool.true, rest } Ok 0 -> { result: Ok Bool.false, rest } @@ -183,7 +183,7 @@ decode_try = \{ result, rest }, map -> Ok a -> map a rest Err e -> { result: Err e, rest } -decode_string = Decode.custom \bytes, @TagLenFmt {} -> +decode_string = Decode.custom \bytes, @TagLenFmt () -> decode_len_pre bytes 's' |> decode_try \len, len_rest -> { before, others } = List.split_at len_rest len @@ -202,13 +202,13 @@ repeat_decode = \pre, bytes, state, step_state -> List.range { start: At 0, end: Before end } |> List.walk { result: Ok state, rest: bs } \res, _i -> decode_try res \s, rest -> - Decode.decode_with rest (step_state s) (@TagLenFmt {}) + Decode.decode_with rest (step_state s) (@TagLenFmt ()) decode_len_pre bytes pre |> decode_try run -decode_list = \elem_decoder -> Decode.custom \bytes, @TagLenFmt {} -> - step = \lst -> Decode.custom \sbytes, @TagLenFmt {} -> - Decode.decode_with sbytes elem_decoder (@TagLenFmt {}) +decode_list = \elem_decoder -> Decode.custom \bytes, @TagLenFmt () -> + step = \lst -> Decode.custom \sbytes, @TagLenFmt () -> + Decode.decode_with sbytes elem_decoder (@TagLenFmt ()) |> Decode.map_result \elem -> List.append lst elem repeat_decode 'l' bytes [] step @@ -216,34 +216,34 @@ expect actual = Decode.from_bytes (Str.to_utf8 "l3 n1 n2 n3 ") tag_len_fmt actual == Ok [1, 2, 3] -decode_record = \init_state, step_field, finalizer -> Decode.custom \bytes, @TagLenFmt {} -> +decode_record = \init_state, step_field, finalizer -> Decode.custom \bytes, @TagLenFmt () -> flatten_field_res = \next, rest -> when next is Keep value_decoder -> { result: Ok value_decoder, rest } Skip -> { result: Err TooShort, rest } - step = \state -> Decode.custom \sbytes, @TagLenFmt {} -> - Decode.decode_with sbytes decode_string (@TagLenFmt {}) + step = \state -> Decode.custom \sbytes, @TagLenFmt () -> + Decode.decode_with sbytes decode_string (@TagLenFmt ()) |> decode_try \key, bs -> flatten_field_res (step_field state key) bs |> decode_try \value_decoder, bs -> - Decode.decode_with bs value_decoder (@TagLenFmt {}) + Decode.decode_with bs value_decoder (@TagLenFmt ()) repeat_decode 'r' bytes init_state step - |> decode_try \state, rest -> { result: finalizer state (@TagLenFmt {}), rest } + |> decode_try \state, rest -> { result: finalizer state (@TagLenFmt ()), rest } expect actual = Decode.from_bytes (Str.to_utf8 "r2 s3 bar n1 s3 foo s3 foo ") tag_len_fmt actual == Ok ({ foo: "foo", bar: Bool.true }) -decode_tuple = \initial_state, step_elem, finalizer -> Decode.custom \bytes, @TagLenFmt {} -> +decode_tuple = \initial_state, step_elem, finalizer -> Decode.custom \bytes, @TagLenFmt () -> flatten_field_res = \next, rest -> when next is Next dec -> { result: Ok dec, rest } TooLong -> { result: Err TooShort, rest } - step = \{ state, i } -> Decode.custom \sbytes, @TagLenFmt {} -> + step = \{ state, i } -> Decode.custom \sbytes, @TagLenFmt () -> flatten_field_res (step_elem state i) sbytes - |> decode_try \dec, rest -> Decode.decode_with rest dec (@TagLenFmt {}) + |> decode_try \dec, rest -> Decode.decode_with rest dec (@TagLenFmt ()) |> Decode.map_result \s -> { state: s, i: i + 1 } repeat_decode 'l' bytes { state: initial_state, i: 0 } step diff --git a/www/main.roc b/www/main.roc index 6b171ce1248..de69ea0fa01 100644 --- a/www/main.roc +++ b/www/main.roc @@ -29,7 +29,7 @@ main! = \{ input_dir, output_dir } -> List.for_each_try!(files, process_file!) page_data = - Dict.empty({}) + Dict.empty() |> Dict.insert("/abilities.html", { title: "Abilities | Roc", description: "Learn about abilities in the Roc programming language." }) |> Dict.insert("/bdfn.html", { title: "Governance | Roc", description: "Learn about the governance model of the Roc programming language." }) |> Dict.insert("/community.html", { title: "Community | Roc", description: "Connect with the community of the Roc programming language." })