From ebc4e5b81d194ef987dda015907ab116ea0399a8 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 15 May 2023 18:35:07 +0200 Subject: [PATCH] Replace the interpreter with an LLVM compiler This replaces the bytecode interpreter with a native code compiled, backed by LLVM. While this is a massive change, various internals remain the same. For example, for generics we still rely on runtime pointer checking and checks. This is a deliberate choice to keep the scope of changes from expanding even more, and we'll take care of these remaining issues separately. As part of these changes we've removed the libffi based FFI, as it makes no sense to use this API in a native code compiler. As introducing a new FFI is going to be a lot of work, we'll also take care of this separately, meaning you temporarily won't be able to interact with C code. The package manager (ipm) is also merged into the compiler executable ("inko"), removing the need for a separate command. For more information, see https://github.com/inko-lang/inko/pull/508. --- .cargo/config | 7 +- .github/workflows/test.yml | 34 +- .gitignore | 2 + Cargo.lock | 551 +- Cargo.toml | 2 +- Dockerfile | 18 +- Makefile | 44 +- README.md | 84 +- ast/src/lexer.rs | 32 +- ast/src/nodes.rs | 123 +- ast/src/parser.rs | 860 +- ast/src/source_location.rs | 4 + bytecode/Cargo.toml | 9 - bytecode/src/lib.rs | 1312 --- compiler/Cargo.toml | 4 +- compiler/build.rs | 49 + compiler/src/codegen.rs | 899 -- compiler/src/compiler.rs | 166 +- compiler/src/config.rs | 140 +- compiler/src/diagnostics.rs | 214 +- compiler/src/hir.rs | 1020 +-- compiler/src/lib.rs | 5 +- compiler/src/linker.rs | 108 + compiler/src/llvm.rs | 10 + compiler/src/llvm/builder.rs | 775 ++ compiler/src/llvm/constants.rs | 74 + compiler/src/llvm/context.rs | 146 + compiler/src/llvm/layouts.rs | 440 + compiler/src/llvm/method_hasher.rs | 96 + compiler/src/llvm/module.rs | 148 + compiler/src/llvm/passes.rs | 5339 ++++++++++++ compiler/src/llvm/runtime_function.rs | 1605 ++++ compiler/src/mir/mod.rs | 584 +- compiler/src/mir/passes.rs | 1372 +-- compiler/src/mir/pattern_matching.rs | 215 +- compiler/src/mir/printer.rs | 4 +- compiler/src/modules_parser.rs | 9 +- compiler/src/presenters.rs | 4 +- compiler/src/symbol_names.rs | 84 + compiler/src/target.rs | 294 + compiler/src/type_check/define_types.rs | 225 +- compiler/src/type_check/expressions.rs | 1941 ++--- compiler/src/type_check/imports.rs | 4 +- compiler/src/type_check/methods.rs | 514 +- compiler/src/type_check/mod.rs | 200 +- deny.toml | 3 +- docs/source/getting-started/installation.md | 10 +- docs/source/getting-started/ivm.md | 2 +- .../getting-started/pattern-matching.md | 10 +- docs/source/getting-started/syntax.md | 11 +- docs/source/guides/contributing.md | 6 +- docs/source/guides/operators.md | 2 +- docs/source/guides/testing.md | 2 +- docs/source/internals/vm.md | 17 +- inko/Cargo.toml | 5 +- inko/src/command.rs | 2 + inko/src/command/build.rs | 43 +- inko/src/command/check.rs | 5 +- inko/src/command/main.rs | 41 +- inko/src/command/pkg.rs | 54 + inko/src/command/pkg/add.rs | 85 + inko/src/command/pkg/init.rs | 62 + inko/src/command/pkg/remove.rs | 41 + .../command => inko/src/command/pkg}/sync.rs | 64 +- .../src/command/pkg}/update.rs | 38 +- inko/src/command/print.rs | 49 + inko/src/command/run.rs | 110 +- inko/src/command/test.rs | 44 +- inko/src/error.rs | 8 +- inko/src/main.rs | 1 + inko/src/options.rs | 2 +- {ipm/src => inko/src/pkg}/git.rs | 45 +- {ipm/src => inko/src/pkg}/manifest.rs | 45 +- inko/src/pkg/mod.rs | 4 + inko/src/pkg/util.rs | 88 + {ipm/src => inko/src/pkg}/version.rs | 4 +- ipm/Cargo.toml | 10 - ipm/src/command/add.rs | 84 - ipm/src/command/init.rs | 56 - ipm/src/command/main.rs | 58 - ipm/src/command/mod.rs | 6 - ipm/src/command/remove.rs | 39 - ipm/src/error.rs | 32 - ipm/src/macros.rs | 11 - ipm/src/main.rs | 20 - ipm/src/util.rs | 127 - libstd/src/std/clone.inko | 7 - libstd/src/std/ffi.inko | 543 -- libstd/src/std/fs/dir.inko | 117 - libstd/src/std/fs/file.inko | 261 - libstd/src/std/index.inko | 54 - libstd/src/std/process.inko | 92 - libstd/src/std/stdio.inko | 74 - libstd/test/helpers.inko | 114 - libstd/test/std/net/test_socket.inko | 1461 ---- libstd/test/std/test_env.inko | 92 - libstd/test/std/test_ffi.inko | 285 - libstd/test/std/test_json.inko | 407 - libstd/test/std/test_process.inko | 54 - libstd/test/std/test_stdio.inko | 81 - rt/Cargo.toml | 25 + {vm => rt}/build.rs | 0 {vm => rt}/src/arc_without_weak.rs | 1 + {vm => rt}/src/config.rs | 36 +- rt/src/context.rs | 67 + rt/src/context/unix.rs | 5 + rt/src/context/unix/aarch64.rs | 66 + rt/src/context/unix/x86_64.rs | 49 + {vm => rt}/src/immutable_string.rs | 33 +- {vm => rt}/src/lib.rs | 21 +- {vm => rt}/src/macros/mod.rs | 22 + rt/src/mem.rs | 624 ++ rt/src/memory_map.rs | 86 + {vm => rt}/src/network_poller.rs | 3 +- rt/src/page.rs | 26 + rt/src/process.rs | 1512 ++++ rt/src/result.rs | 96 + rt/src/runtime.rs | 143 + rt/src/runtime/array.rs | 115 + rt/src/runtime/byte_array.rs | 188 + rt/src/runtime/class.rs | 31 + rt/src/runtime/env.rs | 122 + rt/src/runtime/float.rs | 116 + rt/src/runtime/fs.rs | 369 + rt/src/runtime/general.rs | 74 + rt/src/runtime/helpers.rs | 16 + rt/src/runtime/int.rs | 74 + rt/src/runtime/process.rs | 377 + rt/src/runtime/random.rs | 86 + rt/src/runtime/socket.rs | 478 ++ rt/src/runtime/stdio.rs | 106 + rt/src/runtime/string.rs | 257 + rt/src/runtime/sys.rs | 229 + rt/src/runtime/time.rs | 96 + rt/src/scheduler/mod.rs | 30 + {vm => rt}/src/scheduler/process.rs | 385 +- {vm => rt}/src/scheduler/timeout_worker.rs | 27 +- {vm => rt}/src/scheduler/timeouts.rs | 13 +- {vm => rt}/src/socket.rs | 253 +- {vm => rt}/src/socket/socket_address.rs | 34 +- rt/src/stack.rs | 219 + rt/src/state.rs | 229 + {vm => rt}/src/test.rs | 80 +- scripts/llvm.sh | 18 + {libstd => std}/src/std/array.inko | 210 +- {libstd => std}/src/std/bool.inko | 12 +- {libstd => std}/src/std/byte_array.inko | 125 +- std/src/std/channel.inko | 153 + std/src/std/clone.inko | 7 + {libstd => std}/src/std/cmp.inko | 26 +- {libstd => std}/src/std/crypto/chacha.inko | 73 +- {libstd => std}/src/std/crypto/cipher.inko | 0 {libstd => std}/src/std/crypto/hash.inko | 53 +- {libstd => std}/src/std/crypto/math.inko | 13 +- {libstd => std}/src/std/crypto/md5.inko | 19 +- {libstd => std}/src/std/crypto/poly1305.inko | 12 +- {libstd => std}/src/std/crypto/sha1.inko | 32 +- {libstd => std}/src/std/crypto/sha2.inko | 62 +- {libstd => std}/src/std/debug.inko | 33 +- {libstd => std}/src/std/drop.inko | 0 {libstd => std}/src/std/endian/big.inko | 45 +- {libstd => std}/src/std/endian/little.inko | 45 +- {libstd => std}/src/std/env.inko | 80 +- {libstd => std}/src/std/float.inko | 59 +- {libstd => std}/src/std/fmt.inko | 4 +- std/src/std/fs/dir.inko | 127 + std/src/std/fs/file.inko | 308 + {libstd => std}/src/std/fs/path.inko | 142 +- {libstd => std}/src/std/hash.inko | 2 +- std/src/std/hash/siphash.inko | 99 + {libstd => std}/src/std/init.inko | 2 + {libstd => std}/src/std/int.inko | 111 +- {libstd => std}/src/std/io.inko | 26 +- {libstd => std}/src/std/iter.inko | 138 +- {libstd => std}/src/std/json.inko | 136 +- {libstd => std}/src/std/map.inko | 273 +- {libstd => std}/src/std/net/ip.inko | 90 +- {libstd => std}/src/std/net/socket.inko | 1005 +-- {libstd => std}/src/std/nil.inko | 12 +- {libstd => std}/src/std/ops.inko | 62 +- {libstd => std}/src/std/option.inko | 48 +- std/src/std/process.inko | 21 + {libstd => std}/src/std/rand.inko | 16 +- {libstd => std}/src/std/range.inko | 30 +- std/src/std/result.inko | 258 + {libstd => std}/src/std/set.inko | 14 +- std/src/std/stdio.inko | 81 + {libstd => std}/src/std/string.inko | 51 +- {libstd => std}/src/std/sys.inko | 165 +- {libstd => std}/src/std/test.inko | 379 +- {libstd => std}/src/std/time.inko | 90 +- {libstd => std}/src/std/tuple.inko | 106 +- {libstd => std}/src/std/utf8.inko | 0 std/test/helpers.inko | 137 + {libstd => std}/test/main.inko | 12 +- .../test/std/crypto/test_chacha.inko | 0 .../test/std/crypto/test_hash.inko | 6 +- .../test/std/crypto/test_math.inko | 7 + {libstd => std}/test/std/crypto/test_md5.inko | 6 +- .../test/std/crypto/test_poly1305.inko | 6 +- .../test/std/crypto/test_sha1.inko | 6 +- .../test/std/crypto/test_sha2.inko | 12 +- {libstd => std}/test/std/endian/test_big.inko | 23 +- .../test/std/endian/test_little.inko | 23 +- {libstd => std}/test/std/fs/test_dir.inko | 34 +- {libstd => std}/test/std/fs/test_file.inko | 156 +- {libstd => std}/test/std/fs/test_path.inko | 65 +- std/test/std/hash/test_siphash.inko | 85 + {libstd => std}/test/std/net/test_ip.inko | 0 std/test/std/net/test_socket.inko | 1415 ++++ {libstd => std}/test/std/test_array.inko | 83 +- {libstd => std}/test/std/test_bool.inko | 0 {libstd => std}/test/std/test_byte_array.inko | 55 +- {libstd => std}/test/std/test_cmp.inko | 8 +- {libstd => std}/test/std/test_debug.inko | 4 +- std/test/std/test_env.inko | 93 + {libstd => std}/test/std/test_float.inko | 4 +- {libstd => std}/test/std/test_fmt.inko | 0 {libstd => std}/test/std/test_int.inko | 0 {libstd => std}/test/std/test_io.inko | 30 +- {libstd => std}/test/std/test_iter.inko | 26 +- std/test/std/test_json.inko | 404 + {libstd => std}/test/std/test_map.inko | 132 +- {libstd => std}/test/std/test_nil.inko | 0 {libstd => std}/test/std/test_option.inko | 26 +- std/test/std/test_process.inko | 12 + {libstd => std}/test/std/test_rand.inko | 0 {libstd => std}/test/std/test_range.inko | 0 std/test/std/test_result.inko | 132 + {libstd => std}/test/std/test_set.inko | 0 std/test/std/test_stdio.inko | 87 + {libstd => std}/test/std/test_string.inko | 10 +- {libstd => std}/test/std/test_sys.inko | 42 +- {libstd => std}/test/std/test_test.inko | 48 +- {libstd => std}/test/std/test_time.inko | 56 +- {libstd => std}/test/std/test_tuple.inko | 0 {libstd => std}/test/std/test_utf8.inko | 0 types/Cargo.toml | 3 - types/src/check.rs | 1948 +++++ types/src/either.rs | 5 + types/src/format.rs | 815 ++ types/src/lib.rs | 7347 ++++------------- types/src/module_name.rs | 14 +- types/src/resolve.rs | 804 ++ types/src/test.rs | 165 + vm/Cargo.toml | 39 - vm/src/builtin_functions.rs | 216 - vm/src/builtin_functions/array.rs | 30 - vm/src/builtin_functions/byte_array.rs | 101 - vm/src/builtin_functions/env.rs | 142 - vm/src/builtin_functions/ffi.rs | 172 - vm/src/builtin_functions/float.rs | 27 - vm/src/builtin_functions/fs.rs | 379 - vm/src/builtin_functions/hasher.rs | 57 - vm/src/builtin_functions/process.rs | 79 - vm/src/builtin_functions/random.rs | 109 - vm/src/builtin_functions/socket.rs | 670 -- vm/src/builtin_functions/stdio.rs | 90 - vm/src/builtin_functions/string.rs | 191 - vm/src/builtin_functions/sys.rs | 227 - vm/src/builtin_functions/time.rs | 158 - vm/src/chunk.rs | 101 - vm/src/execution_context.rs | 143 - vm/src/ffi.rs | 731 -- vm/src/hasher.rs | 48 - vm/src/image.rs | 1076 --- vm/src/indexes.rs | 70 - vm/src/instructions/array.rs | 89 - vm/src/instructions/builtin_functions.rs | 23 - vm/src/instructions/byte_array.rs | 113 - vm/src/instructions/float.rs | 246 - vm/src/instructions/future.rs | 124 - vm/src/instructions/general.rs | 178 - vm/src/instructions/integer.rs | 326 - vm/src/instructions/mod.rs | 9 - vm/src/instructions/process.rs | 161 - vm/src/instructions/string.rs | 69 - vm/src/location_table.rs | 107 - vm/src/machine.rs | 1138 --- vm/src/mem.rs | 1164 --- vm/src/numeric.rs | 26 - vm/src/permanent_space.rs | 350 - vm/src/platform.rs | 74 - vm/src/process.rs | 1601 ---- vm/src/registers.rs | 43 - vm/src/runtime_error.rs | 80 - vm/src/scheduler/mod.rs | 3 - vm/src/state.rs | 138 - 288 files changed, 30859 insertions(+), 30855 deletions(-) delete mode 100644 bytecode/Cargo.toml delete mode 100644 bytecode/src/lib.rs create mode 100644 compiler/build.rs delete mode 100644 compiler/src/codegen.rs create mode 100644 compiler/src/linker.rs create mode 100644 compiler/src/llvm.rs create mode 100644 compiler/src/llvm/builder.rs create mode 100644 compiler/src/llvm/constants.rs create mode 100644 compiler/src/llvm/context.rs create mode 100644 compiler/src/llvm/layouts.rs create mode 100644 compiler/src/llvm/method_hasher.rs create mode 100644 compiler/src/llvm/module.rs create mode 100644 compiler/src/llvm/passes.rs create mode 100644 compiler/src/llvm/runtime_function.rs create mode 100644 compiler/src/symbol_names.rs create mode 100644 compiler/src/target.rs create mode 100644 inko/src/command/pkg.rs create mode 100644 inko/src/command/pkg/add.rs create mode 100644 inko/src/command/pkg/init.rs create mode 100644 inko/src/command/pkg/remove.rs rename {ipm/src/command => inko/src/command/pkg}/sync.rs (81%) rename {ipm/src/command => inko/src/command/pkg}/update.rs (78%) create mode 100644 inko/src/command/print.rs rename {ipm/src => inko/src/pkg}/git.rs (78%) rename {ipm/src => inko/src/pkg}/manifest.rs (92%) create mode 100644 inko/src/pkg/mod.rs create mode 100644 inko/src/pkg/util.rs rename {ipm/src => inko/src/pkg}/version.rs (98%) delete mode 100644 ipm/Cargo.toml delete mode 100644 ipm/src/command/add.rs delete mode 100644 ipm/src/command/init.rs delete mode 100644 ipm/src/command/main.rs delete mode 100644 ipm/src/command/mod.rs delete mode 100644 ipm/src/command/remove.rs delete mode 100644 ipm/src/error.rs delete mode 100644 ipm/src/macros.rs delete mode 100644 ipm/src/main.rs delete mode 100644 ipm/src/util.rs delete mode 100644 libstd/src/std/clone.inko delete mode 100644 libstd/src/std/ffi.inko delete mode 100644 libstd/src/std/fs/dir.inko delete mode 100644 libstd/src/std/fs/file.inko delete mode 100644 libstd/src/std/index.inko delete mode 100644 libstd/src/std/process.inko delete mode 100644 libstd/src/std/stdio.inko delete mode 100644 libstd/test/helpers.inko delete mode 100644 libstd/test/std/net/test_socket.inko delete mode 100644 libstd/test/std/test_env.inko delete mode 100644 libstd/test/std/test_ffi.inko delete mode 100644 libstd/test/std/test_json.inko delete mode 100644 libstd/test/std/test_process.inko delete mode 100644 libstd/test/std/test_stdio.inko create mode 100644 rt/Cargo.toml rename {vm => rt}/build.rs (100%) rename {vm => rt}/src/arc_without_weak.rs (99%) rename {vm => rt}/src/config.rs (78%) create mode 100644 rt/src/context.rs create mode 100644 rt/src/context/unix.rs create mode 100644 rt/src/context/unix/aarch64.rs create mode 100644 rt/src/context/unix/x86_64.rs rename {vm => rt}/src/immutable_string.rs (80%) rename {vm => rt}/src/lib.rs (63%) rename {vm => rt}/src/macros/mod.rs (59%) create mode 100644 rt/src/mem.rs create mode 100644 rt/src/memory_map.rs rename {vm => rt}/src/network_poller.rs (98%) create mode 100644 rt/src/page.rs create mode 100644 rt/src/process.rs create mode 100644 rt/src/result.rs create mode 100644 rt/src/runtime.rs create mode 100644 rt/src/runtime/array.rs create mode 100644 rt/src/runtime/byte_array.rs create mode 100644 rt/src/runtime/class.rs create mode 100644 rt/src/runtime/env.rs create mode 100644 rt/src/runtime/float.rs create mode 100644 rt/src/runtime/fs.rs create mode 100644 rt/src/runtime/general.rs create mode 100644 rt/src/runtime/helpers.rs create mode 100644 rt/src/runtime/int.rs create mode 100644 rt/src/runtime/process.rs create mode 100644 rt/src/runtime/random.rs create mode 100644 rt/src/runtime/socket.rs create mode 100644 rt/src/runtime/stdio.rs create mode 100644 rt/src/runtime/string.rs create mode 100644 rt/src/runtime/sys.rs create mode 100644 rt/src/runtime/time.rs create mode 100644 rt/src/scheduler/mod.rs rename {vm => rt}/src/scheduler/process.rs (76%) rename {vm => rt}/src/scheduler/timeout_worker.rs (91%) rename {vm => rt}/src/scheduler/timeouts.rs (96%) rename {vm => rt}/src/socket.rs (61%) rename {vm => rt}/src/socket/socket_address.rs (69%) create mode 100644 rt/src/stack.rs create mode 100644 rt/src/state.rs rename {vm => rt}/src/test.rs (53%) create mode 100755 scripts/llvm.sh rename {libstd => std}/src/std/array.inko (78%) rename {libstd => std}/src/std/bool.inko (86%) rename {libstd => std}/src/std/byte_array.inko (91%) create mode 100644 std/src/std/channel.inko create mode 100644 std/src/std/clone.inko rename {libstd => std}/src/std/cmp.inko (85%) rename {libstd => std}/src/std/crypto/chacha.inko (87%) rename {libstd => std}/src/std/crypto/cipher.inko (100%) rename {libstd => std}/src/std/crypto/hash.inko (76%) rename {libstd => std}/src/std/crypto/math.inko (71%) rename {libstd => std}/src/std/crypto/md5.inko (92%) rename {libstd => std}/src/std/crypto/poly1305.inko (96%) rename {libstd => std}/src/std/crypto/sha1.inko (85%) rename {libstd => std}/src/std/crypto/sha2.inko (90%) rename {libstd => std}/src/std/debug.inko (61%) rename {libstd => std}/src/std/drop.inko (100%) rename {libstd => std}/src/std/endian/big.inko (72%) rename {libstd => std}/src/std/endian/little.inko (73%) rename {libstd => std}/src/std/env.inko (56%) rename {libstd => std}/src/std/float.inko (83%) rename {libstd => std}/src/std/fmt.inko (95%) create mode 100644 std/src/std/fs/dir.inko create mode 100644 std/src/std/fs/file.inko rename {libstd => std}/src/std/fs/path.inko (69%) rename {libstd => std}/src/std/hash.inko (94%) create mode 100644 std/src/std/hash/siphash.inko rename {libstd => std}/src/std/init.inko (88%) rename {libstd => std}/src/std/int.inko (76%) rename {libstd => std}/src/std/io.inko (91%) rename {libstd => std}/src/std/iter.inko (81%) rename {libstd => std}/src/std/json.inko (86%) rename {libstd => std}/src/std/map.inko (77%) rename {libstd => std}/src/std/net/ip.inko (91%) rename {libstd => std}/src/std/net/socket.inko (54%) rename {libstd => std}/src/std/nil.inko (84%) rename {libstd => std}/src/std/ops.inko (54%) rename {libstd => std}/src/std/option.inko (90%) create mode 100644 std/src/std/process.inko rename {libstd => std}/src/std/rand.inko (91%) rename {libstd => std}/src/std/range.inko (87%) create mode 100644 std/src/std/result.inko rename {libstd => std}/src/std/set.inko (93%) create mode 100644 std/src/std/stdio.inko rename {libstd => std}/src/std/string.inko (95%) rename {libstd => std}/src/std/sys.inko (72%) rename {libstd => std}/src/std/test.inko (53%) rename {libstd => std}/src/std/time.inko (89%) rename {libstd => std}/src/std/tuple.inko (76%) rename {libstd => std}/src/std/utf8.inko (100%) create mode 100644 std/test/helpers.inko rename {libstd => std}/test/main.inko (89%) rename {libstd => std}/test/std/crypto/test_chacha.inko (100%) rename {libstd => std}/test/std/crypto/test_hash.inko (95%) rename {libstd => std}/test/std/crypto/test_math.inko (76%) rename {libstd => std}/test/std/crypto/test_md5.inko (92%) rename {libstd => std}/test/std/crypto/test_poly1305.inko (97%) rename {libstd => std}/test/std/crypto/test_sha1.inko (92%) rename {libstd => std}/test/std/crypto/test_sha2.inko (94%) rename {libstd => std}/test/std/endian/test_big.inko (80%) rename {libstd => std}/test/std/endian/test_little.inko (80%) rename {libstd => std}/test/std/fs/test_dir.inko (61%) rename {libstd => std}/test/std/fs/test_file.inko (52%) rename {libstd => std}/test/std/fs/test_path.inko (63%) create mode 100644 std/test/std/hash/test_siphash.inko rename {libstd => std}/test/std/net/test_ip.inko (100%) create mode 100644 std/test/std/net/test_socket.inko rename {libstd => std}/test/std/test_array.inko (77%) rename {libstd => std}/test/std/test_bool.inko (100%) rename {libstd => std}/test/std/test_byte_array.inko (85%) rename {libstd => std}/test/std/test_cmp.inko (93%) rename {libstd => std}/test/std/test_debug.inko (85%) create mode 100644 std/test/std/test_env.inko rename {libstd => std}/test/std/test_float.inko (97%) rename {libstd => std}/test/std/test_fmt.inko (100%) rename {libstd => std}/test/std/test_int.inko (100%) rename {libstd => std}/test/std/test_io.inko (81%) rename {libstd => std}/test/std/test_iter.inko (84%) create mode 100644 std/test/std/test_json.inko rename {libstd => std}/test/std/test_map.inko (59%) rename {libstd => std}/test/std/test_nil.inko (100%) rename {libstd => std}/test/std/test_option.inko (85%) create mode 100644 std/test/std/test_process.inko rename {libstd => std}/test/std/test_rand.inko (100%) rename {libstd => std}/test/std/test_range.inko (100%) create mode 100644 std/test/std/test_result.inko rename {libstd => std}/test/std/test_set.inko (100%) create mode 100644 std/test/std/test_stdio.inko rename {libstd => std}/test/std/test_string.inko (97%) rename {libstd => std}/test/std/test_sys.inko (71%) rename {libstd => std}/test/std/test_test.inko (70%) rename {libstd => std}/test/std/test_time.inko (91%) rename {libstd => std}/test/std/test_tuple.inko (100%) rename {libstd => std}/test/std/test_utf8.inko (100%) create mode 100644 types/src/check.rs create mode 100644 types/src/either.rs create mode 100644 types/src/format.rs create mode 100644 types/src/resolve.rs create mode 100644 types/src/test.rs delete mode 100644 vm/Cargo.toml delete mode 100644 vm/src/builtin_functions.rs delete mode 100644 vm/src/builtin_functions/array.rs delete mode 100644 vm/src/builtin_functions/byte_array.rs delete mode 100644 vm/src/builtin_functions/env.rs delete mode 100644 vm/src/builtin_functions/ffi.rs delete mode 100644 vm/src/builtin_functions/float.rs delete mode 100644 vm/src/builtin_functions/fs.rs delete mode 100644 vm/src/builtin_functions/hasher.rs delete mode 100644 vm/src/builtin_functions/process.rs delete mode 100644 vm/src/builtin_functions/random.rs delete mode 100644 vm/src/builtin_functions/socket.rs delete mode 100644 vm/src/builtin_functions/stdio.rs delete mode 100644 vm/src/builtin_functions/string.rs delete mode 100644 vm/src/builtin_functions/sys.rs delete mode 100644 vm/src/builtin_functions/time.rs delete mode 100644 vm/src/chunk.rs delete mode 100644 vm/src/execution_context.rs delete mode 100644 vm/src/ffi.rs delete mode 100644 vm/src/hasher.rs delete mode 100644 vm/src/image.rs delete mode 100644 vm/src/indexes.rs delete mode 100644 vm/src/instructions/array.rs delete mode 100644 vm/src/instructions/builtin_functions.rs delete mode 100644 vm/src/instructions/byte_array.rs delete mode 100644 vm/src/instructions/float.rs delete mode 100644 vm/src/instructions/future.rs delete mode 100644 vm/src/instructions/general.rs delete mode 100644 vm/src/instructions/integer.rs delete mode 100644 vm/src/instructions/mod.rs delete mode 100644 vm/src/instructions/process.rs delete mode 100644 vm/src/instructions/string.rs delete mode 100644 vm/src/location_table.rs delete mode 100644 vm/src/machine.rs delete mode 100644 vm/src/mem.rs delete mode 100644 vm/src/numeric.rs delete mode 100644 vm/src/permanent_space.rs delete mode 100644 vm/src/platform.rs delete mode 100644 vm/src/process.rs delete mode 100644 vm/src/registers.rs delete mode 100644 vm/src/runtime_error.rs delete mode 100644 vm/src/scheduler/mod.rs delete mode 100644 vm/src/state.rs diff --git a/.cargo/config b/.cargo/config index 1c4ab2c16..e7a82400e 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,2 +1,5 @@ -[target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'] -rustflags = ['-C', 'target-feature=+aes'] +# This is needed for stack traces to work when `panic=abort` is used. See +# https://github.com/rust-lang/rust/issues/94815 and +# https://github.com/rust-lang/backtrace-rs/issues/397 for more details. +[build] +rustflags = ['-C', 'force-unwind-tables'] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 021a86c4d..2caf51cf8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ on: - 'compiler/**' - 'inko/**' - 'ipm/**' - - 'libstd/**' + - 'std/**' - 'types/**' - 'vm/**' - 'Cargo.*' @@ -22,7 +22,9 @@ on: workflow_dispatch: env: - CARGO_HOME: ${{ github.workspace }}/.cargo + # This directory must be named differently from `.cargo`, otherwise it will + # conflict with our local Cargo configuration. + CARGO_HOME: ${{ github.workspace }}/.cargo-home REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} @@ -40,15 +42,17 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@1.62 + - uses: dtolnay/rust-toolchain@1.63 with: components: 'clippy' - uses: actions/cache@v3 with: path: | - ${{ github.workspace }}/.cargo + ${{ env.CARGO_HOME }} target key: ${{ runner.os }} + - name: Install LLVM + run: bash scripts/llvm.sh - run: 'cargo clippy -- -D warnings' cargo-fmt: @@ -61,7 +65,7 @@ jobs: - uses: actions/cache@v3 with: path: | - ${{ github.workspace }}/.cargo + ${{ env.CARGO_HOME }} target key: ${{ runner.os }} - run: 'cargo fmt --all --check' @@ -78,13 +82,13 @@ jobs: compiler: strategy: + fail-fast: false matrix: os: - macos-latest - ubuntu-latest - - windows-latest version: - - '1.62' + - '1.63' - stable runs-on: ${{ matrix.os }} steps: @@ -95,37 +99,41 @@ jobs: - uses: actions/cache@v3 with: path: | - ${{ github.workspace }}/.cargo + ${{ env.CARGO_HOME }} target key: ${{ runner.os }}-stable + - name: Install LLVM + run: bash scripts/llvm.sh - name: Running tests run: cargo test std: strategy: + fail-fast: false matrix: os: - macos-latest - ubuntu-latest - - windows-latest runs-on: ${{ matrix.os }} needs: - compiler steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@1.62 + - uses: dtolnay/rust-toolchain@1.63 - uses: actions/cache@v3 with: path: | - ${{ github.workspace }}/.cargo + ${{ env.CARGO_HOME }} target key: ${{ runner.os }}-stable + - name: Install LLVM + run: bash scripts/llvm.sh - name: Compiling run: cargo build --release - name: Running tests run: | - cd libstd - cargo run --release -p inko -- test + cd std + ../target/release/inko test nightly-container: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index af9cc95ed..23f3b2e63 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ *.egg-info/ __pycache__ /docs/plugins/pygments-inko-lexer/poetry.lock +/build +/std/build diff --git a/Cargo.lock b/Cargo.lock index ce95abcb8..aa915e3e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,21 +3,27 @@ version = 3 [[package]] -name = "abort_on_panic" -version = "2.0.0" +name = "addr2line" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955f37ac58af2416bac687c8ab66a4ccba282229bd7422a28d2281a5e66a6116" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] [[package]] -name = "ahash" -version = "0.8.0" +name = "adler" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e6e951cfbb2db8de1828d49073a113a29fd7117b1596caa781a258c7e38d72" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "version_check", + "memchr", ] [[package]] @@ -29,20 +35,47 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "blake2" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] @@ -58,15 +91,11 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "bytecode" -version = "0.10.0" - [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -79,31 +108,40 @@ name = "compiler" version = "0.10.0" dependencies = [ "ast", - "bytecode", + "fnv", "getopts", + "inkwell", "similar-asserts", "types", "unicode-segmentation", ] +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" -version = "0.15.1" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" dependencies = [ "encode_unicode", + "lazy_static", "libc", - "once_cell", - "terminal_size", - "winapi", + "windows-sys 0.42.0", ] [[package]] name = "crossbeam-queue" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if", "crossbeam-utils", @@ -111,12 +149,11 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -131,15 +168,21 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", "subtle", ] +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -147,16 +190,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] -name = "fs_extra" -version = "1.2.0" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -173,49 +216,71 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "gimli" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" + [[package]] name = "inko" version = "0.10.0" dependencies = [ + "blake2", "compiler", "getopts", "jemallocator", - "vm", ] [[package]] -name = "ipm" -version = "0.10.0" +name = "inkwell" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbac11e485159a525867fb7e6aa61981453e6a72f625fde6a4ab3047b0c6dec9" dependencies = [ - "blake2", - "getopts", + "either", + "inkwell_internals", + "libc", + "llvm-sys", + "once_cell", + "parking_lot", +] + +[[package]] +name = "inkwell_internals" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87d00c17e264ce02be5bc23d7bff959188ec7137beddd06b8b6b05a7c680ea85" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "jemalloc-sys" -version = "0.3.2" +version = "0.5.3+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45" +checksum = "f9bd5d616ea7ed58b571b2e209a65759664d7fb021a0819d7a790afc67e47ca1" dependencies = [ "cc", - "fs_extra", "libc", ] [[package]] name = "jemallocator" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69" +checksum = "16c2514137880c52b0b4822b563fadd38257c1f380858addb74a400889696ea6" dependencies = [ "jemalloc-sys", "libc", @@ -229,38 +294,31 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] -name = "libffi" -version = "3.0.0" +name = "llvm-sys" +version = "150.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e08093a2ddeee94bd0c830a53d895ff91f1f3bb0f9b3c8c6b00739cdf76bc1d" -dependencies = [ - "abort_on_panic", - "libc", - "libffi-sys", -] - -[[package]] -name = "libffi-sys" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4106b7f09d7b87d021334d5618fac1dfcfb824d4c5fe111ff0074dfd242e15" +checksum = "64be8a29d08e3165e4ed989b80cbc6b52104c543f16e272b83e2dedb749e81e6" dependencies = [ "cc", + "lazy_static", + "libc", + "regex", + "semver", ] [[package]] -name = "libloading" -version = "0.7.3" +name = "lock_api" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ - "cfg-if", - "winapi", + "autocfg", + "scopeguard", ] [[package]] @@ -278,30 +336,98 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.30.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.13.1" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "polling" -version = "2.2.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ + "autocfg", + "bitflags", "cfg-if", + "concurrent-queue", "libc", "log", - "wepoll-ffi", - "winapi", + "pin-project-lite", + "windows-sys 0.48.0", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] [[package]] name = "rand" @@ -326,24 +452,82 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + +[[package]] +name = "rt" +version = "0.10.0" +dependencies = [ + "backtrace", + "crossbeam-queue", + "crossbeam-utils", + "libc", + "polling", + "rand", + "socket2", + "unicode-segmentation", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "similar" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" dependencies = [ "bstr", "unicode-segmentation", @@ -359,14 +543,20 @@ dependencies = [ "similar", ] +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "socket2" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "6d283f86695ae989d1e18440a943880967156325ba025f05049946bff47bcc2b" dependencies = [ "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -376,39 +566,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] -name = "terminal_size" -version = "0.1.17" +name = "syn" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "libc", - "winapi", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "types" version = "0.10.0" -dependencies = [ - "bytecode", -] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "version_check" @@ -416,24 +610,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vm" -version = "0.10.0" -dependencies = [ - "ahash", - "bytecode", - "crossbeam-queue", - "crossbeam-utils", - "libc", - "libffi", - "libloading", - "polling", - "rand", - "socket2", - "unicode-segmentation", - "windows-sys", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -441,75 +617,148 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wepoll-ffi" -version = "0.1.2" +name = "windows-sys" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "cc", + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] -name = "winapi" -version = "0.3.9" +name = "windows-sys" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-targets 0.42.2", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-targets" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] [[package]] -name = "windows-sys" -version = "0.36.1" +name = "windows-targets" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml index 47ed6582f..9be6f9552 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["ast", "bytecode", "inko", "compiler", "vm", "ipm"] +members = ["ast", "inko", "compiler", "rt"] [profile.release] panic = "abort" diff --git a/Dockerfile b/Dockerfile index fd5ea68b2..0ca062415 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,24 @@ -FROM alpine:3 AS builder +FROM fedora-minimal:38 AS builder -RUN apk add --update make libffi libffi-dev rust cargo build-base +ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL sparse + +# Fedora builds LLVM with libffi support, and when statically linking against +# LLVM the build will fail if libffi-devel isn't installed, hence we include it +# here. See https://gitlab.com/taricorp/llvm-sys.rs/-/issues/41 for some extra +# details. +RUN microdnf install --assumeyes gcc make rust cargo \ + llvm15 llvm15-devel llvm15-static libstdc++-devel libstdc++-static \ + libffi-devel zlib-devel ADD . /inko/ WORKDIR /inko -RUN make build FEATURES=libffi-system PREFIX='/usr' +RUN make build PREFIX='/usr' RUN strip target/release/inko RUN make install PREFIX='/usr' -FROM alpine:3 +FROM fedora-minimal:38 # libgcc is needed because libgcc is dynamically linked to the executable. -RUN apk add --update libffi libffi-dev libgcc +RUN microdnf install --assumeyes libgcc COPY --from=builder ["/usr/bin/inko", "/usr/bin/inko"] COPY --from=builder ["/usr/lib/inko", "/usr/lib/inko/"] diff --git a/Makefile b/Makefile index e071b8507..3e15a7d96 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,9 @@ else INSTALL_PREFIX = ${PREFIX} endif +# The name of the static runtime library. +RUNTIME_NAME := libinko.a + # The directory to place the Inko executable in. INSTALL_INKO := ${INSTALL_PREFIX}/bin/inko @@ -18,7 +21,10 @@ INSTALL_INKO := ${INSTALL_PREFIX}/bin/inko INSTALL_IPM := ${INSTALL_PREFIX}/bin/ipm # The directory to place the standard library in. -INSTALL_STD := ${INSTALL_PREFIX}/lib/inko/libstd +INSTALL_STD := ${INSTALL_PREFIX}/lib/inko/std + +# The directory to place runtime library files in. +INSTALL_RT := ${INSTALL_PREFIX}/lib/inko/runtime/${RUNTIME_NAME} # The install path of the license file. INSTALL_LICENSE := ${INSTALL_PREFIX}/share/licenses/inko/LICENSE @@ -65,7 +71,7 @@ SOURCE_TAR := ${TMP_DIR}/${VERSION}.tar.gz MANIFEST := ${TMP_DIR}/manifest.txt build: - INKO_LIBSTD=${INSTALL_STD} cargo build --release ${FEATURES_OPTION} + INKO_STD=${INSTALL_STD} INKO_RT=${INSTALL_RT} cargo build --release ${FEATURES_OPTION} ${TMP_DIR}: mkdir -p "${@}" @@ -79,13 +85,12 @@ ${SOURCE_TAR}: ${TMP_DIR} LICENSE \ Makefile \ ast \ - bytecode \ compiler \ inko \ - libstd/src \ - vm \ + std/src \ + rt \ types \ - ipm \ + pkg \ | gzip > "${@}" release/source: ${SOURCE_TAR} @@ -119,33 +124,30 @@ release/publish: release/versions release/changelog release/commit release/tag ${INSTALL_STD}: mkdir -p "${@}" - cp -r libstd/src/* "${@}" + cp -r std/src/* "${@}" + +${INSTALL_RT}: + install -D -m644 target/release/${RUNTIME_NAME} "${@}" ${INSTALL_INKO}: - mkdir -p "$$(dirname ${@})" - install -m755 target/release/inko "${@}" + install -D -m755 target/release/inko "${@}" ${INSTALL_IPM}: - mkdir -p "$$(dirname ${@})" - install -m755 target/release/ipm "${@}" + install -D -m755 target/release/ipm "${@}" ${INSTALL_LICENSE}: - mkdir -p "$$(dirname ${@})" - install -m644 LICENSE "${@}" + install -D -m644 LICENSE "${@}" install: ${INSTALL_STD} \ + ${INSTALL_RT} \ ${INSTALL_INKO} \ ${INSTALL_IPM} \ ${INSTALL_LICENSE} -uninstall: - rm -rf ${INSTALL_STD} - rm -f ${INSTALL_INKO} - rm -f ${INSTALL_IPM} - rm -rf ${INSTALL_PREFIX}/share/licenses/inko - clean: rm -rf "${TMP_DIR}" + rm -rf build + rm -rf std/build cargo clean docs/install: @@ -183,6 +185,6 @@ rustfmt: .PHONY: release/source release/manifest release/changelog release/versions .PHONY: release/commit release/publish release/tag -.PHONY: build install uninstall clean -.PHONY: libstd/test rustfmt rustfmt-check clippy +.PHONY: build install clean +.PHONY: rustfmt rustfmt-check clippy .PHONY: docs/install docs/build docs/server docs/publish docs/versions diff --git a/README.md b/README.md index ab46970c2..e6ae82aca 100644 --- a/README.md +++ b/README.md @@ -2,86 +2,16 @@ Inko is a language for building concurrent software with confidence. Inko makes it easy to build concurrent software, without having to worry about -unpredictable performance, unexpected runtime errors, race conditions, and type -errors. +unpredictable performance, unexpected runtime errors, or race conditions. Inko features deterministic automatic memory management, move semantics, static -typing, type-safe concurrency, efficient error handling, and more. +typing, type-safe concurrency, efficient error handling, and more. Inko source +code is compiled to machine code using [LLVM](https://llvm.org/). -Inko supports 64-bits Linux, macOS and Windows, and installing Inko is quick and -easy. - -For more information, refer to the [Inko website](https://inko-lang.org/). - -## Features - -- Deterministic automatic memory management based on single ownership -- Easy concurrency through lightweight isolated processes -- Static typing -- Error handling done right -- An efficient, compact and portable bytecode interpreter -- Pattern matching -- Algebraic data types - -## Examples - -Here's what "Hello, World!" looks like in Inko: - -```inko -import std::stdio::STDOUT - -class async Main { - fn async main { - STDOUT.new.print('Hello, World!') - } -} -``` - -And here's how you'd define a concurrent counter: - -```inko -class async Counter { - let @value: Int - - fn async mut increment { - @value += 1 - } - - fn async value -> Int { - @value.clone - } -} - -class async Main { - fn async main { - let counter = Counter { @value = 0 } - - # "Main" will wait for the results of these calls, without blocking any OS - # threads. - counter.increment - counter.increment - counter.value # => 2 - } -} -``` - -Inko uses single ownership like Rust, but unlike Rust our implementation is -easier and less frustrating to use. For example, defining self-referential data -types such as doubly linked lists is trivial, and doesn't require unsafe code or -raw pointers: - -```inko -class Node[T] { - let @next: Option[Node[T]] - let @previous: Option[mut Node[T]] - let @value: T -} - -class List[T] { - let @head: Option[Node[T]] - let @tail: Option[mut Node[T]] -} -``` +For more information, refer to the [Inko website](https://inko-lang.org/) or +[the documentation](https://docs.inko-lang.org). If you'd like to follow the +development of Inko, consider joining [our Discord +server](https://discord.gg/seeURxHxCb). ## Installing diff --git a/ast/src/lexer.rs b/ast/src/lexer.rs index d5617a001..1ea3e0730 100644 --- a/ast/src/lexer.rs +++ b/ast/src/lexer.rs @@ -161,16 +161,13 @@ pub enum TokenKind { Sub, SubAssign, Throw, - Throws, Trait, True, Try, - TryPanic, Uni, UnicodeEscape, UnsignedShr, UnsignedShrAssign, - When, While, Whitespace, } @@ -261,12 +258,9 @@ impl TokenKind { TokenKind::Sub => "a '-'", TokenKind::SubAssign => "a '-='", TokenKind::Throw => "the 'throw' keyword", - TokenKind::Throws => "a '!!'", TokenKind::Trait => "the 'trait' keyword", TokenKind::Try => "the 'try' keyword", - TokenKind::TryPanic => "the 'try!' keyword", TokenKind::UnicodeEscape => "an Unicode escape sequence", - TokenKind::When => "the 'when' keyword", TokenKind::While => "the 'while' keyword", TokenKind::Whitespace => "whitespace", TokenKind::Mut => "the 'mut' keyword", @@ -335,8 +329,6 @@ impl Token { | TokenKind::Throw | TokenKind::Trait | TokenKind::Try - | TokenKind::TryPanic - | TokenKind::When | TokenKind::While | TokenKind::Mut | TokenKind::Recover @@ -924,12 +916,6 @@ impl Lexer { self.position += 2; self.token(TokenKind::Ne, start, self.line) } - EXCLAMATION => { - let start = self.position; - - self.position += 2; - self.token(TokenKind::Throws, start, self.line) - } _ => self.invalid(self.position, self.position + 1), } } @@ -952,7 +938,7 @@ impl Lexer { self.advance_identifier_bytes(); - let mut value = self.slice_string(start, self.position); + let value = self.slice_string(start, self.position); // We use this approach so that: // @@ -975,16 +961,7 @@ impl Lexer { "for" => TokenKind::For, "let" => TokenKind::Let, "ref" => TokenKind::Ref, - "try" => { - if self.current_byte() == EXCLAMATION { - self.position += 1; - - value.push('!'); - TokenKind::TryPanic - } else { - TokenKind::Try - } - } + "try" => TokenKind::Try, "mut" => TokenKind::Mut, "uni" => TokenKind::Uni, "pub" => TokenKind::Pub, @@ -997,7 +974,6 @@ impl Lexer { "loop" => TokenKind::Loop, "next" => TokenKind::Next, "self" => TokenKind::SelfObject, - "when" => TokenKind::When, "move" => TokenKind::Move, "true" => TokenKind::True, "case" => TokenKind::Case, @@ -1475,7 +1451,6 @@ mod tests { assert!(tok(TokenKind::Trait, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::True, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::Try, "", 1..=1, 1..=1).is_keyword()); - assert!(tok(TokenKind::When, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::While, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::Recover, "", 1..=1, 1..=1).is_keyword()); assert!(tok(TokenKind::Nil, "", 1..=1, 1..=1).is_keyword()); @@ -2047,7 +2022,6 @@ mod tests { fn test_lexer_exclamation() { assert_token!("!", Invalid, "!", 1..=1, 1..=1); assert_token!("!=", Ne, "!=", 1..=1, 1..=2); - assert_token!("!!", Throws, "!!", 1..=1, 1..=2); } #[test] @@ -2072,7 +2046,6 @@ mod tests { assert_token!("let", Let, "let", 1..=1, 1..=3); assert_token!("ref", Ref, "ref", 1..=1, 1..=3); assert_token!("try", Try, "try", 1..=1, 1..=3); - assert_token!("try!", TryPanic, "try!", 1..=1, 1..=4); assert_token!("mut", Mut, "mut", 1..=1, 1..=3); assert_token!("uni", Uni, "uni", 1..=1, 1..=3); assert_token!("pub", Pub, "pub", 1..=1, 1..=3); @@ -2083,7 +2056,6 @@ mod tests { assert_token!("loop", Loop, "loop", 1..=1, 1..=4); assert_token!("next", Next, "next", 1..=1, 1..=4); assert_token!("self", SelfObject, "self", 1..=1, 1..=4); - assert_token!("when", When, "when", 1..=1, 1..=4); assert_token!("move", Move, "move", 1..=1, 1..=4); assert_token!("true", True, "true", 1..=1, 1..=4); assert_token!("case", Case, "case", 1..=1, 1..=4); diff --git a/ast/src/nodes.rs b/ast/src/nodes.rs index ce2cc1657..303fd623f 100644 --- a/ast/src/nodes.rs +++ b/ast/src/nodes.rs @@ -152,18 +152,6 @@ impl Node for Constant { } } -#[derive(Debug, PartialEq, Eq)] -pub struct Async { - pub expression: Expression, - pub location: SourceLocation, -} - -impl Node for Async { - fn location(&self) -> &SourceLocation { - &self.location - } -} - #[derive(Debug, PartialEq, Eq)] pub struct Call { pub receiver: Option, @@ -381,7 +369,6 @@ pub struct DefineMethod { pub name: Identifier, pub type_parameters: Option, pub arguments: Option, - pub throw_type: Option, pub return_type: Option, pub body: Option, pub location: SourceLocation, @@ -547,6 +534,7 @@ pub struct ReopenClass { pub class_name: Constant, pub body: ImplementationExpressions, pub location: SourceLocation, + pub bounds: Option, } impl Node for ReopenClass { @@ -555,10 +543,38 @@ impl Node for ReopenClass { } } +#[cfg_attr(feature = "cargo-clippy", allow(clippy::large_enum_variant))] +#[derive(Debug, PartialEq, Eq)] +pub enum Requirement { + Trait(TypeName), + Mutable(SourceLocation), +} + +impl Node for Requirement { + fn location(&self) -> &SourceLocation { + match self { + Requirement::Trait(n) => &n.location, + Requirement::Mutable(loc) => loc, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Requirements { + pub values: Vec, + pub location: SourceLocation, +} + +impl Node for Requirements { + fn location(&self) -> &SourceLocation { + &self.location + } +} + #[derive(Debug, PartialEq, Eq)] pub struct TypeBound { pub name: Constant, - pub requirements: TypeNames, + pub requirements: Requirements, pub location: SourceLocation, } @@ -638,12 +654,9 @@ pub enum Expression { And(Box), Or(Box), TypeCast(Box), - Index(Box), - SetIndex(Box), Throw(Box), Return(Box), Try(Box), - TryPanic(Box), If(Box), Match(Box), Loop(Box), @@ -651,7 +664,6 @@ pub enum Expression { True(Box), False(Box), Nil(Box), - Async(Box), ClassLiteral(Box), Scope(Box), Array(Box), @@ -684,7 +696,6 @@ impl Node for Expression { Expression::AssignSetter(ref typ) => typ.location(), Expression::AssignVariable(ref typ) => typ.location(), Expression::ReplaceVariable(ref typ) => typ.location(), - Expression::Async(ref typ) => typ.location(), Expression::Binary(ref typ) => typ.location(), Expression::BinaryAssignField(ref typ) => typ.location(), Expression::BinaryAssignSetter(ref typ) => typ.location(), @@ -702,7 +713,6 @@ impl Node for Expression { Expression::Group(ref typ) => typ.location(), Expression::Identifier(ref typ) => typ.location(), Expression::If(ref typ) => typ.location(), - Expression::Index(ref typ) => typ.location(), Expression::Int(ref typ) => typ.location(), Expression::Loop(ref typ) => typ.location(), Expression::Match(ref typ) => typ.location(), @@ -712,13 +722,11 @@ impl Node for Expression { Expression::Return(ref typ) => typ.location(), Expression::Scope(ref typ) => typ.location(), Expression::SelfObject(ref typ) => typ.location(), - Expression::SetIndex(ref typ) => typ.location(), Expression::SingleString(ref typ) => typ.location(), Expression::Throw(ref typ) => typ.location(), Expression::True(ref typ) => typ.location(), Expression::Nil(ref typ) => typ.location(), Expression::Try(ref typ) => typ.location(), - Expression::TryPanic(ref typ) => typ.location(), Expression::Tuple(ref typ) => typ.location(), Expression::TypeCast(ref typ) => typ.location(), Expression::While(ref typ) => typ.location(), @@ -755,7 +763,7 @@ impl Node for TypeNames { #[derive(Debug, PartialEq, Eq)] pub struct TypeParameter { pub name: Constant, - pub requirements: Option, + pub requirements: Option, pub location: SourceLocation, } @@ -899,7 +907,6 @@ impl Node for ReferrableType { #[derive(Debug, PartialEq, Eq)] pub struct ClosureType { pub arguments: Option, - pub throw_type: Option, pub return_type: Option, pub location: SourceLocation, } @@ -1046,7 +1053,6 @@ impl Node for BlockArguments { pub struct Closure { pub moving: bool, pub arguments: Option, - pub throw_type: Option, pub return_type: Option, pub body: Expressions, pub location: SourceLocation, @@ -1250,33 +1256,6 @@ impl Node for TypeCast { } } -#[derive(Debug, PartialEq, Eq)] -pub struct Index { - pub receiver: Expression, - pub index: Expression, - pub location: SourceLocation, -} - -impl Node for Index { - fn location(&self) -> &SourceLocation { - &self.location - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct SetIndex { - pub receiver: Expression, - pub index: Expression, - pub value: Expression, - pub location: SourceLocation, -} - -impl Node for SetIndex { - fn location(&self) -> &SourceLocation { - &self.location - } -} - #[derive(Debug, PartialEq, Eq)] pub struct Throw { pub value: Expression, @@ -1301,35 +1280,9 @@ impl Node for Return { } } -#[derive(Debug, PartialEq, Eq)] -pub struct TryBlock { - pub value: Expression, - pub location: SourceLocation, -} - -impl Node for TryBlock { - fn location(&self) -> &SourceLocation { - &self.location - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct ElseBlock { - pub body: Expressions, - pub argument: Option, - pub location: SourceLocation, -} - -impl Node for ElseBlock { - fn location(&self) -> &SourceLocation { - &self.location - } -} - #[derive(Debug, PartialEq, Eq)] pub struct Try { - pub try_block: TryBlock, - pub else_block: Option, + pub expression: Expression, pub location: SourceLocation, } @@ -1339,18 +1292,6 @@ impl Node for Try { } } -#[derive(Debug, PartialEq, Eq)] -pub struct TryPanic { - pub try_block: TryBlock, - pub location: SourceLocation, -} - -impl Node for TryPanic { - fn location(&self) -> &SourceLocation { - &self.location - } -} - #[derive(Debug, PartialEq, Eq)] pub struct IfCondition { pub condition: Expression, diff --git a/ast/src/parser.rs b/ast/src/parser.rs index 8a2dcb59a..4d202c70c 100644 --- a/ast/src/parser.rs +++ b/ast/src/parser.rs @@ -524,45 +524,12 @@ impl Parser { fn optional_type_parameter_requirements( &mut self, - ) -> Result, ParseError> { + ) -> Result, ParseError> { if self.peek().kind != TokenKind::Colon { return Ok(None); } - self.next(); - - let mut values = Vec::new(); - - loop { - let token = self.require()?; - - values.push(self.type_name_with_optional_namespace(token)?); - - let after = self.peek(); - - match after.kind { - TokenKind::Add => { - self.next(); - } - TokenKind::BracketClose | TokenKind::Comma => { - break; - } - _ => { - error!( - after.location.clone(), - "Expected a '+' or a ']', found '{}' instead", - after.value - ); - } - } - } - - let location = SourceLocation::start_end( - values.first().unwrap().location(), - values.last().unwrap().location(), - ); - - Ok(Some(TypeNames { values, location })) + Ok(Some(self.requirements()?)) } fn reference_type( @@ -631,15 +598,13 @@ impl Parser { start: Token, ) -> Result { let arguments = self.optional_block_argument_types()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let end_loc = location!(return_type) - .or_else(|| location!(throw_type)) .or_else(|| location!(arguments)) .unwrap_or(&start.location); let location = SourceLocation::start_end(&start.location, end_loc); - Ok(ClosureType { arguments, throw_type, return_type, location }) + Ok(ClosureType { arguments, return_type, location }) } fn optional_block_argument_types( @@ -733,18 +698,6 @@ impl Parser { Ok(BlockArgument { name, value_type, location }) } - fn optional_throw_type(&mut self) -> Result, ParseError> { - if self.peek().kind != TokenKind::Throws { - return Ok(None); - } - - self.next(); - - let start = self.require()?; - - Ok(Some(self.type_reference(start)?)) - } - fn optional_return_type(&mut self) -> Result, ParseError> { if self.peek().kind != TokenKind::Arrow { return Ok(None); @@ -767,7 +720,6 @@ impl Parser { let (name, operator) = self.method_name(name_token)?; let type_parameters = self.optional_type_parameter_definitions()?; let arguments = self.optional_method_arguments()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let body_token = self.expect(TokenKind::CurlyOpen)?; let body = self.expressions(body_token)?; @@ -780,7 +732,6 @@ impl Parser { name, type_parameters, arguments, - throw_type, return_type, location, body: Some(body), @@ -822,7 +773,6 @@ impl Parser { let (name, operator) = self.method_name(name_token)?; let type_parameters = self.optional_type_parameter_definitions()?; let arguments = self.optional_method_arguments()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let body_token = self.expect(TokenKind::CurlyOpen)?; let body = self.expressions(body_token)?; @@ -835,7 +785,6 @@ impl Parser { name, type_parameters, arguments, - throw_type, return_type, location, body: Some(body), @@ -863,7 +812,6 @@ impl Parser { let (name, operator) = self.method_name(name_token)?; let type_parameters = self.optional_type_parameter_definitions()?; let arguments = self.optional_method_arguments()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let body_token = self.expect(TokenKind::CurlyOpen)?; let body = self.expressions(body_token)?; @@ -876,7 +824,6 @@ impl Parser { name, type_parameters, arguments, - throw_type, return_type, location, body: Some(body), @@ -1112,22 +1059,28 @@ impl Parser { fn type_bound(&mut self, start: Token) -> Result { let name = Constant::from(start); - let requirements = self.type_bound_requirements()?; + let requirements = self.requirements()?; let location = SourceLocation::start_end(name.location(), requirements.location()); Ok(TypeBound { name, requirements, location }) } - fn type_bound_requirements(&mut self) -> Result { + fn requirements(&mut self) -> Result { self.expect(TokenKind::Colon)?; let mut values = Vec::new(); loop { let token = self.require()?; + let req = match token.kind { + TokenKind::Mut => Requirement::Mutable(token.location), + _ => Requirement::Trait( + self.type_name_with_optional_namespace(token)?, + ), + }; - values.push(self.type_name_with_optional_namespace(token)?); + values.push(req); let after = self.peek(); @@ -1146,7 +1099,7 @@ impl Parser { values.last().unwrap().location(), ); - Ok(TypeNames { values, location }) + Ok(Requirements { values, location }) } fn reopen_class( @@ -1155,14 +1108,16 @@ impl Parser { class_token: Token, ) -> Result { let class_name = Constant::from(class_token); + let bounds = self.optional_type_bounds()?; let body = self.reopen_class_expressions()?; - let location = - SourceLocation::start_end(&start.location, body.location()); + let end_loc = location!(bounds).unwrap_or_else(|| body.location()); + let location = SourceLocation::start_end(&start.location, end_loc); Ok(TopLevelExpression::ReopenClass(Box::new(ReopenClass { class_name, body, location, + bounds, }))) } @@ -1291,7 +1246,6 @@ impl Parser { let (name, operator) = self.method_name(name_token)?; let type_parameters = self.optional_type_parameter_definitions()?; let arguments = self.optional_method_arguments()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let body = if self.peek().kind == TokenKind::CurlyOpen { let body_token = self.expect(TokenKind::CurlyOpen)?; @@ -1302,7 +1256,6 @@ impl Parser { }; let end_loc = location!(body) .or_else(|| location!(return_type)) - .or_else(|| location!(throw_type)) .or_else(|| location!(arguments)) .or_else(|| location!(type_parameters)) .unwrap_or_else(|| name.location()); @@ -1314,7 +1267,6 @@ impl Parser { name, type_parameters, arguments, - throw_type, return_type, location, body, @@ -1368,28 +1320,6 @@ impl Parser { }) } - fn expression_with_optional_curly_braces( - &mut self, - ) -> Result<(Expression, SourceLocation), ParseError> { - if self.peek().kind == TokenKind::CurlyOpen { - let open = self.next(); - let token = self.require()?; - let expr = self.expression(token)?; - let close = self.expect(TokenKind::CurlyClose)?; - - Ok(( - expr, - SourceLocation::start_end(&open.location, &close.location), - )) - } else { - let token = self.require()?; - let expr = self.expression(token)?; - let loc = expr.location().clone(); - - Ok((expr, loc)) - } - } - fn type_cast(&mut self, start: Token) -> Result { let mut node = self.boolean_and_or(start)?; @@ -1491,67 +1421,25 @@ impl Parser { } fn postfix(&mut self, start: Token) -> Result { - let start_line = *start.location.line_range.start(); let mut node = self.value(start)?; loop { let peeked = self.peek(); - match peeked.kind { - TokenKind::Dot => { - node = self.call_with_receiver(node)?; - } - TokenKind::BracketOpen - if *peeked.location.line_range.start() == start_line => - { - node = self.index_expression(node)?; - } - _ => break, + if let TokenKind::Dot = peeked.kind { + node = self.call_with_receiver(node)?; + } else { + break; } } Ok(node) } - fn index_expression( - &mut self, - receiver: Expression, - ) -> Result { - self.next(); - - let index_token = self.require()?; - let index = self.expression(index_token)?; - let close = self.expect(TokenKind::BracketClose)?; - - if self.peek().kind == TokenKind::Assign { - self.next(); - - let value_token = self.require()?; - let value = self.expression(value_token)?; - let location = SourceLocation::start_end( - receiver.location(), - value.location(), - ); - - Ok(Expression::SetIndex(Box::new(SetIndex { - receiver, - index, - value, - location, - }))) - } else { - let location = - SourceLocation::start_end(receiver.location(), &close.location); - - Ok(Expression::Index(Box::new(Index { receiver, index, location }))) - } - } - fn value(&mut self, start: Token) -> Result { // When updating this match, also update the one used for parsing return // value expressions. let value = match start.kind { - TokenKind::Async => self.async_expression(start)?, TokenKind::BracketOpen => self.array_literal(start)?, TokenKind::Break => self.break_loop(start), TokenKind::Constant => self.constant(start)?, @@ -1578,7 +1466,6 @@ impl Parser { TokenKind::True => self.true_literal(start), TokenKind::Nil => self.nil_literal(start), TokenKind::Try => self.try_expression(start)?, - TokenKind::TryPanic => self.try_panic_expression(start)?, TokenKind::While => self.while_expression(start)?, TokenKind::Let => self.define_variable(start)?, _ => { @@ -2268,20 +2155,13 @@ impl Parser { false }; let arguments = self.optional_closure_arguments()?; - let throw_type = self.optional_throw_type()?; let return_type = self.optional_return_type()?; let body_token = self.expect(TokenKind::CurlyOpen)?; let body = self.expressions(body_token)?; let location = SourceLocation::start_end(&start.location, &body.location); - let closure = Closure { - moving, - body, - arguments, - throw_type, - return_type, - location, - }; + let closure = + Closure { moving, body, arguments, return_type, location }; Ok(Expression::Closure(Box::new(closure))) } @@ -2376,17 +2256,6 @@ impl Parser { Expression::False(Box::new(False { location: start.location })) } - fn async_expression( - &mut self, - start: Token, - ) -> Result { - let (expression, expr_loc) = - self.expression_with_optional_curly_braces()?; - let location = SourceLocation::start_end(&start.location, &expr_loc); - - Ok(Expression::Async(Box::new(Async { expression, location }))) - } - fn group_or_tuple( &mut self, start: Token, @@ -2490,8 +2359,7 @@ impl Parser { == start.location.line_range.start(); let value = match peeked.kind { - TokenKind::Async - | TokenKind::BracketOpen + TokenKind::BracketOpen | TokenKind::Break | TokenKind::Constant | TokenKind::CurlyOpen @@ -2518,7 +2386,6 @@ impl Parser { | TokenKind::Throw | TokenKind::True | TokenKind::Try - | TokenKind::TryPanic | TokenKind::While if same_line => { @@ -2539,60 +2406,12 @@ impl Parser { &mut self, start: Token, ) -> Result { - let try_block = self.try_block()?; - let else_block = self.optional_else_block()?; - let end_loc = - location!(else_block).unwrap_or_else(|| try_block.location()); + let expr_token = self.require()?; + let expression = self.expression(expr_token)?; + let end_loc = expression.location(); let location = SourceLocation::start_end(&start.location, end_loc); - Ok(Expression::Try(Box::new(Try { try_block, else_block, location }))) - } - - fn try_panic_expression( - &mut self, - start: Token, - ) -> Result { - let try_block = self.try_block()?; - let location = - SourceLocation::start_end(&start.location, try_block.location()); - - Ok(Expression::TryPanic(Box::new(TryPanic { try_block, location }))) - } - - fn optional_try_binding( - &mut self, - ) -> Result, ParseError> { - if self.peek().kind != TokenKind::ParenOpen { - return Ok(None); - } - - self.next(); - - let start = self.require()?; - let arg = self.define_closure_argument(start)?; - - self.expect(TokenKind::ParenClose)?; - Ok(Some(arg)) - } - - fn try_block(&mut self) -> Result { - let (value, location) = self.expression_with_optional_curly_braces()?; - - Ok(TryBlock { value, location }) - } - - fn optional_else_block(&mut self) -> Result, ParseError> { - if self.peek().kind != TokenKind::Else { - return Ok(None); - } - - let start = self.next(); - let binding = self.optional_try_binding()?; - let body = self.expressions_with_optional_curly_braces()?; - let location = - SourceLocation::start_end(&start.location, body.location()); - - Ok(Some(ElseBlock { argument: binding, body, location })) + Ok(Expression::Try(Box::new(Try { expression, location }))) } fn if_expression( @@ -3760,7 +3579,6 @@ mod tests { parser.type_reference(start).unwrap(), Type::Closure(Box::new(ClosureType { arguments: None, - throw_type: None, return_type: None, location: cols(1, 2) })) @@ -3787,37 +3605,12 @@ mod tests { }))], location: cols(4, 6) }), - throw_type: None, return_type: None, location: cols(1, 6) })) ); } - #[test] - fn test_type_reference_with_closure_type_with_throw_type() { - let mut parser = parser("fn !! T"); - let start = parser.require().unwrap(); - - assert_eq!( - parser.type_reference(start).unwrap(), - Type::Closure(Box::new(ClosureType { - arguments: None, - throw_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "T".to_string(), - location: cols(7, 7), - }, - arguments: None, - location: cols(7, 7) - }))), - return_type: None, - location: cols(1, 7) - })) - ); - } - #[test] fn test_type_reference_with_closure_type_with_return_type() { let mut parser = parser("fn -> T"); @@ -3827,7 +3620,6 @@ mod tests { parser.type_reference(start).unwrap(), Type::Closure(Box::new(ClosureType { arguments: None, - throw_type: None, return_type: Some(Type::Named(Box::new(TypeName { name: Constant { source: None, @@ -3844,7 +3636,7 @@ mod tests { #[test] fn test_type_reference_with_full_closure_type() { - let mut parser = parser("fn (B) !! C -> D"); + let mut parser = parser("fn (B) -> D"); let start = parser.require().unwrap(); assert_eq!( @@ -3862,25 +3654,16 @@ mod tests { }))], location: cols(4, 6) }), - throw_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "C".to_string(), - location: cols(11, 11), - }, - arguments: None, - location: cols(11, 11) - }))), return_type: Some(Type::Named(Box::new(TypeName { name: Constant { source: None, name: "D".to_string(), - location: cols(16, 16), + location: cols(11, 11), }, arguments: None, - location: cols(16, 16) + location: cols(11, 11) }))), - location: cols(1, 16) + location: cols(1, 11) })) ); } @@ -3977,7 +3760,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -3999,7 +3781,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4021,7 +3802,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4043,7 +3823,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4065,7 +3844,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4087,7 +3865,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4109,7 +3886,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4145,7 +3921,6 @@ mod tests { location: cols(8, 10) }), arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4172,9 +3947,9 @@ mod tests { name: "T".to_string(), location: cols(9, 9) }, - requirements: Some(TypeNames { + requirements: Some(Requirements { values: vec![ - TypeName { + Requirement::Trait(TypeName { name: Constant { source: None, name: "A".to_string(), @@ -4182,8 +3957,8 @@ mod tests { }, arguments: None, location: cols(12, 12) - }, - TypeName { + }), + Requirement::Trait(TypeName { name: Constant { source: None, name: "B".to_string(), @@ -4191,7 +3966,7 @@ mod tests { }, arguments: None, location: cols(16, 16) - } + }) ], location: cols(12, 16) }), @@ -4200,7 +3975,6 @@ mod tests { location: cols(8, 17) }), arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4261,7 +4035,6 @@ mod tests { ], location: cols(8, 19) }), - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4272,39 +4045,6 @@ mod tests { ); } - #[test] - fn test_method_with_throw_type() { - assert_eq!( - top(parse("fn foo !! A {}")), - TopLevelExpression::DefineMethod(Box::new(DefineMethod { - public: false, - operator: false, - kind: MethodKind::Instance, - name: Identifier { - name: "foo".to_string(), - location: cols(4, 6) - }, - type_parameters: None, - arguments: None, - throw_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "A".to_string(), - location: cols(11, 11), - }, - arguments: None, - location: cols(11, 11) - }))), - return_type: None, - body: Some(Expressions { - values: Vec::new(), - location: cols(13, 14) - }), - location: cols(1, 14) - })) - ); - } - #[test] fn test_method_with_return_type() { assert_eq!( @@ -4319,7 +4059,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: Some(Type::Named(Box::new(TypeName { name: Constant { source: None, @@ -4352,7 +4091,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: vec![Expression::Int(Box::new(IntLiteral { @@ -4372,7 +4110,6 @@ mod tests { assert_error!("fn foo [A: ] {}", cols(12, 12)); assert_error!("fn foo (A: ) {}", cols(9, 9)); assert_error!("fn foo (a: ) {}", cols(12, 12)); - assert_error!("fn foo !! {}", cols(11, 11)); assert_error!("fn foo -> {}", cols(11, 11)); assert_error!("fn foo {", cols(8, 8)); assert_error!("fn foo", cols(6, 6)); @@ -4501,7 +4238,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4539,7 +4275,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4575,8 +4310,8 @@ mod tests { name: "B".to_string(), location: cols(9, 9) }, - requirements: Some(TypeNames { - values: vec![TypeName { + requirements: Some(Requirements { + values: vec![Requirement::Trait(TypeName { name: Constant { source: None, name: "X".to_string(), @@ -4584,7 +4319,7 @@ mod tests { }, arguments: None, location: cols(12, 12) - }], + })], location: cols(12, 12) }), location: cols(9, 12) @@ -4626,8 +4361,8 @@ mod tests { name: "B".to_string(), location: cols(9, 9) }, - requirements: Some(TypeNames { - values: vec![TypeName { + requirements: Some(Requirements { + values: vec![Requirement::Trait(TypeName { name: Constant { source: Some(Identifier { name: "a".to_string(), @@ -4638,7 +4373,7 @@ mod tests { }, arguments: None, location: cols(12, 15) - }], + })], location: cols(12, 15) }), location: cols(9, 15) @@ -4679,7 +4414,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4717,7 +4451,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4758,7 +4491,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4799,7 +4531,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -4840,7 +4571,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5006,7 +4736,7 @@ mod tests { ); assert_eq!( - top(parse("impl A for B if X: A + B, Y: C {}")), + top(parse("impl A for B if X: A + B, Y: C + mut {}")), TopLevelExpression::ImplementTrait(Box::new(ImplementTrait { trait_name: TypeName { name: Constant { @@ -5024,7 +4754,7 @@ mod tests { }, body: ImplementationExpressions { values: Vec::new(), - location: cols(32, 33) + location: cols(38, 39) }, bounds: Some(TypeBounds { values: vec![ @@ -5034,9 +4764,9 @@ mod tests { name: "X".to_string(), location: cols(17, 17) }, - requirements: TypeNames { + requirements: Requirements { values: vec![ - TypeName { + Requirement::Trait(TypeName { name: Constant { source: None, name: "A".to_string(), @@ -5044,8 +4774,8 @@ mod tests { }, arguments: None, location: cols(20, 20) - }, - TypeName { + }), + Requirement::Trait(TypeName { name: Constant { source: None, name: "B".to_string(), @@ -5053,7 +4783,7 @@ mod tests { }, arguments: None, location: cols(24, 24) - }, + }), ], location: cols(20, 24) }, @@ -5065,24 +4795,27 @@ mod tests { name: "Y".to_string(), location: cols(27, 27) }, - requirements: TypeNames { - values: vec![TypeName { - name: Constant { - source: None, - name: "C".to_string(), + requirements: Requirements { + values: vec![ + Requirement::Trait(TypeName { + name: Constant { + source: None, + name: "C".to_string(), + location: cols(30, 30) + }, + arguments: None, location: cols(30, 30) - }, - arguments: None, - location: cols(30, 30) - }], - location: cols(30, 30) + }), + Requirement::Mutable(cols(34, 36)) + ], + location: cols(30, 36) }, - location: cols(27, 30) + location: cols(27, 36) } ], - location: cols(17, 30) + location: cols(17, 36) }), - location: cols(1, 33) + location: cols(1, 39) })) ); @@ -5114,7 +4847,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5144,6 +4876,7 @@ mod tests { values: Vec::new(), location: cols(8, 9) }, + bounds: None, location: cols(1, 9) })) ); @@ -5167,7 +4900,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5177,6 +4909,7 @@ mod tests { }], location: cols(8, 20) }, + bounds: None, location: cols(1, 20) })) ); @@ -5200,7 +4933,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5210,9 +4942,41 @@ mod tests { }], location: cols(8, 26) }, + bounds: None, location: cols(1, 26) })) ); + + assert_eq!( + top(parse("impl A if T: mut {}")), + TopLevelExpression::ReopenClass(Box::new(ReopenClass { + class_name: Constant { + source: None, + name: "A".to_string(), + location: cols(6, 6) + }, + body: ImplementationExpressions { + values: Vec::new(), + location: cols(18, 19) + }, + bounds: Some(TypeBounds { + values: vec![TypeBound { + name: Constant { + source: None, + name: "T".to_string(), + location: cols(11, 11) + }, + requirements: Requirements { + values: vec![Requirement::Mutable(cols(14, 16))], + location: cols(14, 16) + }, + location: cols(11, 16) + }], + location: cols(11, 16) + }), + location: cols(1, 16) + })) + ); } #[test] @@ -5236,7 +5000,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5246,6 +5009,7 @@ mod tests { }], location: cols(8, 27) }, + bounds: None, location: cols(1, 27) })) ); @@ -5395,8 +5159,8 @@ mod tests { name: "B".to_string(), location: cols(9, 9) }, - requirements: Some(TypeNames { - values: vec![TypeName { + requirements: Some(Requirements { + values: vec![Requirement::Trait(TypeName { name: Constant { source: None, name: "X".to_string(), @@ -5404,7 +5168,7 @@ mod tests { }, arguments: None, location: cols(12, 12) - }], + })], location: cols(12, 12) }), location: cols(9, 12) @@ -5455,7 +5219,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: None, location: cols(11, 16) @@ -5491,7 +5254,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: None, location: cols(11, 16) @@ -5504,9 +5266,9 @@ mod tests { } #[test] - fn test_trait_with_required_method_with_throw_type() { + fn test_trait_with_required_method_with_return_type() { assert_eq!( - top(parse("trait A { fn foo !! A }")), + top(parse("trait A { fn foo -> A }")), TopLevelExpression::DefineTrait(Box::new(DefineTrait { public: false, name: Constant { @@ -5527,7 +5289,7 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: Some(Type::Named(Box::new(TypeName { + return_type: Some(Type::Named(Box::new(TypeName { name: Constant { source: None, name: "A".to_string(), @@ -5536,7 +5298,6 @@ mod tests { arguments: None, location: cols(21, 21) }))), - return_type: None, body: None, location: cols(11, 21) }], @@ -5548,53 +5309,9 @@ mod tests { } #[test] - fn test_trait_with_required_method_with_return_type() { + fn test_trait_with_required_method_with_arguments() { assert_eq!( - top(parse("trait A { fn foo -> A }")), - TopLevelExpression::DefineTrait(Box::new(DefineTrait { - public: false, - name: Constant { - source: None, - name: "A".to_string(), - location: cols(7, 7) - }, - type_parameters: None, - requirements: None, - body: TraitExpressions { - values: vec![DefineMethod { - public: false, - operator: false, - kind: MethodKind::Instance, - name: Identifier { - name: "foo".to_string(), - location: cols(14, 16) - }, - type_parameters: None, - arguments: None, - throw_type: None, - return_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "A".to_string(), - location: cols(21, 21) - }, - arguments: None, - location: cols(21, 21) - }))), - body: None, - location: cols(11, 21) - }], - location: cols(9, 23) - }, - location: cols(1, 23) - })) - ); - } - - #[test] - fn test_trait_with_required_method_with_arguments() { - assert_eq!( - top(parse("trait A { fn foo (a: A) }")), + top(parse("trait A { fn foo (a: A) }")), TopLevelExpression::DefineTrait(Box::new(DefineTrait { public: false, name: Constant { @@ -5633,7 +5350,6 @@ mod tests { }], location: cols(18, 23) }), - throw_type: None, return_type: None, body: None, location: cols(11, 23) @@ -5680,7 +5396,6 @@ mod tests { location: cols(18, 20) }), arguments: None, - throw_type: None, return_type: None, body: None, location: cols(11, 20) @@ -5716,7 +5431,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -5755,7 +5469,6 @@ mod tests { }, type_parameters: None, arguments: None, - throw_type: None, return_type: None, body: Some(Expressions { values: Vec::new(), @@ -7151,47 +6864,6 @@ mod tests { ); } - #[test] - fn test_async_expression() { - assert_eq!( - expr("async 10.foo"), - Expression::Async(Box::new(Async { - expression: Expression::Call(Box::new(Call { - receiver: Some(Expression::Int(Box::new(IntLiteral { - value: "10".to_string(), - location: cols(7, 8) - }))), - name: Identifier { - name: "foo".to_string(), - location: cols(10, 12) - }, - arguments: None, - location: cols(7, 12) - })), - location: cols(1, 12) - })) - ); - - assert_eq!( - expr("async { 10.foo }"), - Expression::Async(Box::new(Async { - expression: Expression::Call(Box::new(Call { - receiver: Some(Expression::Int(Box::new(IntLiteral { - value: "10".to_string(), - location: cols(9, 10) - }))), - name: Identifier { - name: "foo".to_string(), - location: cols(12, 14) - }, - arguments: None, - location: cols(9, 14) - })), - location: cols(1, 16) - })) - ); - } - #[test] fn test_call_with_trailing_blocks_without_parentheses() { assert_eq!( @@ -7207,7 +6879,6 @@ mod tests { Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![], @@ -7241,7 +6912,6 @@ mod tests { Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![], @@ -7275,7 +6945,6 @@ mod tests { Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![], @@ -7306,7 +6975,6 @@ mod tests { Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![], @@ -7343,7 +7011,6 @@ mod tests { Expression::Closure(Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: Vec::new(), @@ -7540,7 +7207,6 @@ mod tests { Expression::Closure(Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![Expression::Int(Box::new(IntLiteral { @@ -7558,7 +7224,6 @@ mod tests { Expression::Closure(Box::new(Closure { moving: true, arguments: None, - throw_type: None, return_type: None, body: Expressions { values: vec![Expression::Int(Box::new(IntLiteral { @@ -7586,7 +7251,6 @@ mod tests { }], location: cols(4, 6) }), - throw_type: None, return_type: None, body: Expressions { values: vec![Expression::Int(Box::new(IntLiteral { @@ -7622,7 +7286,6 @@ mod tests { }], location: cols(4, 9) }), - throw_type: None, return_type: None, body: Expressions { values: vec![Expression::Int(Box::new(IntLiteral { @@ -7635,38 +7298,11 @@ mod tests { })) ); - assert_eq!( - expr("fn !! T { 10 }"), - Expression::Closure(Box::new(Closure { - moving: false, - arguments: None, - throw_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "T".to_string(), - location: cols(7, 7) - }, - arguments: None, - location: cols(7, 7) - }))), - return_type: None, - body: Expressions { - values: vec![Expression::Int(Box::new(IntLiteral { - value: "10".to_string(), - location: cols(11, 12) - }))], - location: cols(9, 14) - }, - location: cols(1, 14) - })) - ); - assert_eq!( expr("fn -> T { 10 }"), Expression::Closure(Box::new(Closure { moving: false, arguments: None, - throw_type: None, return_type: Some(Type::Named(Box::new(TypeName { name: Constant { source: None, @@ -7691,7 +7327,6 @@ mod tests { #[test] fn test_invalid_closures() { assert_error_expr!("fn {", cols(4, 4)); - assert_error_expr!("fn !!", cols(5, 5)); assert_error_expr!("fn ->", cols(5, 5)); assert_error_expr!("fn =>", cols(4, 5)); } @@ -8084,70 +7719,6 @@ mod tests { ); } - #[test] - fn test_index_expression() { - assert_eq!( - expr("10[1]"), - Expression::Index(Box::new(Index { - receiver: Expression::Int(Box::new(IntLiteral { - value: "10".to_string(), - location: cols(1, 2) - })), - index: Expression::Int(Box::new(IntLiteral { - value: "1".to_string(), - location: cols(4, 4) - })), - location: cols(1, 5) - })) - ); - - assert_eq!( - expr("10[1] = 2"), - Expression::SetIndex(Box::new(SetIndex { - receiver: Expression::Int(Box::new(IntLiteral { - value: "10".to_string(), - location: cols(1, 2) - })), - index: Expression::Int(Box::new(IntLiteral { - value: "1".to_string(), - location: cols(4, 4) - })), - value: Expression::Int(Box::new(IntLiteral { - value: "2".to_string(), - location: cols(9, 9) - })), - location: cols(1, 9) - })) - ); - } - - #[test] - fn test_index_on_separate_line() { - let mut parser = parser("10\n[1]"); - - parser.next(); - - let start = parser.require().unwrap(); - let expr = parser.expression(start).unwrap(); - - assert_eq!( - expr, - Expression::Array(Box::new(Array { - values: vec![Expression::Int(Box::new(IntLiteral { - value: "1".to_string(), - location: location(2..=2, 2..=2) - }))], - location: location(2..=2, 1..=3) - })) - ); - } - - #[test] - fn test_invalid_indexing() { - assert_error_expr!("10[1", cols(4, 4)); - assert_error_expr!("10[1] =", cols(7, 7)); - } - #[test] fn test_throw_expression() { assert_eq!( @@ -8200,7 +7771,6 @@ mod tests { moving: false, arguments: None, return_type: None, - throw_type: None, body: Expressions { values: Vec::new(), location: cols(11, 12) @@ -8391,219 +7961,13 @@ mod tests { assert_eq!( expr("try a"), Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(5, 5) - })), + expression: Expression::Identifier(Box::new(Identifier { + name: "a".to_string(), location: cols(5, 5) - }, - else_block: None, + })), location: cols(1, 5) })) ); - - assert_eq!( - expr("try { a }"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(7, 7) - })), - location: cols(5, 9) - }, - else_block: None, - location: cols(1, 9) - })) - ); - - assert_eq!( - expr("try a else b"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(5, 5) - })), - location: cols(5, 5) - }, - else_block: Some(ElseBlock { - argument: None, - body: Expressions { - values: vec![Expression::Identifier(Box::new( - Identifier { - name: "b".to_string(), - location: cols(12, 12) - } - ))], - location: cols(12, 12) - }, - location: cols(7, 12) - }), - location: cols(1, 12) - })) - ); - - assert_eq!( - expr("try a else { b }"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(5, 5) - })), - location: cols(5, 5) - }, - else_block: Some(ElseBlock { - body: Expressions { - values: vec![Expression::Identifier(Box::new( - Identifier { - name: "b".to_string(), - location: cols(14, 14) - } - ))], - location: cols(12, 16) - }, - argument: None, - location: cols(7, 16) - }), - location: cols(1, 16) - })) - ); - - assert_eq!( - expr("try a else (arg) b"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(5, 5) - })), - location: cols(5, 5) - }, - else_block: Some(ElseBlock { - body: Expressions { - values: vec![Expression::Identifier(Box::new( - Identifier { - name: "b".to_string(), - location: cols(18, 18) - } - ))], - location: cols(18, 18) - }, - argument: Some(BlockArgument { - name: Identifier { - name: "arg".to_string(), - location: cols(13, 15) - }, - value_type: None, - location: cols(13, 15) - }), - location: cols(7, 18) - }), - location: cols(1, 18) - })) - ); - - assert_eq!( - expr("try a else (arg: T) b"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(5, 5) - })), - location: cols(5, 5) - }, - else_block: Some(ElseBlock { - body: Expressions { - values: vec![Expression::Identifier(Box::new( - Identifier { - name: "b".to_string(), - location: cols(21, 21) - } - ))], - location: cols(21, 21) - }, - argument: Some(BlockArgument { - name: Identifier { - name: "arg".to_string(), - location: cols(13, 15) - }, - value_type: Some(Type::Named(Box::new(TypeName { - name: Constant { - source: None, - name: "T".to_string(), - location: cols(18, 18) - }, - arguments: None, - location: cols(18, 18) - }))), - location: cols(13, 18) - }), - location: cols(7, 21) - }), - location: cols(1, 21) - })) - ); - - assert_eq!( - expr("try { a } else (arg) { b }"), - Expression::Try(Box::new(Try { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(7, 7) - })), - location: cols(5, 9) - }, - else_block: Some(ElseBlock { - body: Expressions { - values: vec![Expression::Identifier(Box::new( - Identifier { - name: "b".to_string(), - location: cols(24, 24) - } - ))], - location: cols(22, 26) - }, - argument: Some(BlockArgument { - name: Identifier { - name: "arg".to_string(), - location: cols(17, 19) - }, - value_type: None, - location: cols(17, 19) - }), - location: cols(11, 26) - }), - location: cols(1, 26) - })) - ); - } - - #[test] - fn test_try_panic_expression() { - assert_eq!( - expr("try! a"), - Expression::TryPanic(Box::new(TryPanic { - try_block: TryBlock { - value: Expression::Identifier(Box::new(Identifier { - name: "a".to_string(), - location: cols(6, 6) - })), - location: cols(6, 6) - }, - location: cols(1, 6) - })) - ); - } - - #[test] - fn test_invalid_try_expressions() { - assert_error_expr!("try a else", cols(10, 10)); - assert_error_expr!("try a else (arg)", cols(16, 16)); } #[test] diff --git a/ast/src/source_location.rs b/ast/src/source_location.rs index 38f07df23..16c4e3409 100644 --- a/ast/src/source_location.rs +++ b/ast/src/source_location.rs @@ -27,6 +27,10 @@ impl SourceLocation { ..=(*end.column_range.end()), } } + + pub fn line_column(&self) -> (usize, usize) { + (*self.line_range.start(), *self.column_range.start()) + } } impl fmt::Debug for SourceLocation { diff --git a/bytecode/Cargo.toml b/bytecode/Cargo.toml deleted file mode 100644 index 80c725a58..000000000 --- a/bytecode/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "bytecode" -version = "0.10.0" # VERSION -authors = ["Yorick Peterse "] -edition = "2021" -license = "MPL-2.0" - -[lib] -doctest = false diff --git a/bytecode/src/lib.rs b/bytecode/src/lib.rs deleted file mode 100644 index ee07ff27b..000000000 --- a/bytecode/src/lib.rs +++ /dev/null @@ -1,1312 +0,0 @@ -//! Bytecode types shared between the compiler and VM. -use std::fmt; - -/// A value is an owned reference. -pub const REF_OWNED: u16 = 0; - -/// A value is a regular reference/borrow. -pub const REF_REF: u16 = 1; - -/// A value is an atomic value. -pub const REF_ATOMIC: u16 = 2; - -/// A value is an immediate or permanent value, i.e. a value that doesn't need -/// to be dropped. -pub const REF_PERMANENT: u16 = 3; - -/// The bytes that every bytecode file must start with. -pub const SIGNATURE_BYTES: [u8; 4] = [105, 110, 107, 111]; // "inko" - -/// The current version of the bytecode format. -pub const VERSION: u8 = 1; - -/// The tag that marks the start of an integer constant. -pub const CONST_INTEGER: u8 = 0; - -/// The tag that marks the start of a float constant. -pub const CONST_FLOAT: u8 = 1; - -/// The tag that marks the start of a string constant. -pub const CONST_STRING: u8 = 2; - -/// The tag that marks the start of an array constant. -pub const CONST_ARRAY: u8 = 3; - -/// Enum containing all possible instruction types. -/// -/// When adding new opcodes, you must also add them to the `Opcode::from_byte` -/// method. -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -#[repr(u8)] -pub enum Opcode { - Allocate, - ArrayAllocate, - ArrayClear, - ArrayDrop, - ArrayGet, - ArrayLength, - ArrayPop, - ArrayPush, - ArrayRemove, - ArraySet, - Branch, - BranchResult, - BuiltinFunctionCall, - ByteArrayAllocate, - ByteArrayClear, - ByteArrayClone, - ByteArrayDrop, - ByteArrayEquals, - ByteArrayGet, - ByteArrayLength, - ByteArrayPop, - ByteArrayPush, - ByteArrayRemove, - ByteArraySet, - CallDynamic, - CallStatic, - CallVirtual, - CheckRefs, - Decrement, - DecrementAtomic, - Exit, - FloatAdd, - FloatCeil, - FloatClone, - FloatDiv, - FloatEq, - FloatFloor, - FloatGe, - FloatGt, - FloatIsInf, - FloatIsNan, - FloatLe, - FloatLt, - FloatMod, - FloatMul, - FloatRound, - FloatSub, - FloatToInt, - FloatToString, - Free, - FutureDrop, - FutureGet, - FutureGetFor, - FuturePoll, - GetConstant, - GetFalse, - GetField, - GetNil, - GetTrue, - GetUndefined, - Goto, - Increment, - IntAdd, - IntBitAnd, - IntBitNot, - IntBitOr, - IntBitXor, - IntClone, - IntDiv, - IntEq, - IntGe, - IntGt, - IntLe, - IntLt, - IntMod, - IntMul, - IntPow, - IntRotateLeft, - IntRotateRight, - IntShl, - IntShr, - IntSub, - IntToFloat, - IntToString, - IntUnsignedShr, - IntWrappingAdd, - IntWrappingMul, - IntWrappingSub, - IsUndefined, - JumpTable, - MoveRegister, - MoveResult, - ObjectEq, - Panic, - Pop, - ProcessAllocate, - ProcessFinishTask, - ProcessGetField, - ProcessSend, - ProcessSendAsync, - ProcessSetField, - ProcessSuspend, - ProcessWriteResult, - Push, - Reduce, - RefKind, - Return, - SetField, - StringByte, - StringConcat, - StringDrop, - StringEq, - StringSize, - Throw, -} - -impl Opcode { - pub fn from_byte(byte: u8) -> Result { - let opcode = match byte { - 0 => Opcode::Allocate, - 1 => Opcode::ArrayAllocate, - 2 => Opcode::ArrayClear, - 3 => Opcode::ArrayDrop, - 4 => Opcode::ArrayGet, - 5 => Opcode::ArrayLength, - 6 => Opcode::ArrayPop, - 7 => Opcode::ArrayPush, - 8 => Opcode::ArrayRemove, - 9 => Opcode::ArraySet, - 10 => Opcode::BuiltinFunctionCall, - 11 => Opcode::ByteArrayAllocate, - 12 => Opcode::ByteArrayClear, - 13 => Opcode::ByteArrayClone, - 14 => Opcode::ByteArrayDrop, - 15 => Opcode::ByteArrayEquals, - 16 => Opcode::ByteArrayGet, - 17 => Opcode::ByteArrayLength, - 18 => Opcode::ByteArrayPop, - 19 => Opcode::ByteArrayPush, - 20 => Opcode::ByteArrayRemove, - 21 => Opcode::ByteArraySet, - 22 => Opcode::CallDynamic, - 23 => Opcode::CallVirtual, - 24 => Opcode::CheckRefs, - 25 => Opcode::MoveRegister, - 26 => Opcode::Decrement, - 27 => Opcode::Exit, - 28 => Opcode::FloatAdd, - 29 => Opcode::FloatCeil, - 30 => Opcode::FloatClone, - 31 => Opcode::FloatDiv, - 32 => Opcode::FloatEq, - 33 => Opcode::FloatFloor, - 34 => Opcode::FloatGe, - 35 => Opcode::FloatGt, - 36 => Opcode::FloatIsInf, - 37 => Opcode::FloatIsNan, - 38 => Opcode::FloatLe, - 39 => Opcode::FloatLt, - 40 => Opcode::FloatMod, - 41 => Opcode::FloatMul, - 42 => Opcode::FloatRound, - 43 => Opcode::FloatSub, - 44 => Opcode::FloatToInt, - 45 => Opcode::FloatToString, - 46 => Opcode::FutureDrop, - 47 => Opcode::FutureGet, - 48 => Opcode::FutureGetFor, - 49 => Opcode::GetConstant, - 50 => Opcode::GetFalse, - 51 => Opcode::GetField, - 52 => Opcode::CallStatic, - 53 => Opcode::GetNil, - 54 => Opcode::GetTrue, - 55 => Opcode::GetUndefined, - 56 => Opcode::Goto, - 57 => Opcode::Branch, - 58 => Opcode::BranchResult, - 59 => Opcode::Increment, - 60 => Opcode::IntAdd, - 61 => Opcode::IntBitAnd, - 62 => Opcode::IntBitOr, - 63 => Opcode::IntBitXor, - 64 => Opcode::IntClone, - 65 => Opcode::IntDiv, - 66 => Opcode::IntEq, - 67 => Opcode::IntGe, - 68 => Opcode::IntGt, - 69 => Opcode::IntLe, - 70 => Opcode::IntLt, - 71 => Opcode::IntMod, - 72 => Opcode::IntMul, - 73 => Opcode::IntPow, - 74 => Opcode::IntShl, - 75 => Opcode::IntShr, - 76 => Opcode::IntSub, - 77 => Opcode::IntToFloat, - 78 => Opcode::IntToString, - 79 => Opcode::IsUndefined, - 80 => Opcode::RefKind, - 81 => Opcode::MoveResult, - 82 => Opcode::ObjectEq, - 83 => Opcode::Panic, - 84 => Opcode::ProcessAllocate, - 85 => Opcode::ProcessGetField, - 86 => Opcode::ProcessSendAsync, - 87 => Opcode::ProcessSend, - 88 => Opcode::ProcessSetField, - 89 => Opcode::ProcessSuspend, - 90 => Opcode::ProcessWriteResult, - 91 => Opcode::Free, - 92 => Opcode::Reduce, - 93 => Opcode::Return, - 94 => Opcode::SetField, - 95 => Opcode::StringByte, - 96 => Opcode::StringConcat, - 97 => Opcode::StringDrop, - 98 => Opcode::StringEq, - 99 => Opcode::StringSize, - 100 => Opcode::Throw, - 101 => Opcode::DecrementAtomic, - 102 => Opcode::ProcessFinishTask, - 103 => Opcode::JumpTable, - 104 => Opcode::Push, - 105 => Opcode::Pop, - 106 => Opcode::FuturePoll, - 107 => Opcode::IntBitNot, - 108 => Opcode::IntRotateLeft, - 109 => Opcode::IntRotateRight, - 110 => Opcode::IntWrappingAdd, - 111 => Opcode::IntWrappingSub, - 112 => Opcode::IntWrappingMul, - 113 => Opcode::IntUnsignedShr, - _ => return Err(format!("The opcode {} is invalid", byte)), - }; - - Ok(opcode) - } - - pub fn to_int(self) -> u8 { - // This must be kept in sync with `Opcode::from_byte()`. - match self { - Opcode::Allocate => 0, - Opcode::ArrayAllocate => 1, - Opcode::ArrayClear => 2, - Opcode::ArrayDrop => 3, - Opcode::ArrayGet => 4, - Opcode::ArrayLength => 5, - Opcode::ArrayPop => 6, - Opcode::ArrayPush => 7, - Opcode::ArrayRemove => 8, - Opcode::ArraySet => 9, - Opcode::BuiltinFunctionCall => 10, - Opcode::ByteArrayAllocate => 11, - Opcode::ByteArrayClear => 12, - Opcode::ByteArrayClone => 13, - Opcode::ByteArrayDrop => 14, - Opcode::ByteArrayEquals => 15, - Opcode::ByteArrayGet => 16, - Opcode::ByteArrayLength => 17, - Opcode::ByteArrayPop => 18, - Opcode::ByteArrayPush => 19, - Opcode::ByteArrayRemove => 20, - Opcode::ByteArraySet => 21, - Opcode::CallDynamic => 22, - Opcode::CallVirtual => 23, - Opcode::CheckRefs => 24, - Opcode::MoveRegister => 25, - Opcode::Decrement => 26, - Opcode::Exit => 27, - Opcode::FloatAdd => 28, - Opcode::FloatCeil => 29, - Opcode::FloatClone => 30, - Opcode::FloatDiv => 31, - Opcode::FloatEq => 32, - Opcode::FloatFloor => 33, - Opcode::FloatGe => 34, - Opcode::FloatGt => 35, - Opcode::FloatIsInf => 36, - Opcode::FloatIsNan => 37, - Opcode::FloatLe => 38, - Opcode::FloatLt => 39, - Opcode::FloatMod => 40, - Opcode::FloatMul => 41, - Opcode::FloatRound => 42, - Opcode::FloatSub => 43, - Opcode::FloatToInt => 44, - Opcode::FloatToString => 45, - Opcode::FutureDrop => 46, - Opcode::FutureGet => 47, - Opcode::FutureGetFor => 48, - Opcode::GetConstant => 49, - Opcode::GetFalse => 50, - Opcode::GetField => 51, - Opcode::CallStatic => 52, - Opcode::GetNil => 53, - Opcode::GetTrue => 54, - Opcode::GetUndefined => 55, - Opcode::Goto => 56, - Opcode::Branch => 57, - Opcode::BranchResult => 58, - Opcode::Increment => 59, - Opcode::IntAdd => 60, - Opcode::IntBitAnd => 61, - Opcode::IntBitOr => 62, - Opcode::IntBitXor => 63, - Opcode::IntClone => 64, - Opcode::IntDiv => 65, - Opcode::IntEq => 66, - Opcode::IntGe => 67, - Opcode::IntGt => 68, - Opcode::IntLe => 69, - Opcode::IntLt => 70, - Opcode::IntMod => 71, - Opcode::IntMul => 72, - Opcode::IntPow => 73, - Opcode::IntShl => 74, - Opcode::IntShr => 75, - Opcode::IntSub => 76, - Opcode::IntToFloat => 77, - Opcode::IntToString => 78, - Opcode::IsUndefined => 79, - Opcode::RefKind => 80, - Opcode::MoveResult => 81, - Opcode::ObjectEq => 82, - Opcode::Panic => 83, - Opcode::ProcessAllocate => 84, - Opcode::ProcessGetField => 85, - Opcode::ProcessSendAsync => 86, - Opcode::ProcessSend => 87, - Opcode::ProcessSetField => 88, - Opcode::ProcessSuspend => 89, - Opcode::ProcessWriteResult => 90, - Opcode::Free => 91, - Opcode::Reduce => 92, - Opcode::Return => 93, - Opcode::SetField => 94, - Opcode::StringByte => 95, - Opcode::StringConcat => 96, - Opcode::StringDrop => 97, - Opcode::StringEq => 98, - Opcode::StringSize => 99, - Opcode::Throw => 100, - Opcode::DecrementAtomic => 101, - Opcode::ProcessFinishTask => 102, - Opcode::JumpTable => 103, - Opcode::Push => 104, - Opcode::Pop => 105, - Opcode::FuturePoll => 106, - Opcode::IntBitNot => 107, - Opcode::IntRotateLeft => 108, - Opcode::IntRotateRight => 109, - Opcode::IntWrappingAdd => 110, - Opcode::IntWrappingSub => 111, - Opcode::IntWrappingMul => 112, - Opcode::IntUnsignedShr => 113, - } - } - - pub fn name(self) -> &'static str { - match self { - Opcode::Allocate => "allocate", - Opcode::ArrayAllocate => "array_allocate", - Opcode::ArrayClear => "array_clear", - Opcode::ArrayDrop => "array_drop", - Opcode::ArrayGet => "array_get", - Opcode::ArrayLength => "array_length", - Opcode::ArrayPop => "array_pop", - Opcode::ArrayPush => "array_push", - Opcode::ArrayRemove => "array_remove", - Opcode::ArraySet => "array_set", - Opcode::BuiltinFunctionCall => "builtin_function_call", - Opcode::ByteArrayAllocate => "byte_array_allocate", - Opcode::ByteArrayClear => "byte_array_clear", - Opcode::ByteArrayClone => "byte_array_clone", - Opcode::ByteArrayDrop => "byte_array_drop", - Opcode::ByteArrayEquals => "byte_array_equals", - Opcode::ByteArrayGet => "byte_array_get", - Opcode::ByteArrayLength => "byte_array_length", - Opcode::ByteArrayPop => "byte_array_pop", - Opcode::ByteArrayPush => "byte_array_push", - Opcode::ByteArrayRemove => "byte_array_remove", - Opcode::ByteArraySet => "byte_array_set", - Opcode::CallDynamic => "call_dynamic", - Opcode::CallVirtual => "call_virtual", - Opcode::CheckRefs => "check_refs", - Opcode::MoveRegister => "move_register", - Opcode::Decrement => "decrement", - Opcode::Exit => "exit", - Opcode::FloatAdd => "float_add", - Opcode::FloatCeil => "float_ceil", - Opcode::FloatClone => "float_clone", - Opcode::FloatDiv => "float_div", - Opcode::FloatEq => "float_eq", - Opcode::FloatFloor => "float_floor", - Opcode::FloatGe => "float_ge", - Opcode::FloatGt => "float_gt", - Opcode::FloatIsInf => "float_is_inf", - Opcode::FloatIsNan => "float_is_nan", - Opcode::FloatLe => "float_le", - Opcode::FloatLt => "float_lt", - Opcode::FloatMod => "float_mod", - Opcode::FloatMul => "float_mul", - Opcode::FloatRound => "float_round", - Opcode::FloatSub => "float_sub", - Opcode::FloatToInt => "float_to_int", - Opcode::FloatToString => "float_to_string", - Opcode::FutureDrop => "future_drop", - Opcode::FutureGet => "future_get", - Opcode::FutureGetFor => "future_get_for", - Opcode::GetConstant => "get_constant", - Opcode::GetFalse => "get_false", - Opcode::GetField => "get_field", - Opcode::CallStatic => "call_static", - Opcode::GetNil => "get_nil", - Opcode::GetTrue => "get_true", - Opcode::GetUndefined => "get_undefined", - Opcode::Goto => "goto", - Opcode::Branch => "branch", - Opcode::BranchResult => "branch_result", - Opcode::Increment => "increment", - Opcode::IntAdd => "int_add", - Opcode::IntBitAnd => "int_bit_and", - Opcode::IntBitOr => "int_bit_or", - Opcode::IntBitXor => "int_bit_xor", - Opcode::IntClone => "int_clone", - Opcode::IntDiv => "int_div", - Opcode::IntEq => "int_eq", - Opcode::IntGe => "int_ge", - Opcode::IntGt => "int_gt", - Opcode::IntLe => "int_le", - Opcode::IntLt => "int_lt", - Opcode::IntMod => "int_mod", - Opcode::IntMul => "int_mul", - Opcode::IntPow => "int_pow", - Opcode::IntShl => "int_shl", - Opcode::IntShr => "int_shr", - Opcode::IntSub => "int_sub", - Opcode::IntToFloat => "int_to_float", - Opcode::IntToString => "int_to_string", - Opcode::RefKind => "is_ref", - Opcode::MoveResult => "move_result", - Opcode::ObjectEq => "object_eq", - Opcode::Panic => "panic", - Opcode::ProcessAllocate => "process_allocate", - Opcode::ProcessGetField => "process_get_field", - Opcode::ProcessSendAsync => "process_send_async", - Opcode::ProcessSend => "process_send", - Opcode::ProcessSetField => "process_set_field", - Opcode::ProcessSuspend => "process_suspend", - Opcode::ProcessWriteResult => "process_write_result", - Opcode::Free => "free", - Opcode::Reduce => "reduce", - Opcode::Return => "return", - Opcode::SetField => "set_field", - Opcode::StringByte => "string_byte", - Opcode::StringConcat => "string_concat", - Opcode::StringDrop => "string_drop", - Opcode::StringEq => "string_eq", - Opcode::StringSize => "string_size", - Opcode::Throw => "throw", - Opcode::IsUndefined => "is_undefined", - Opcode::DecrementAtomic => "decrement_atomic", - Opcode::ProcessFinishTask => "process_finish_task", - Opcode::JumpTable => "jump_table", - Opcode::Push => "push", - Opcode::Pop => "pop", - Opcode::FuturePoll => "future_poll", - Opcode::IntBitNot => "int_bit_not", - Opcode::IntRotateLeft => "int_rotate_left", - Opcode::IntRotateRight => "int_rotate_right", - Opcode::IntWrappingAdd => "int_wrapping_add", - Opcode::IntWrappingSub => "int_wrapping_sub", - Opcode::IntWrappingMul => "int_wrapping_mul", - Opcode::IntUnsignedShr => "int_unsigned_shr", - } - } - - pub fn writes(self) -> bool { - // This list doesn't have to be exhaustive, as long as we cover the - // instructions directly exposed to the standard library. - !matches!( - self, - Opcode::ArrayClear - | Opcode::ArrayDrop - | Opcode::ArrayPush - | Opcode::ByteArrayClear - | Opcode::ByteArrayDrop - | Opcode::ByteArrayPush - | Opcode::Exit - | Opcode::Panic - | Opcode::ProcessSuspend - | Opcode::StringDrop - ) - } - - pub fn arity(self) -> usize { - match self { - Opcode::Allocate => 3, - Opcode::ArrayAllocate => 1, - Opcode::ArrayClear => 1, - Opcode::ArrayDrop => 1, - Opcode::ArrayGet => 3, - Opcode::ArrayLength => 2, - Opcode::ArrayPop => 2, - Opcode::ArrayPush => 2, - Opcode::ArrayRemove => 3, - Opcode::ArraySet => 4, - Opcode::Branch => 3, - Opcode::BranchResult => 2, - Opcode::BuiltinFunctionCall => 4, - Opcode::ByteArrayAllocate => 1, - Opcode::ByteArrayClear => 1, - Opcode::ByteArrayClone => 2, - Opcode::ByteArrayDrop => 1, - Opcode::ByteArrayEquals => 3, - Opcode::ByteArrayGet => 3, - Opcode::ByteArrayLength => 2, - Opcode::ByteArrayPop => 2, - Opcode::ByteArrayPush => 2, - Opcode::ByteArrayRemove => 3, - Opcode::ByteArraySet => 4, - Opcode::CallDynamic => 3, - Opcode::CallVirtual => 2, - Opcode::CheckRefs => 1, - Opcode::Decrement => 1, - Opcode::DecrementAtomic => 2, - Opcode::Exit => 1, - Opcode::FloatAdd => 3, - Opcode::FloatCeil => 2, - Opcode::FloatClone => 2, - Opcode::FloatDiv => 3, - Opcode::FloatEq => 3, - Opcode::FloatFloor => 2, - Opcode::FloatGe => 3, - Opcode::FloatGt => 3, - Opcode::FloatIsInf => 2, - Opcode::FloatIsNan => 2, - Opcode::FloatLe => 3, - Opcode::FloatLt => 3, - Opcode::FloatMod => 3, - Opcode::FloatMul => 3, - Opcode::FloatRound => 3, - Opcode::FloatSub => 3, - Opcode::FloatToInt => 2, - Opcode::FloatToString => 2, - Opcode::Free => 1, - Opcode::FutureDrop => 2, - Opcode::FutureGet => 1, - Opcode::FutureGetFor => 2, - Opcode::CallStatic => 3, - Opcode::GetConstant => 3, - Opcode::GetFalse => 1, - Opcode::GetField => 3, - Opcode::GetNil => 1, - Opcode::GetTrue => 1, - Opcode::GetUndefined => 1, - Opcode::Goto => 1, - Opcode::Increment => 2, - Opcode::IntAdd => 3, - Opcode::IntBitAnd => 3, - Opcode::IntBitOr => 3, - Opcode::IntBitXor => 3, - Opcode::IntClone => 2, - Opcode::IntDiv => 3, - Opcode::IntEq => 3, - Opcode::IntGe => 3, - Opcode::IntGt => 3, - Opcode::IntLe => 3, - Opcode::IntLt => 3, - Opcode::IntMod => 3, - Opcode::IntMul => 3, - Opcode::IntPow => 3, - Opcode::IntShl => 3, - Opcode::IntShr => 3, - Opcode::IntSub => 3, - Opcode::IntToFloat => 2, - Opcode::IntToString => 2, - Opcode::IsUndefined => 2, - Opcode::MoveRegister => 2, - Opcode::MoveResult => 1, - Opcode::ObjectEq => 3, - Opcode::Panic => 1, - Opcode::ProcessAllocate => 3, - Opcode::ProcessFinishTask => 1, - Opcode::ProcessGetField => 3, - Opcode::ProcessSend => 3, - Opcode::ProcessSendAsync => 3, - Opcode::ProcessSetField => 3, - Opcode::ProcessSuspend => 1, - Opcode::ProcessWriteResult => 3, - Opcode::Reduce => 1, - Opcode::RefKind => 2, - Opcode::Return => 1, - Opcode::SetField => 3, - Opcode::StringByte => 3, - Opcode::StringConcat => 1, - Opcode::StringDrop => 1, - Opcode::StringEq => 3, - Opcode::StringSize => 2, - Opcode::JumpTable => 2, - Opcode::Throw => 2, - Opcode::Push => 1, - Opcode::Pop => 1, - Opcode::FuturePoll => 2, - Opcode::IntBitNot => 2, - Opcode::IntRotateLeft => 3, - Opcode::IntRotateRight => 3, - Opcode::IntWrappingAdd => 3, - Opcode::IntWrappingSub => 3, - Opcode::IntWrappingMul => 3, - Opcode::IntUnsignedShr => 3, - } - } - - pub fn rewind_before_call(self) -> bool { - matches!( - self, - Opcode::BuiltinFunctionCall - | Opcode::FutureGet - | Opcode::FutureGetFor - ) - } -} - -/// A fixed-width VM instruction. -pub struct Instruction { - /// The instruction opcode/type. - pub opcode: Opcode, - - /// The arguments/operands of the instruction. - /// - /// This field is private so other code won't depend on this field having a - /// particular shape. - pub arguments: [u16; 5], -} - -impl fmt::Debug for Instruction { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut fmt = f.debug_tuple(self.opcode.name()); - - for index in 0..self.opcode.arity() { - fmt.field(&self.arguments[index]); - } - - fmt.finish() - } -} - -impl Instruction { - pub fn new(opcode: Opcode, arguments: [u16; 5]) -> Self { - Instruction { opcode, arguments } - } - - pub fn zero(opcode: Opcode) -> Self { - Self::new(opcode, [0, 0, 0, 0, 0]) - } - - pub fn one(opcode: Opcode, arg0: u16) -> Self { - Self::new(opcode, [arg0, 0, 0, 0, 0]) - } - - pub fn two(opcode: Opcode, arg0: u16, arg1: u16) -> Self { - Self::new(opcode, [arg0, arg1, 0, 0, 0]) - } - - pub fn three(opcode: Opcode, arg0: u16, arg1: u16, arg2: u16) -> Self { - Self::new(opcode, [arg0, arg1, arg2, 0, 0]) - } - - pub fn four( - opcode: Opcode, - arg0: u16, - arg1: u16, - arg2: u16, - arg3: u16, - ) -> Self { - Self::new(opcode, [arg0, arg1, arg2, arg3, 0]) - } - - /// Returns the value of the given instruction argument. - /// - /// This method is always inlined to ensure bounds checking is optimised - /// away when using literal index values. - #[inline(always)] - pub fn arg(&self, index: usize) -> u16 { - self.arguments[index] - } - - #[inline(always)] - pub fn u32_arg(&self, one: usize, two: usize) -> u32 { - let arg1 = u16::to_le_bytes(self.arg(one)); - let arg2 = u16::to_le_bytes(self.arg(two)); - - u32::from_le_bytes([arg1[0], arg1[1], arg2[0], arg2[1]]) - } - - #[inline(always)] - pub fn u64_arg( - &self, - one: usize, - two: usize, - three: usize, - four: usize, - ) -> u64 { - let arg1 = u16::to_le_bytes(self.arg(one)); - let arg2 = u16::to_le_bytes(self.arg(two)); - let arg3 = u16::to_le_bytes(self.arg(three)); - let arg4 = u16::to_le_bytes(self.arg(four)); - - u64::from_le_bytes([ - arg1[0], arg1[1], arg2[0], arg2[1], arg3[0], arg3[1], arg4[0], - arg4[1], - ]) - } -} - -#[repr(u16)] -#[derive(Copy, Clone)] -pub enum BuiltinFunction { - ByteArrayDrainToString, - ByteArrayToString, - ChildProcessDrop, - ChildProcessSpawn, - ChildProcessStderrClose, - ChildProcessStderrRead, - ChildProcessStdinClose, - ChildProcessStdinFlush, - ChildProcessStdinWriteBytes, - ChildProcessStdinWriteString, - ChildProcessStdoutClose, - ChildProcessStdoutRead, - ChildProcessTryWait, - ChildProcessWait, - DirectoryCreate, - DirectoryCreateRecursive, - DirectoryList, - DirectoryRemove, - DirectoryRemoveRecursive, - EnvArguments, - EnvExecutable, - EnvGet, - EnvGetWorkingDirectory, - EnvHomeDirectory, - EnvPlatform, - EnvSetWorkingDirectory, - EnvTempDirectory, - EnvVariables, - FFIFunctionAttach, - FFIFunctionCall, - FFIFunctionDrop, - FFILibraryDrop, - FFILibraryOpen, - FFIPointerAddress, - FFIPointerAttach, - FFIPointerFromAddress, - FFIPointerRead, - FFIPointerWrite, - FFITypeAlignment, - FFITypeSize, - FileCopy, - FileDrop, - FileFlush, - FileOpenAppendOnly, - FileOpenReadAppend, - FileOpenReadOnly, - FileOpenReadWrite, - FileOpenWriteOnly, - FileRead, - FileRemove, - FileSeek, - FileSize, - FileWriteBytes, - FileWriteString, - HasherDrop, - HasherNew, - HasherToHash, - HasherWriteInt, - PathAccessedAt, - PathCreatedAt, - PathExists, - PathIsDirectory, - PathIsFile, - PathModifiedAt, - ProcessStacktraceDrop, - ProcessCallFrameLine, - ProcessCallFrameName, - ProcessCallFramePath, - ProcessStacktrace, - RandomBytes, - RandomFloat, - RandomFloatRange, - RandomInt, - RandomIntRange, - SocketAcceptIp, - SocketAcceptUnix, - SocketAddressPairAddress, - SocketAddressPairDrop, - SocketAddressPairPort, - SocketAllocateIpv4, - SocketAllocateIpv6, - SocketAllocateUnix, - SocketBind, - SocketConnect, - SocketDrop, - SocketGetBroadcast, - SocketGetKeepalive, - SocketGetLinger, - SocketGetNodelay, - SocketGetOnlyV6, - SocketGetRecvSize, - SocketGetReuseAddress, - SocketGetReusePort, - SocketGetSendSize, - SocketGetTtl, - SocketListen, - SocketLocalAddress, - SocketPeerAddress, - SocketRead, - SocketReceiveFrom, - SocketSendBytesTo, - SocketSendStringTo, - SocketSetBroadcast, - SocketSetKeepalive, - SocketSetLinger, - SocketSetNodelay, - SocketSetOnlyV6, - SocketSetRecvSize, - SocketSetReuseAddress, - SocketSetReusePort, - SocketSetSendSize, - SocketSetTtl, - SocketShutdownRead, - SocketShutdownReadWrite, - SocketShutdownWrite, - SocketTryClone, - SocketWriteBytes, - SocketWriteString, - StderrFlush, - StderrWriteBytes, - StderrWriteString, - StdinRead, - StdoutFlush, - StdoutWriteBytes, - StdoutWriteString, - StringToByteArray, - StringToFloat, - StringToInt, - StringToLower, - StringToUpper, - TimeMonotonic, - TimeSystem, - TimeSystemOffset, - CpuCores, - StringCharacters, - StringCharactersNext, - StringCharactersDrop, - StringConcatArray, - ArrayReserve, - ArrayCapacity, - ProcessStacktraceLength, - FloatToBits, - FloatFromBits, - RandomNew, - RandomFromInt, - RandomDrop, - StringSliceBytes, - ByteArraySlice, - ByteArrayAppend, - ByteArrayCopyFrom, - ByteArrayResize, -} - -impl BuiltinFunction { - pub fn to_int(self) -> u16 { - match self { - BuiltinFunction::ByteArrayDrainToString => 0, - BuiltinFunction::ByteArrayToString => 1, - BuiltinFunction::ChildProcessDrop => 2, - BuiltinFunction::ChildProcessSpawn => 3, - BuiltinFunction::ChildProcessStderrClose => 4, - BuiltinFunction::ChildProcessStderrRead => 5, - BuiltinFunction::ChildProcessStdinClose => 6, - BuiltinFunction::ChildProcessStdinFlush => 7, - BuiltinFunction::ChildProcessStdinWriteBytes => 8, - BuiltinFunction::ChildProcessStdinWriteString => 9, - BuiltinFunction::ChildProcessStdoutClose => 10, - BuiltinFunction::ChildProcessStdoutRead => 11, - BuiltinFunction::ChildProcessTryWait => 12, - BuiltinFunction::ChildProcessWait => 13, - BuiltinFunction::EnvArguments => 14, - BuiltinFunction::EnvExecutable => 15, - BuiltinFunction::EnvGet => 16, - BuiltinFunction::EnvGetWorkingDirectory => 17, - BuiltinFunction::EnvHomeDirectory => 18, - BuiltinFunction::EnvPlatform => 19, - BuiltinFunction::EnvSetWorkingDirectory => 20, - BuiltinFunction::EnvTempDirectory => 21, - BuiltinFunction::EnvVariables => 22, - BuiltinFunction::FFIFunctionAttach => 23, - BuiltinFunction::FFIFunctionCall => 24, - BuiltinFunction::FFIFunctionDrop => 25, - BuiltinFunction::FFILibraryDrop => 26, - BuiltinFunction::FFILibraryOpen => 27, - BuiltinFunction::FFIPointerAddress => 28, - BuiltinFunction::FFIPointerAttach => 29, - BuiltinFunction::FFIPointerFromAddress => 30, - BuiltinFunction::FFIPointerRead => 31, - BuiltinFunction::FFIPointerWrite => 32, - BuiltinFunction::FFITypeAlignment => 33, - BuiltinFunction::FFITypeSize => 34, - BuiltinFunction::DirectoryCreate => 35, - BuiltinFunction::DirectoryCreateRecursive => 36, - BuiltinFunction::DirectoryList => 37, - BuiltinFunction::DirectoryRemove => 38, - BuiltinFunction::DirectoryRemoveRecursive => 39, - BuiltinFunction::FileCopy => 40, - BuiltinFunction::FileDrop => 41, - BuiltinFunction::FileFlush => 42, - BuiltinFunction::FileOpenAppendOnly => 43, - BuiltinFunction::FileOpenReadAppend => 44, - BuiltinFunction::FileOpenReadOnly => 45, - BuiltinFunction::FileOpenReadWrite => 46, - BuiltinFunction::FileOpenWriteOnly => 47, - BuiltinFunction::FileRead => 48, - BuiltinFunction::FileRemove => 49, - BuiltinFunction::FileSeek => 50, - BuiltinFunction::FileSize => 51, - BuiltinFunction::FileWriteBytes => 52, - BuiltinFunction::FileWriteString => 53, - BuiltinFunction::PathAccessedAt => 54, - BuiltinFunction::PathCreatedAt => 55, - BuiltinFunction::PathExists => 56, - BuiltinFunction::PathIsDirectory => 57, - BuiltinFunction::PathIsFile => 58, - BuiltinFunction::PathModifiedAt => 59, - BuiltinFunction::HasherDrop => 60, - BuiltinFunction::HasherNew => 61, - BuiltinFunction::HasherToHash => 62, - BuiltinFunction::HasherWriteInt => 63, - BuiltinFunction::ProcessStacktraceDrop => 64, - BuiltinFunction::ProcessCallFrameLine => 65, - BuiltinFunction::ProcessCallFrameName => 66, - BuiltinFunction::ProcessCallFramePath => 67, - BuiltinFunction::ProcessStacktrace => 68, - BuiltinFunction::RandomBytes => 69, - BuiltinFunction::RandomFloat => 70, - BuiltinFunction::RandomFloatRange => 71, - BuiltinFunction::RandomInt => 72, - BuiltinFunction::RandomIntRange => 73, - BuiltinFunction::SocketAcceptIp => 74, - BuiltinFunction::SocketAcceptUnix => 75, - BuiltinFunction::SocketAddressPairAddress => 76, - BuiltinFunction::SocketAddressPairDrop => 77, - BuiltinFunction::SocketAddressPairPort => 78, - BuiltinFunction::SocketAllocateIpv4 => 79, - BuiltinFunction::SocketAllocateIpv6 => 80, - BuiltinFunction::SocketAllocateUnix => 81, - BuiltinFunction::SocketBind => 82, - BuiltinFunction::SocketConnect => 83, - BuiltinFunction::SocketDrop => 84, - BuiltinFunction::SocketGetBroadcast => 85, - BuiltinFunction::SocketGetKeepalive => 86, - BuiltinFunction::SocketGetLinger => 87, - BuiltinFunction::SocketGetNodelay => 88, - BuiltinFunction::SocketGetOnlyV6 => 89, - BuiltinFunction::SocketGetRecvSize => 90, - BuiltinFunction::SocketGetReuseAddress => 91, - BuiltinFunction::SocketGetReusePort => 92, - BuiltinFunction::SocketGetSendSize => 93, - BuiltinFunction::SocketGetTtl => 94, - BuiltinFunction::SocketListen => 95, - BuiltinFunction::SocketLocalAddress => 96, - BuiltinFunction::SocketPeerAddress => 97, - BuiltinFunction::SocketRead => 98, - BuiltinFunction::SocketReceiveFrom => 99, - BuiltinFunction::SocketSendBytesTo => 100, - BuiltinFunction::SocketSendStringTo => 101, - BuiltinFunction::SocketSetBroadcast => 102, - BuiltinFunction::SocketSetKeepalive => 103, - BuiltinFunction::SocketSetLinger => 104, - BuiltinFunction::SocketSetNodelay => 105, - BuiltinFunction::SocketSetOnlyV6 => 106, - BuiltinFunction::SocketSetRecvSize => 107, - BuiltinFunction::SocketSetReuseAddress => 108, - BuiltinFunction::SocketSetReusePort => 109, - BuiltinFunction::SocketSetSendSize => 110, - BuiltinFunction::SocketSetTtl => 111, - BuiltinFunction::SocketShutdownRead => 112, - BuiltinFunction::SocketShutdownReadWrite => 113, - BuiltinFunction::SocketShutdownWrite => 114, - BuiltinFunction::SocketTryClone => 115, - BuiltinFunction::SocketWriteBytes => 116, - BuiltinFunction::SocketWriteString => 117, - BuiltinFunction::StderrFlush => 118, - BuiltinFunction::StderrWriteBytes => 119, - BuiltinFunction::StderrWriteString => 120, - BuiltinFunction::StdinRead => 121, - BuiltinFunction::StdoutFlush => 122, - BuiltinFunction::StdoutWriteBytes => 123, - BuiltinFunction::StdoutWriteString => 124, - BuiltinFunction::StringToByteArray => 125, - BuiltinFunction::StringToFloat => 126, - BuiltinFunction::StringToInt => 127, - BuiltinFunction::StringToLower => 128, - BuiltinFunction::StringToUpper => 129, - BuiltinFunction::TimeMonotonic => 130, - BuiltinFunction::TimeSystem => 131, - BuiltinFunction::TimeSystemOffset => 132, - BuiltinFunction::CpuCores => 133, - BuiltinFunction::StringCharacters => 134, - BuiltinFunction::StringCharactersNext => 135, - BuiltinFunction::StringCharactersDrop => 136, - BuiltinFunction::StringConcatArray => 137, - BuiltinFunction::ArrayReserve => 138, - BuiltinFunction::ArrayCapacity => 139, - BuiltinFunction::ProcessStacktraceLength => 140, - BuiltinFunction::FloatToBits => 141, - BuiltinFunction::FloatFromBits => 142, - BuiltinFunction::RandomNew => 143, - BuiltinFunction::RandomFromInt => 144, - BuiltinFunction::RandomDrop => 145, - BuiltinFunction::StringSliceBytes => 146, - BuiltinFunction::ByteArraySlice => 147, - BuiltinFunction::ByteArrayAppend => 148, - BuiltinFunction::ByteArrayCopyFrom => 149, - BuiltinFunction::ByteArrayResize => 150, - } - } - - pub fn name(self) -> &'static str { - match self { - BuiltinFunction::ChildProcessDrop => "child_process_drop", - BuiltinFunction::ChildProcessSpawn => "child_process_spawn", - BuiltinFunction::ChildProcessStderrClose => { - "child_process_stderr_close" - } - BuiltinFunction::ChildProcessStderrRead => { - "child_process_stderr_read" - } - BuiltinFunction::ChildProcessStdinClose => { - "child_process_stdin_close" - } - BuiltinFunction::ChildProcessStdinFlush => { - "child_process_stdin_flush" - } - BuiltinFunction::ChildProcessStdinWriteBytes => { - "child_process_stdin_write_bytes" - } - BuiltinFunction::ChildProcessStdinWriteString => { - "child_process_stdin_write_string" - } - BuiltinFunction::ChildProcessStdoutClose => { - "child_process_stdout_close" - } - BuiltinFunction::ChildProcessStdoutRead => { - "child_process_stdout_read" - } - BuiltinFunction::ChildProcessTryWait => "child_process_try_wait", - BuiltinFunction::ChildProcessWait => "child_process_wait", - BuiltinFunction::EnvArguments => "env_arguments", - BuiltinFunction::EnvExecutable => "env_executable", - BuiltinFunction::EnvGet => "env_get", - BuiltinFunction::EnvGetWorkingDirectory => { - "env_get_working_directory" - } - BuiltinFunction::EnvHomeDirectory => "env_home_directory", - BuiltinFunction::EnvPlatform => "env_platform", - BuiltinFunction::EnvSetWorkingDirectory => { - "env_set_working_directory" - } - BuiltinFunction::EnvTempDirectory => "env_temp_directory", - BuiltinFunction::EnvVariables => "env_variables", - BuiltinFunction::FFIFunctionAttach => "ffi_function_attach", - BuiltinFunction::FFIFunctionCall => "ffi_function_call", - BuiltinFunction::FFIFunctionDrop => "ffi_function_drop", - BuiltinFunction::FFILibraryDrop => "ffi_library_drop", - BuiltinFunction::FFILibraryOpen => "ffi_library_open", - BuiltinFunction::FFIPointerAddress => "ffi_pointer_address", - BuiltinFunction::FFIPointerAttach => "ffi_pointer_attach", - BuiltinFunction::FFIPointerFromAddress => { - "ffi_pointer_from_address" - } - BuiltinFunction::FFIPointerRead => "ffi_pointer_read", - BuiltinFunction::FFIPointerWrite => "ffi_pointer_write", - BuiltinFunction::FFITypeAlignment => "ffi_type_alignment", - BuiltinFunction::FFITypeSize => "ffi_type_size", - BuiltinFunction::DirectoryCreate => "directory_create", - BuiltinFunction::DirectoryCreateRecursive => { - "directory_create_recursive" - } - BuiltinFunction::DirectoryList => "directory_list", - BuiltinFunction::DirectoryRemove => "directory_remove", - BuiltinFunction::DirectoryRemoveRecursive => { - "directory_remove_recursive" - } - BuiltinFunction::FileCopy => "file_copy", - BuiltinFunction::FileDrop => "file_drop", - BuiltinFunction::FileFlush => "file_flush", - BuiltinFunction::FileOpenAppendOnly => "file_open_append_only", - BuiltinFunction::FileOpenReadAppend => "file_open_read_append", - BuiltinFunction::FileOpenReadOnly => "file_open_read_only", - BuiltinFunction::FileOpenReadWrite => "file_open_read_write", - BuiltinFunction::FileOpenWriteOnly => "file_open_write_only", - BuiltinFunction::FileRead => "file_read", - BuiltinFunction::FileRemove => "file_remove", - BuiltinFunction::FileSeek => "file_seek", - BuiltinFunction::FileSize => "file_size", - BuiltinFunction::FileWriteBytes => "file_write_bytes", - BuiltinFunction::FileWriteString => "file_write_string", - BuiltinFunction::PathAccessedAt => "path_accessed_at", - BuiltinFunction::PathCreatedAt => "path_created_at", - BuiltinFunction::PathExists => "path_exists", - BuiltinFunction::PathIsDirectory => "path_is_directory", - BuiltinFunction::PathIsFile => "path_is_file", - BuiltinFunction::PathModifiedAt => "path_modified_at", - BuiltinFunction::HasherDrop => "hasher_drop", - BuiltinFunction::HasherNew => "hasher_new", - BuiltinFunction::HasherToHash => "hasher_to_hash", - BuiltinFunction::HasherWriteInt => "hasher_write_int", - BuiltinFunction::ProcessStacktraceDrop => "process_stacktrace_drop", - BuiltinFunction::ProcessCallFrameLine => "process_call_frame_line", - BuiltinFunction::ProcessCallFrameName => "process_call_frame_name", - BuiltinFunction::ProcessCallFramePath => "process_call_frame_path", - BuiltinFunction::ProcessStacktrace => "process_stacktrace", - BuiltinFunction::RandomBytes => "random_bytes", - BuiltinFunction::RandomFloat => "random_float", - BuiltinFunction::RandomFloatRange => "random_float_range", - BuiltinFunction::RandomIntRange => "random_int_range", - BuiltinFunction::RandomInt => "random_int", - BuiltinFunction::SocketAcceptIp => "socket_accept_ip", - BuiltinFunction::SocketAcceptUnix => "socket_accept_unix", - BuiltinFunction::SocketAddressPairAddress => { - "socket_address_pair_address" - } - BuiltinFunction::SocketAddressPairDrop => { - "socket_address_pair_drop" - } - BuiltinFunction::SocketAddressPairPort => { - "socket_address_pair_port" - } - BuiltinFunction::SocketAllocateIpv4 => "socket_allocate_ipv4", - BuiltinFunction::SocketAllocateIpv6 => "socket_allocate_ipv6", - BuiltinFunction::SocketAllocateUnix => "socket_allocate_unix", - BuiltinFunction::SocketBind => "socket_bind", - BuiltinFunction::SocketConnect => "socket_connect", - BuiltinFunction::SocketDrop => "socket_drop", - BuiltinFunction::SocketGetBroadcast => "socket_get_broadcast", - BuiltinFunction::SocketGetKeepalive => "socket_get_keepalive", - BuiltinFunction::SocketGetLinger => "socket_get_linger", - BuiltinFunction::SocketGetNodelay => "socket_get_nodelay", - BuiltinFunction::SocketGetOnlyV6 => "socket_get_only_v6", - BuiltinFunction::SocketGetRecvSize => "socket_get_recv_size", - BuiltinFunction::SocketGetReuseAddress => { - "socket_get_reuse_address" - } - BuiltinFunction::SocketGetReusePort => "socket_get_reuse_port", - BuiltinFunction::SocketGetSendSize => "socket_get_send_size", - BuiltinFunction::SocketGetTtl => "socket_get_ttl", - BuiltinFunction::SocketListen => "socket_listen", - BuiltinFunction::SocketLocalAddress => "socket_local_address", - BuiltinFunction::SocketPeerAddress => "socket_peer_address", - BuiltinFunction::SocketRead => "socket_read", - BuiltinFunction::SocketReceiveFrom => "socket_receive_from", - BuiltinFunction::SocketSendBytesTo => "socket_send_bytes_to", - BuiltinFunction::SocketSendStringTo => "socket_send_string_to", - BuiltinFunction::SocketSetBroadcast => "socket_set_broadcast", - BuiltinFunction::SocketSetKeepalive => "socket_set_keepalive", - BuiltinFunction::SocketSetLinger => "socket_set_linger", - BuiltinFunction::SocketSetNodelay => "socket_set_nodelay", - BuiltinFunction::SocketSetOnlyV6 => "socket_set_only_v6", - BuiltinFunction::SocketSetRecvSize => "socket_set_recv_size", - BuiltinFunction::SocketSetReuseAddress => { - "socket_set_reuse_address" - } - BuiltinFunction::SocketSetReusePort => "socket_set_reuse_port", - BuiltinFunction::SocketSetSendSize => "socket_set_send_size", - BuiltinFunction::SocketSetTtl => "socket_set_ttl", - BuiltinFunction::SocketShutdownRead => "socket_shutdown_read", - BuiltinFunction::SocketShutdownReadWrite => { - "socket_shutdown_read_write" - } - BuiltinFunction::SocketShutdownWrite => "socket_shutdown_write", - BuiltinFunction::SocketTryClone => "socket_try_clone", - BuiltinFunction::SocketWriteBytes => "socket_write_bytes", - BuiltinFunction::SocketWriteString => "socket_write_string", - BuiltinFunction::StderrFlush => "stderr_flush", - BuiltinFunction::StderrWriteBytes => "stderr_write_bytes", - BuiltinFunction::StderrWriteString => "stderr_write_string", - BuiltinFunction::StdinRead => "stdin_read", - BuiltinFunction::StdoutFlush => "stdout_flush", - BuiltinFunction::StdoutWriteBytes => "stdout_write_bytes", - BuiltinFunction::StdoutWriteString => "stdout_write_string", - BuiltinFunction::TimeMonotonic => "time_monotonic", - BuiltinFunction::TimeSystem => "time_system", - BuiltinFunction::TimeSystemOffset => "time_system_offset", - BuiltinFunction::StringToLower => "string_to_lower", - BuiltinFunction::StringToUpper => "string_to_upper", - BuiltinFunction::StringToByteArray => "string_to_byte_array", - BuiltinFunction::StringToFloat => "string_to_float", - BuiltinFunction::StringToInt => "string_to_int", - BuiltinFunction::ByteArrayDrainToString => { - "byte_array_drain_to_string" - } - BuiltinFunction::ByteArrayToString => "byte_array_to_string", - BuiltinFunction::CpuCores => "cpu_cores", - BuiltinFunction::StringCharacters => "string_characters", - BuiltinFunction::StringCharactersNext => "string_characters_next", - BuiltinFunction::StringCharactersDrop => "string_characters_drop", - BuiltinFunction::StringConcatArray => "string_concat_array", - BuiltinFunction::ArrayReserve => "array_reserve", - BuiltinFunction::ArrayCapacity => "array_capacity", - BuiltinFunction::ProcessStacktraceLength => { - "process_stacktrace_length" - } - BuiltinFunction::FloatToBits => "float_to_bits", - BuiltinFunction::FloatFromBits => "float_from_bits", - BuiltinFunction::RandomNew => "random_new", - BuiltinFunction::RandomFromInt => "random_from_int", - BuiltinFunction::RandomDrop => "random_drop", - BuiltinFunction::StringSliceBytes => "string_slice_bytes", - BuiltinFunction::ByteArraySlice => "byte_array_slice", - BuiltinFunction::ByteArrayAppend => "byte_array_append", - BuiltinFunction::ByteArrayCopyFrom => "byte_array_copy_from", - BuiltinFunction::ByteArrayResize => "byte_array_resize", - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::mem::size_of; - - #[test] - fn test_opcode_from_byte() { - assert_eq!(Opcode::from_byte(93), Ok(Opcode::Return)); - assert_eq!( - Opcode::from_byte(255), - Err("The opcode 255 is invalid".to_string()) - ); - } - - #[test] - fn test_arg() { - let ins = Instruction::new(Opcode::GetConstant, [1, 2, 0, 0, 0]); - - assert_eq!(ins.arg(0), 1); - } - - #[test] - fn test_u32_arg() { - let ins = Instruction::new(Opcode::Return, [0, 14, 1, 1, 1]); - - assert_eq!(ins.u32_arg(1, 2), 65_550); - } - - #[test] - fn test_u64_arg() { - let ins0 = Instruction::new(Opcode::Return, [0, 14, 1, 0, 0]); - let ins1 = Instruction::new(Opcode::Return, [0, 14, 1, 1, 1]); - - assert_eq!(ins0.u64_arg(1, 2, 3, 4), 65_550); - assert_eq!(ins1.u64_arg(1, 2, 3, 4), 281_479_271_743_502); - } - - #[test] - fn test_type_size() { - assert_eq!(size_of::(), 12); - } -} diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index da997514c..5a5e6d399 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -4,6 +4,7 @@ version = "0.10.0" # VERSION authors = ["Yorick Peterse "] edition = "2021" license = "MPL-2.0" +build = "build.rs" [lib] doctest = false @@ -12,8 +13,9 @@ doctest = false unicode-segmentation = "^1.8" getopts = "^0.2" ast = { path = "../ast" } -bytecode = { path = "../bytecode" } types = { path = "../types" } +fnv = "^1.0" +inkwell = { version = "^0.1", features = ["llvm15-0"] } [dev-dependencies] similar-asserts = "^1.1" diff --git a/compiler/build.rs b/compiler/build.rs new file mode 100644 index 000000000..7be520255 --- /dev/null +++ b/compiler/build.rs @@ -0,0 +1,49 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + // To make development easier we default to using repository-local paths. + let rt = env::var("INKO_RT") + .map(PathBuf::from) + .unwrap_or_else(|_| runtime_directory()); + + let std = env::var("INKO_STD") + .map(PathBuf::from) + .unwrap_or_else(|_| std_directory()); + + println!("cargo:rerun-if-env-changed=INKO_STD"); + println!("cargo:rerun-if-env-changed=INKO_RT"); + println!( + "cargo:rustc-env=INKO_STD={}", + std.canonicalize().unwrap_or(std).display(), + ); + println!( + "cargo:rustc-env=INKO_RT={}", + rt.canonicalize().unwrap_or(rt).display() + ); +} + +fn std_directory() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("std") + .join("src") +} + +fn runtime_directory() -> PathBuf { + let target = env::var("TARGET").unwrap(); + let profile = env::var("PROFILE").unwrap(); + let base = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("target"); + let without_target = base.join(&profile); + let with_target = base.join(target).join(profile); + + if with_target.is_dir() { + with_target + } else { + without_target + } +} diff --git a/compiler/src/codegen.rs b/compiler/src/codegen.rs deleted file mode 100644 index c83b9bae5..000000000 --- a/compiler/src/codegen.rs +++ /dev/null @@ -1,899 +0,0 @@ -//! Converting MIR to bytecode. -use crate::mir::{self, CloneKind, Constant, LocationId, Method, Mir}; -use bytecode::{Instruction, Opcode}; -use bytecode::{ - CONST_ARRAY, CONST_FLOAT, CONST_INTEGER, CONST_STRING, SIGNATURE_BYTES, - VERSION, -}; -use std::collections::{HashMap, HashSet, VecDeque}; -use types::{ClassId, Database, MethodId, FIRST_USER_CLASS_ID}; - -const REGISTERS_LIMIT: usize = u16::MAX as usize; -const BLOCKS_LIMIT: usize = u16::MAX as usize; -const JUMP_TABLES_LIMIT: usize = u16::MAX as usize; - -/// Method table sizes are multiplied by this value in an attempt to reduce the -/// amount of collisions when performing dynamic dispatch. -/// -/// While this increases the amount of memory needed per method table, it's not -/// really significant: each slot only takes up one word of memory. On a 64-bits -/// system this means you can fit a total of 131 072 slots in 1 MiB. In -/// addition, this cost is a one-time and constant cost, whereas collisions -/// introduce a cost that you may have to pay every time you perform dynamic -/// dispatch. -const METHOD_TABLE_FACTOR: usize = 4; - -/// The class-local index of the dropper method. -const DROPPER_INDEX: u16 = 0; - -/// The class-local index of the "call" method for closures. -const CALL_CLOSURE_INDEX: u16 = 1; - -fn nearest_power_of_two(mut value: usize) -> usize { - if value == 0 { - return 0; - } - - value -= 1; - value |= value >> 1; - value |= value >> 2; - value |= value >> 4; - value |= value >> 8; - value |= value >> 16; - value |= value >> 32; - value += 1; - - value -} - -fn split_u32(value: u32) -> (u16, u16) { - let bytes = u32::to_le_bytes(value); - - ( - u16::from_le_bytes([bytes[0], bytes[1]]), - u16::from_le_bytes([bytes[2], bytes[3]]), - ) -} - -fn push_values(buffer: &mut Vec, values: &[mir::RegisterId]) { - for val in values { - buffer.push(Instruction::one(Opcode::Push, val.0 as u16)); - } -} - -fn number_of_methods(info: &HashMap, id: ClassId) -> u16 { - info.get(&id).unwrap().method_slots -} - -pub(crate) struct Bytecode { - pub(crate) bytes: Vec, -} - -pub(crate) struct Buffer { - pub(crate) bytes: Vec, -} - -impl Buffer { - fn new() -> Self { - Self { bytes: Vec::new() } - } - - fn len(&self) -> usize { - self.bytes.len() - } - - fn append(&mut self, other: &mut Self) { - self.bytes.append(&mut other.bytes); - } - - fn write_u8(&mut self, value: u8) { - self.bytes.push(value); - } - - fn write_bool(&mut self, value: bool) { - self.bytes.push(value as u8); - } - - fn write_u16(&mut self, value: u16) { - self.bytes.extend_from_slice(&u16::to_le_bytes(value)); - } - - fn write_u32(&mut self, value: u32) { - self.bytes.extend_from_slice(&u32::to_le_bytes(value)); - } - - fn write_u64(&mut self, value: u64) { - self.bytes.extend_from_slice(&u64::to_le_bytes(value)); - } - - fn write_i64(&mut self, value: i64) { - self.bytes.extend_from_slice(&i64::to_le_bytes(value)); - } - - fn write_f64(&mut self, value: f64) { - self.bytes.extend_from_slice(&u64::to_le_bytes(value.to_bits())); - } - - fn write_string(&mut self, value: &str) { - self.write_u32(value.len() as u32); - self.bytes.extend_from_slice(value.as_bytes()); - } -} - -struct ClassInfo { - /// A globally unique, monotonically increasing index for this class. - /// - /// MIR may remove classes as part of certain optimisations, so the class - /// IDs may not be monotonically increasing. The VM expects a list of - /// classes and IDs, without any holes between them. To achieve this we - /// assign every class ID a monotonically increasing index, and use that - /// index when generating bytecode. - index: u32, - - /// The number of slots to reserve for methods. - method_slots: u16, - - /// The number of fields the class defines. - fields: u8, -} - -struct MethodInfo { - index: u16, - hash: u32, -} - -/// A compiler pass that lowers MIR into a bytecode file. -pub(crate) struct Lower<'a> { - db: &'a Database, - mir: &'a Mir, - class_info: &'a HashMap, - method_info: &'a HashMap, - constant_indexes: HashMap, -} - -impl<'a> Lower<'a> { - pub(crate) fn run_all(db: &'a Database, mir: Mir) -> Bytecode { - let mut method_hashes: HashMap<&str, u32> = HashMap::new(); - let mut method_info = HashMap::new(); - let mut class_info = HashMap::new(); - let mut class_index = FIRST_USER_CLASS_ID; - - // These methods are given fixed hash codes so they always reside in a - // fixed slot, removing the need for dynamic dispatch when calling these - // methods. - method_hashes.insert(types::DROPPER_METHOD, DROPPER_INDEX as u32); - method_hashes.insert(types::CALL_METHOD, CALL_CLOSURE_INDEX as u32); - - for (&class_id, class) in &mir.classes { - let index = if class_id.0 < FIRST_USER_CLASS_ID { - class_id.0 - } else { - let idx = class_index; - - class_index += 1; - idx - }; - - let num_methods = class_id.number_of_methods(db); - - // For classes with very few methods, the cost of handling - // collisions is so low we can just keep the table sizes as small as - // possible. - let raw_size = if num_methods <= 4 { - num_methods - } else { - num_methods * METHOD_TABLE_FACTOR - }; - - // The number of methods is a power of two, as this allows the use - // of the & operator instead of the % operator. The & operator - // requires far fewer instructions compared to the the % operator. - let size = nearest_power_of_two(raw_size); - let mut buckets = vec![false; size]; - - for &method_id in &class.methods { - // We use a state of the art hashing algorithm (patent pending): - // each unique name is simply assigned a monotonically - // increasing 32-bits unsigned integer. This is safe because as - // part of type-checking we limit the total number of methods to - // fit in this number. - // - // Indexes can be safely cast to a u16 because we limit the - // number of methods per class to fit in this limit. - let next_hash = method_hashes.len() as u32; - let hash = *method_hashes - .entry(method_id.name(db)) - .or_insert(next_hash); - - // Because the number of methods/buckets is a power of two, we - // can also use the & operator here. The subtractions of 1 are - // needed to ensure the & operator works correctly: - // - // 15 % 8 => 7 - // 15 & 8 => 8 - // 15 & (8 - 1) => 7 - let raw_index = hash as usize & (size - 1); - let mut index = raw_index; - - while buckets[index] { - index = (index + 1) & (buckets.len() - 1); - } - - buckets[index] = true; - - let info = MethodInfo { index: index as u16, hash }; - - method_info.insert(method_id, info); - } - - let info = ClassInfo { - index, - method_slots: size as u16, - fields: class_id.number_of_fields(db) as u8, - }; - - class_info.insert(class_id, info); - } - - for mir_trait in mir.traits.values() { - for method_id in mir_trait - .id - .required_methods(db) - .into_iter() - .chain(mir_trait.id.default_methods(db)) - { - let next_hash = method_hashes.len() as u32; - let hash = *method_hashes - .entry(method_id.name(db)) - .or_insert(next_hash); - - method_info.insert(method_id, MethodInfo { index: 0, hash }); - } - } - - let mut buffer = Buffer::new(); - let main_mod = db.module(db.main_module().unwrap().as_str()); - let main_class = match main_mod.symbol(db, types::MAIN_CLASS) { - Some(types::Symbol::Class(id)) => id, - _ => unreachable!(), - }; - let main_method = main_class.method(db, types::MAIN_METHOD).unwrap(); - let main_class_idx = class_info.get(&main_class).unwrap().index; - let main_method_idx = method_info.get(&main_method).unwrap().index; - - buffer.bytes.extend_from_slice(&SIGNATURE_BYTES); - buffer.write_u8(VERSION); - buffer.write_u32(mir.modules.len() as u32); - buffer.write_u32(mir.classes.len() as u32); - - buffer.write_u16(number_of_methods(&class_info, ClassId::int())); - buffer.write_u16(number_of_methods(&class_info, ClassId::float())); - buffer.write_u16(number_of_methods(&class_info, ClassId::string())); - buffer.write_u16(number_of_methods(&class_info, ClassId::array())); - buffer.write_u16(number_of_methods(&class_info, ClassId::boolean())); - buffer.write_u16(number_of_methods(&class_info, ClassId::nil())); - buffer.write_u16(number_of_methods(&class_info, ClassId::byte_array())); - buffer.write_u16(number_of_methods(&class_info, ClassId::future())); - - buffer.write_u32(main_class_idx); - buffer.write_u16(main_method_idx); - - for index in 0..mir.modules.len() { - let mut chunk = Lower { - db, - mir: &mir, - constant_indexes: HashMap::new(), - class_info: &class_info, - method_info: &method_info, - } - .run(index); - - buffer.write_u64(chunk.len() as u64); - buffer.append(&mut chunk); - } - - Bytecode { bytes: buffer.bytes } - } - - pub(crate) fn run(mut self, module_index: usize) -> Buffer { - let mir_mod = self.mir.modules[module_index].clone(); - - for id in mir_mod.constants { - let val = self.mir.constants.get(&id).cloned().unwrap(); - - self.constant_index(val); - } - - let mut classes_buffer = Buffer::new(); - - classes_buffer.write_u16(mir_mod.classes.len() as u16); - - for class_id in mir_mod.classes { - self.class(class_id, &mut classes_buffer); - } - - let mut module_buffer = Buffer::new(); - - module_buffer.write_u32(module_index as u32); - - // We currently don't validate the number of constants, based on the - // idea that you are _extremely_ unlikely to ever need more than - // (2^32)-1 constants in a single module. - module_buffer.write_u32(self.constant_indexes.len() as u32); - - for (cons, index) in &self.constant_indexes { - module_buffer.write_u32(*index); - - self.constant(cons, &mut module_buffer); - } - - let class_idx = - self.class_info.get(&mir_mod.id.class(self.db)).unwrap().index; - - module_buffer.write_u32(class_idx); - module_buffer.append(&mut classes_buffer); - module_buffer - } - - fn constant(&self, cons: &Constant, buffer: &mut Buffer) { - match cons { - Constant::Int(v) => { - buffer.write_u8(CONST_INTEGER); - buffer.write_i64(*v); - } - Constant::Float(v) => { - buffer.write_u8(CONST_FLOAT); - buffer.write_f64(*v); - } - Constant::String(v) => { - buffer.write_u8(CONST_STRING); - buffer.write_string(v); - } - Constant::Array(values) => { - buffer.write_u8(CONST_ARRAY); - buffer.write_u16(values.len() as u16); - - for cons in values { - self.constant(cons, buffer); - } - } - } - } - - fn class(&mut self, class_id: ClassId, buffer: &mut Buffer) { - let info = self.class_info.get(&class_id).unwrap(); - let mir_class = self.mir.classes.get(&class_id).unwrap(); - - buffer.write_u32(info.index); - buffer.write_bool(class_id.kind(self.db).is_async()); - buffer.write_string(class_id.name(self.db)); - buffer.write_u8(info.fields); - buffer.write_u16(info.method_slots); - buffer.write_u16(mir_class.methods.len() as u16); - - for &mid in &mir_class.methods { - self.method(mid, buffer); - } - } - - fn method(&mut self, method_id: MethodId, buffer: &mut Buffer) { - let info = self.method_info.get(&method_id).unwrap(); - let method = self.mir.methods.get(&method_id).unwrap(); - let regs = method.registers.len(); - let mut jump_tables = Vec::new(); - let mut locations = Vec::new(); - - // if method.id.name(self.db) == "foo" { - // println!( - // "{}", - // crate::mir::printer::to_dot(self.db, self.mir, &[&method]) - // ); - // } - - // This should never happen, unless somebody is writing _really_ - // crazy code, or due to a compiler bug. Because of this, we just - // resort to a simple assertion. - assert!(regs <= REGISTERS_LIMIT); - assert!(method.body.blocks.len() <= BLOCKS_LIMIT); - - buffer.write_u16(info.index); - buffer.write_u32(info.hash); - buffer.write_u16(regs as u16); - - self.instructions(method, buffer, &mut jump_tables, &mut locations); - self.location_table(buffer, locations); - - assert!(jump_tables.len() <= JUMP_TABLES_LIMIT); - - buffer.write_u16(jump_tables.len() as u16); - - for table in jump_tables { - // MIR already limits the possible switch (and thus jump table - // values), e.g. by limiting the number of enum variants. - buffer.write_u16(table.len() as u16); - - for value in table { - buffer.write_u32(value); - } - } - } - - fn instructions( - &mut self, - method: &Method, - buffer: &mut Buffer, - jump_tables: &mut Vec>, - locations: &mut Vec<(usize, LocationId)>, - ) { - let mut instructions = Vec::new(); - let mut queue = VecDeque::new(); - let mut visited = HashSet::new(); - let mut offsets = vec![0_u16; method.body.blocks.len()]; - - // Arguments are pushed in order, meaning that for arguments `(a, b, c)` - // the stack would be `[a, b, c]`, and thus the first pop produces the - // last argument. - let num_args = method.id.number_of_arguments(self.db) as u16; - let arg_range = if method.id.is_instance_method(self.db) { - 0..=num_args - } else { - // For static methods the receiver isn't passed, so we subtract one - // and saturate at zero. This way a static method with a single - // argument still generates one pop(). - 0..=num_args.saturating_sub(1) - }; - - for index in arg_range.rev() { - instructions.push(Instruction::one(Opcode::Pop, index)); - } - - queue.push_back(method.body.start_id); - visited.insert(method.body.start_id); - - while let Some(block_id) = queue.pop_front() { - let block = &method.body.blocks[block_id.0]; - let offset = instructions.len() as u16; - - offsets[block_id.0] = offset; - - for ins in &block.instructions { - self.instruction( - method, - ins, - &mut instructions, - jump_tables, - locations, - ); - } - - for &child in &block.successors { - if visited.insert(child) { - queue.push_back(child); - } - } - } - - for ins in &mut instructions { - match ins.opcode { - Opcode::Goto => { - ins.arguments[0] = offsets[ins.arg(0) as usize]; - } - Opcode::Branch => { - ins.arguments[1] = offsets[ins.arg(1) as usize]; - ins.arguments[2] = offsets[ins.arg(2) as usize]; - } - Opcode::BranchResult => { - ins.arguments[0] = offsets[ins.arg(0) as usize]; - ins.arguments[1] = offsets[ins.arg(1) as usize]; - } - _ => {} - } - } - - // For jump tables we don't need to update the instruction, but the jump - // tables themselves. - for table in jump_tables { - for index in 0..table.len() { - table[index] = offsets[table[index] as usize] as u32; - } - } - - buffer.write_u32(instructions.len() as u32); - - for ins in instructions { - buffer.write_u8(ins.opcode.to_int()); - - for index in 0..ins.opcode.arity() { - buffer.write_u16(ins.arg(index as usize)); - } - } - } - - fn instruction( - &mut self, - method: &Method, - instruction: &mir::Instruction, - buffer: &mut Vec, - jump_tables: &mut Vec>, - locations: &mut Vec<(usize, LocationId)>, - ) { - match instruction { - mir::Instruction::Nil(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::GetNil, reg)); - } - mir::Instruction::CheckRefs(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::CheckRefs, reg)); - } - mir::Instruction::Reduce(ins) => { - buffer.push(Instruction::one(Opcode::Reduce, ins.amount)); - } - mir::Instruction::Goto(ins) => { - let block_id = ins.block.0 as u16; - - buffer.push(Instruction::one(Opcode::Goto, block_id)); - } - mir::Instruction::Branch(ins) => { - let reg = ins.condition.0 as u16; - let ok = ins.if_true.0 as u16; - let err = ins.if_false.0 as u16; - - buffer.push(Instruction::three(Opcode::Branch, reg, ok, err)); - } - mir::Instruction::BranchResult(ins) => { - let ok = ins.ok.0 as u16; - let err = ins.error.0 as u16; - - buffer.push(Instruction::two(Opcode::BranchResult, ok, err)); - } - mir::Instruction::JumpTable(ins) => { - let reg = ins.register.0 as u16; - let idx = jump_tables.len() as u16; - let table = ins.blocks.iter().map(|b| b.0 as u32).collect(); - - jump_tables.push(table); - buffer.push(Instruction::two(Opcode::JumpTable, reg, idx)); - } - mir::Instruction::Return(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::Return, reg)); - } - mir::Instruction::ReturnAsync(ins) => { - let op = Opcode::ProcessWriteResult; - let reg = ins.register.0 as u16; - let val = ins.value.0 as u16; - - buffer.push(Instruction::three(op, reg, val, 0)); - } - mir::Instruction::Throw(ins) => { - let reg = ins.register.0 as u16; - let unwind = 1; - - buffer.push(Instruction::two(Opcode::Throw, reg, unwind)); - } - mir::Instruction::ThrowAsync(ins) => { - let op = Opcode::ProcessWriteResult; - let reg = ins.register.0 as u16; - let val = ins.value.0 as u16; - - buffer.push(Instruction::three(op, reg, val, 1)); - } - mir::Instruction::Finish(v) => { - let op = Opcode::ProcessFinishTask; - let arg = v.terminate as u16; - - buffer.push(Instruction::one(op, arg)); - } - mir::Instruction::AllocateArray(ins) => { - let op = Opcode::ArrayAllocate; - let reg = ins.register.0 as u16; - - push_values(buffer, &ins.values); - buffer.push(Instruction::one(op, reg)); - } - mir::Instruction::True(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::GetTrue, reg)); - } - mir::Instruction::False(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::GetFalse, reg)); - } - mir::Instruction::Float(ins) => { - let op = Opcode::GetConstant; - let reg = ins.register.0 as u16; - let idx = self.constant_index(Constant::Float(ins.value)); - let (arg1, arg2) = split_u32(idx); - - buffer.push(Instruction::three(op, reg, arg1, arg2)); - } - mir::Instruction::Int(ins) => { - let op = Opcode::GetConstant; - let reg = ins.register.0 as u16; - let idx = self.constant_index(Constant::Int(ins.value)); - let (arg1, arg2) = split_u32(idx); - - buffer.push(Instruction::three(op, reg, arg1, arg2)); - } - mir::Instruction::String(ins) => { - let op = Opcode::GetConstant; - let reg = ins.register.0 as u16; - let idx = - self.constant_index(Constant::String(ins.value.clone())); - let (arg1, arg2) = split_u32(idx); - - buffer.push(Instruction::three(op, reg, arg1, arg2)); - } - mir::Instruction::Strings(ins) => { - let op = Opcode::StringConcat; - let reg = ins.register.0 as u16; - - push_values(buffer, &ins.values); - buffer.push(Instruction::one(op, reg)); - } - mir::Instruction::MoveRegister(ins) => { - let reg = ins.target.0 as u16; - let src = ins.source.0 as u16; - - buffer.push(Instruction::two(Opcode::MoveRegister, reg, src)); - } - mir::Instruction::MoveResult(ins) => { - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(Opcode::MoveResult, reg)); - } - mir::Instruction::Allocate(ins) => { - let reg = ins.register.0 as u16; - let idx = self.class_info.get(&ins.class).unwrap().index; - let (arg1, arg2) = split_u32(idx); - let op = if ins.class.kind(self.db).is_async() { - Opcode::ProcessAllocate - } else { - Opcode::Allocate - }; - - buffer.push(Instruction::three(op, reg, arg1, arg2)); - } - mir::Instruction::CallStatic(ins) => { - let op = Opcode::CallStatic; - let class = self.class_info.get(&ins.class).unwrap().index; - let method = self.method_info.get(&ins.method).unwrap().index; - let (arg1, arg2) = split_u32(class); - - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::three(op, arg1, arg2, method)); - } - mir::Instruction::CallVirtual(ins) => { - let op = Opcode::CallVirtual; - let rec = ins.receiver.0 as u16; - let idx = self.method_info.get(&ins.method).unwrap().index; - - buffer.push(Instruction::one(Opcode::Push, rec)); - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::two(op, rec, idx)); - } - mir::Instruction::CallBuiltin(ins) => { - let op = Opcode::BuiltinFunctionCall; - let idx = ins.id.to_int(); - - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::one(op, idx)); - } - mir::Instruction::CallClosure(ins) => { - let op = Opcode::CallVirtual; - let rec = ins.receiver.0 as u16; - let idx = CALL_CLOSURE_INDEX; - - buffer.push(Instruction::one(Opcode::Push, rec)); - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::two(op, rec, idx)); - } - mir::Instruction::CallDropper(ins) => { - let op = Opcode::CallVirtual; - let rec = ins.receiver.0 as u16; - let idx = DROPPER_INDEX; - - buffer.push(Instruction::one(Opcode::Push, rec)); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::two(op, rec, idx)); - } - mir::Instruction::CallDynamic(ins) => { - let op = Opcode::CallDynamic; - let rec = ins.receiver.0 as u16; - let (hash1, hash2) = - split_u32(self.method_info.get(&ins.method).unwrap().hash); - - buffer.push(Instruction::one(Opcode::Push, rec)); - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::three(op, rec, hash1, hash2)); - } - mir::Instruction::Clone(ins) => { - let reg = ins.register.0 as u16; - let src = ins.source.0 as u16; - let op = match ins.kind { - CloneKind::Float => Opcode::FloatClone, - CloneKind::Int => Opcode::IntClone, - CloneKind::Process | CloneKind::String => Opcode::Increment, - CloneKind::Other => Opcode::MoveRegister, - }; - - buffer.push(Instruction::two(op, reg, src)); - } - mir::Instruction::Decrement(ins) => { - let op = Opcode::Decrement; - let reg = ins.register.0 as u16; - - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::one(op, reg)); - } - mir::Instruction::DecrementAtomic(ins) => { - let op = Opcode::DecrementAtomic; - let reg = ins.register.0 as u16; - let val = ins.value.0 as u16; - - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::two(op, reg, val)); - } - mir::Instruction::Free(ins) => { - let op = Opcode::Free; - let reg = ins.register.0 as u16; - - buffer.push(Instruction::one(op, reg)); - } - mir::Instruction::GetConstant(ins) => { - let op = Opcode::GetConstant; - let reg = ins.register.0 as u16; - let val = self.mir.constants.get(&ins.id).cloned().unwrap(); - let (idx1, idx2) = split_u32(self.constant_index(val)); - - buffer.push(Instruction::three(op, reg, idx1, idx2)); - } - mir::Instruction::GetField(ins) => { - let rec_typ = method.registers.value_type(ins.receiver); - let stype = method.id.self_type(self.db); - let op = if rec_typ.is_async(self.db, stype) { - Opcode::ProcessGetField - } else { - Opcode::GetField - }; - - let reg = ins.register.0 as u16; - let rec = ins.receiver.0 as u16; - let idx = ins.field.index(self.db) as u16; - - buffer.push(Instruction::three(op, reg, rec, idx)); - } - mir::Instruction::SetField(ins) => { - let rec_typ = method.registers.value_type(ins.receiver); - let stype = method.id.self_type(self.db); - let op = if rec_typ.is_async(self.db, stype) { - Opcode::ProcessSetField - } else { - Opcode::SetField - }; - - let rec = ins.receiver.0 as u16; - let idx = ins.field.index(self.db) as u16; - let val = ins.value.0 as u16; - - buffer.push(Instruction::three(op, rec, idx, val)); - } - mir::Instruction::Increment(ins) => { - let op = Opcode::Increment; - let reg = ins.register.0 as u16; - let val = ins.value.0 as u16; - - buffer.push(Instruction::two(op, reg, val)); - } - mir::Instruction::IntEq(ins) => { - let op = Opcode::IntEq; - let reg = ins.register.0 as u16; - let left = ins.left.0 as u16; - let right = ins.right.0 as u16; - - buffer.push(Instruction::three(op, reg, left, right)); - } - mir::Instruction::StringEq(ins) => { - let op = Opcode::StringEq; - let reg = ins.register.0 as u16; - let left = ins.left.0 as u16; - let right = ins.right.0 as u16; - - buffer.push(Instruction::three(op, reg, left, right)); - } - mir::Instruction::RawInstruction(ins) => { - let mut args = [0, 0, 0, 0, 0]; - - for (idx, arg) in args.iter_mut().enumerate() { - if let Some(reg) = ins.arguments.get(idx) { - *arg = reg.0 as u16; - } else { - break; - } - } - - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::new(ins.opcode, args)); - } - mir::Instruction::RefKind(ins) => { - let op = Opcode::RefKind; - let reg = ins.register.0 as u16; - let val = ins.value.0 as u16; - - buffer.push(Instruction::two(op, reg, val)); - } - mir::Instruction::Send(ins) => { - let op = Opcode::ProcessSend; - let rec = ins.receiver.0 as u16; - let idx = self.method_info.get(&ins.method).unwrap().index; - let wait = ins.wait as u16; - - buffer.push(Instruction::one(Opcode::Push, rec)); - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::three(op, rec, idx, wait)); - } - mir::Instruction::SendAsync(ins) => { - let op = Opcode::ProcessSendAsync; - let reg = ins.register.0 as u16; - let rec = ins.receiver.0 as u16; - let idx = self.method_info.get(&ins.method).unwrap().index; - - buffer.push(Instruction::one(Opcode::Push, rec)); - push_values(buffer, &ins.arguments); - locations.push((buffer.len(), ins.location)); - buffer.push(Instruction::three(op, reg, rec, idx)); - } - mir::Instruction::Drop(_) => { - // This instruction is expanded before converting MIR to - // bytecode, and thus shouldn't occur at this point. - unreachable!(); - } - } - } - - fn location_table( - &mut self, - buffer: &mut Buffer, - locations: Vec<(usize, LocationId)>, - ) { - let mut entries = Vec::new(); - - for (index, id) in locations { - let loc = self.mir.location(id); - let name = loc.method.name(self.db).clone(); - let file = loc.module.file(self.db).to_string_lossy().into_owned(); - let line = *loc.range.line_range.start() as u16; - let file_idx = self.constant_index(Constant::String(file)); - let name_idx = self.constant_index(Constant::String(name)); - - entries.push((index as u32, line, file_idx, name_idx)); - } - - buffer.write_u16(entries.len() as u16); - - for (index, line, file, name) in entries { - buffer.write_u32(index); - buffer.write_u16(line); - buffer.write_u32(file); - buffer.write_u32(name); - } - } - - fn constant_index(&mut self, constant: Constant) -> u32 { - let len = self.constant_indexes.len() as u32; - - *self.constant_indexes.entry(constant).or_insert(len) - } -} diff --git a/compiler/src/compiler.rs b/compiler/src/compiler.rs index 2a40cd104..0c99f8d10 100644 --- a/compiler/src/compiler.rs +++ b/compiler/src/compiler.rs @@ -1,20 +1,22 @@ -use crate::codegen; -use crate::config::{Config, IMAGE_EXT, SOURCE, SOURCE_EXT}; +use crate::config::{BuildDirectories, Output}; +use crate::config::{Config, SOURCE, SOURCE_EXT}; use crate::hir; +use crate::linker::link; +use crate::llvm; use crate::mir::{passes as mir, Mir}; use crate::modules_parser::{ModulesParser, ParsedModule}; use crate::state::State; use crate::type_check::define_types::{ - CheckTraitImplementations, CheckTypeParameters, DefineFields, - DefineTraitRequirements, DefineTypeParameterRequirements, - DefineTypeParameters, DefineTypes, DefineVariants, ImplementTraits, - InsertPrelude, + define_runtime_result_fields, CheckTraitImplementations, + CheckTypeParameters, DefineFields, DefineTraitRequirements, + DefineTypeParameterRequirements, DefineTypeParameters, DefineTypes, + DefineVariants, ImplementTraits, InsertPrelude, }; use crate::type_check::expressions::{DefineConstants, Expressions}; use crate::type_check::imports::DefineImportedTypes; use crate::type_check::methods::{ - define_builtin_functions, CheckMainMethod, DefineMethods, - DefineModuleMethodNames, ImplementTraitMethods, + CheckMainMethod, DefineMethods, DefineModuleMethodNames, + ImplementTraitMethods, }; use std::env::current_dir; use std::ffi::OsStr; @@ -52,31 +54,26 @@ impl Compiler { self.all_source_modules()? }; - let ast_modules = ModulesParser::new(&mut self.state).run(input); + let ast = ModulesParser::new(&mut self.state).run(input); + let hir = self.compile_hir(ast)?; - self.compile_to_mir(ast_modules)?; - Ok(()) + self.compile_mir(hir).map(|_| ()) } - pub fn compile_to_file( + pub fn run( &mut self, file: Option, ) -> Result { - let input = self.main_module_path(file)?; - let code = self.compile_bytecode(input.clone())?; - let path = self.write_code(input, code); - - Ok(path) - } + let file = self.main_module_path(file)?; + let main_mod = self.state.db.main_module().unwrap().clone(); + let ast = ModulesParser::new(&mut self.state) + .run(vec![(main_mod, file.clone())]); - pub fn compile_to_memory( - &mut self, - file: Option, - ) -> Result, CompileError> { - let input = self.main_module_path(file)?; - let code = self.compile_bytecode(input)?; + let hir = self.compile_hir(ast)?; + let mut mir = self.compile_mir(hir)?; - Ok(code.bytes) + self.optimise_mir(&mut mir); + self.compile_machine_code(&mir, file) } pub fn print_diagnostics(&self) { @@ -114,54 +111,45 @@ impl Compiler { Ok(path) } - fn compile_bytecode( + fn compile_mir( &mut self, - file: PathBuf, - ) -> Result { - let main_mod = self.state.db.main_module().unwrap().clone(); - let ast_modules = - ModulesParser::new(&mut self.state).run(vec![(main_mod, file)]); - - self.compile_to_mir(ast_modules).map(|mut mir| { - self.optimise_mir(&mut mir); - codegen::Lower::run_all(&self.state.db, mir) - }) - } - - fn compile_to_mir( - &mut self, - ast_modules: Vec, + mut modules: Vec, ) -> Result { - let mut hir_mods = if let Some(v) = self.lower_to_hir(ast_modules) { - v - } else { - return Err(CompileError::Invalid); - }; - - if !self.type_check(&mut hir_mods) { + if !self.check_types(&mut modules) { return Err(CompileError::Invalid); } - self.lower_to_mir(hir_mods) + let mut mir = Mir::new(); + + mir::check_global_limits(&mut self.state) + .map_err(CompileError::Internal)?; + + if mir::DefineConstants::run_all(&mut self.state, &mut mir, &modules) + && mir::LowerToMir::run_all(&mut self.state, &mut mir, modules) + { + Ok(mir) + } else { + Err(CompileError::Invalid) + } } - fn lower_to_hir( + fn compile_hir( &mut self, modules: Vec, - ) -> Option> { + ) -> Result, CompileError> { let hir = hir::LowerToHir::run_all(&mut self.state, modules); // Errors produced at this state are likely to result in us not being // able to compile the program properly (e.g. imported modules don't // exist), so we bail out right away. if self.state.diagnostics.has_errors() { - None + Err(CompileError::Invalid) } else { - Some(hir) + Ok(hir) } } - fn type_check(&mut self, modules: &mut Vec) -> bool { + fn check_types(&mut self, modules: &mut Vec) -> bool { let state = &mut self.state; DefineTypes::run_all(state, modules) @@ -176,62 +164,48 @@ impl Compiler { && CheckTypeParameters::run_all(state, modules) && DefineVariants::run_all(state, modules) && DefineFields::run_all(state, modules) + && define_runtime_result_fields(state) && DefineMethods::run_all(state, modules) && CheckMainMethod::run(state) && ImplementTraitMethods::run_all(state, modules) - && define_builtin_functions(state) && DefineConstants::run_all(state, modules) && Expressions::run_all(state, modules) } - fn lower_to_mir( - &mut self, - modules: Vec, - ) -> Result { - let state = &mut self.state; - let mut mir = Mir::new(); - - mir::check_global_limits(state).map_err(CompileError::Internal)?; - - if mir::DefineConstants::run_all(state, &mut mir, &modules) - && mir::LowerToMir::run_all(state, &mut mir, modules) - { - Ok(mir) - } else { - Err(CompileError::Invalid) - } - } - fn optimise_mir(&mut self, mir: &mut Mir) { mir::ExpandDrop::run_all(&self.state.db, mir); + mir::ExpandReference::run_all(&self.state.db, mir); mir::clean_up_basic_blocks(mir); } - fn write_code( - &self, + fn compile_machine_code( + &mut self, + mir: &Mir, main_file: PathBuf, - code: codegen::Bytecode, - ) -> PathBuf { - let path = self.state.config.output.clone().unwrap_or_else(|| { - let name = main_file - .file_stem() - .map(|s| s.to_string_lossy().into_owned()) - .unwrap_or_else(|| "main".to_string()); - - let dir = if self.state.config.build.is_dir() { - self.state.config.build.clone() - } else { - current_dir().unwrap_or_else(|_| PathBuf::new()) - }; + ) -> Result { + let dirs = BuildDirectories::new(&self.state.config); + + dirs.create().map_err(CompileError::Internal)?; + + let exe = match &self.state.config.output { + Output::Derive => { + let name = main_file + .file_stem() + .map(|s| s.to_string_lossy().into_owned()) + .unwrap_or_else(|| "main".to_string()); - let mut path = dir.join(name); + dirs.bin.join(name) + } + Output::File(name) => dirs.bin.join(name), + Output::Path(path) => path.clone(), + }; - path.set_extension(IMAGE_EXT); - path - }); + let objects = llvm::passes::Compile::run_all(&self.state, &dirs, mir) + .map_err(CompileError::Internal)?; - std::fs::write(&path, code.bytes).unwrap(); - path + link(&self.state.config, &exe, &objects) + .map_err(CompileError::Internal)?; + Ok(exe) } fn module_name_from_path(&self, file: &Path) -> ModuleName { @@ -239,11 +213,11 @@ impl Compiler { .ok() .or_else(|| file.strip_prefix(&self.state.config.tests).ok()) .or_else(|| { - // This allows us to check e.g. `./libstd/src/std/string.inko` + // This allows us to check e.g. `./std/src/std/string.inko` // while the current working directory is `.`. This is useful // when e.g. checking files using a text editor, as they would // likely have the working directory set to `.` and not - // `./libstd`. + // `./std`. let mut components = file.components(); if components.any(|c| c.as_os_str() == SOURCE) { diff --git a/compiler/src/config.rs b/compiler/src/config.rs index 633f0ec77..a43ecdc73 100644 --- a/compiler/src/config.rs +++ b/compiler/src/config.rs @@ -1,15 +1,14 @@ //! Configuration for the compiler. use crate::presenters::{JSONPresenter, Presenter, TextPresenter}; use crate::source_paths::SourcePaths; +use crate::target::Target; use std::env; -use std::path::PathBuf; +use std::fs::create_dir_all; +use std::path::{Path, PathBuf}; use types::module_name::ModuleName; /// The extension to use for source files. -pub const SOURCE_EXT: &str = "inko"; - -/// The extension to use for bytecode files. -pub const IMAGE_EXT: &str = "ibi"; +pub(crate) const SOURCE_EXT: &str = "inko"; /// The name of the module to compile if no explicit file/module is provided. pub(crate) const MAIN_MODULE: &str = "main"; @@ -26,11 +25,91 @@ const TESTS: &str = "test"; /// The name of the directory to store build files in. const BUILD: &str = "build"; +fn create_directory(path: &Path) -> Result<(), String> { + if path.is_dir() { + return Ok(()); + } + + create_dir_all(path) + .map_err(|err| format!("Failed to create {}: {}", path.display(), err)) +} + +/// A type storing the various build directories to use. +pub(crate) struct BuildDirectories { + /// The base build directory. + pub(crate) build: PathBuf, + + /// The directory to store object files in. + pub(crate) objects: PathBuf, + + /// The directory to place executable files in. + pub(crate) bin: PathBuf, +} + +impl BuildDirectories { + pub(crate) fn new(config: &Config) -> BuildDirectories { + let build = config.build.join(config.mode.directory_name()); + let objects = build.join("objects"); + let bin = build.clone(); + + BuildDirectories { build, objects, bin } + } + + pub(crate) fn create(&self) -> Result<(), String> { + create_directory(&self.build) + .and_then(|_| create_directory(&self.objects)) + .and_then(|_| create_directory(&self.bin)) + } +} + +/// A type describing to what degree a program should be optimised. +#[derive(Clone, Copy)] +pub enum Mode { + /// A mode suitable for development and debugging. + /// + /// This mode favours fast compile times over runtime performance. For + /// releases/deployments you should use the dedicated release mode. + Debug, + + /// A mode suitable for releases. + /// + /// In this mode a reasonable number of optimisations is enabled, such that + /// there's a healthy balance between runtime performance and compile times. + Release, +} + +impl Mode { + pub(crate) fn directory_name(self) -> &'static str { + match self { + Mode::Debug => "debug", + Mode::Release => "release", + } + } +} + +/// A type describing where to write the executable to. +pub enum Output { + /// Derive the output path from the main module, and place it in the default + /// output directory. + Derive, + + /// Write the executable to the default output directory, but using the + /// given name. + File(String), + + /// Write the executable to the given path. + Path(PathBuf), +} + /// A type for storing compiler configuration, such as the source directories to /// search for modules. pub struct Config { /// The directory containing the Inko's standard library. - pub(crate) libstd: PathBuf, + pub(crate) std: PathBuf, + + /// The directory containing runtime library files to link to the generated + /// code. + pub runtime: PathBuf, /// The directory containing the project's source code. pub(crate) source: PathBuf, @@ -48,51 +127,49 @@ pub struct Config { /// third-party dependencies. pub sources: SourcePaths, + /// The path to save the executable at. + pub output: Output, + + /// The optimisation mode to apply when compiling code. + pub mode: Mode, + /// The presenter to use for displaying diagnostics. pub(crate) presenter: Box, /// Modules to implicitly import and process. pub(crate) implicit_imports: Vec, - /// The file to write a compiled bytecode file to. - pub output: Option, + /// The target to compile code for. + pub(crate) target: Target, } impl Config { - pub(crate) fn new() -> Self { + pub fn new() -> Self { let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::new()); - let libstd = option_env!("INKO_LIBSTD") - .map(PathBuf::from) - .unwrap_or_else(|| { - // To ease the development process, we default to the standard - // library directory in the Git repository. This way you don't - // need to set any environment variables during development. - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .join("libstd") - .join(SOURCE) - }); + let std = PathBuf::from(env!("INKO_STD")); Self { - libstd, + std, + runtime: PathBuf::from(env!("INKO_RT")), source: cwd.join(SOURCE), tests: cwd.join(TESTS), build: cwd.join(BUILD), - dependencies: cwd.join(DEP), + dependencies: cwd.join(DEP).join(SOURCE), sources: SourcePaths::new(), presenter: Box::new(TextPresenter::with_colors()), implicit_imports: vec![], - output: None, + output: Output::Derive, + target: Target::native(), + mode: Mode::Debug, } } fn add_default_source_directories(&mut self) { - if self.libstd.is_dir() { - self.sources.add(self.libstd.clone()); + if self.std.is_dir() { + self.sources.add(self.std.clone()); } - if self.source.is_dir() && self.source != self.libstd { + if self.source.is_dir() && self.source != self.std { self.sources.add(self.source.clone()); } @@ -116,6 +193,15 @@ impl Config { Ok(()) } + pub fn set_target(&mut self, name: &str) -> Result<(), String> { + if let Some(val) = Target::from_str(name) { + self.target = val; + Ok(()) + } else { + Err(format!("The target '{}' isn't supported", name)) + } + } + pub(crate) fn main_source_module(&self) -> PathBuf { let mut main_file = self.source.join(MAIN_MODULE); diff --git a/compiler/src/diagnostics.rs b/compiler/src/diagnostics.rs index e07bb5079..03fced2da 100644 --- a/compiler/src/diagnostics.rs +++ b/compiler/src/diagnostics.rs @@ -8,29 +8,21 @@ use std::path::PathBuf; pub(crate) enum DiagnosticId { DuplicateSymbol, InvalidAssign, - InvalidBound, InvalidCall, - InvalidClass, InvalidConstExpr, InvalidFile, InvalidImplementation, InvalidMethod, InvalidSyntax, - InvalidTry, InvalidType, MissingTrait, - PrivateSymbol, InvalidSymbol, InvalidLoopKeyword, InvalidThrow, MissingField, - InvalidRef, InvalidPattern, - InvalidField, - MissingThrow, Unreachable, - MovedVariable, - InvalidMove, + Moved, InvalidMatch, LimitReached, MissingMain, @@ -47,23 +39,15 @@ impl fmt::Display for DiagnosticId { DiagnosticId::InvalidSymbol => "invalid-symbol", DiagnosticId::InvalidType => "invalid-type", DiagnosticId::MissingTrait => "missing-trait", - DiagnosticId::InvalidBound => "invalid-bound", DiagnosticId::InvalidMethod => "invalid-method", DiagnosticId::InvalidImplementation => "invalid-implementation", - DiagnosticId::InvalidClass => "invalid-class", - DiagnosticId::PrivateSymbol => "private-symbol", - DiagnosticId::InvalidTry => "invalid-try", DiagnosticId::InvalidAssign => "invalid-assign", DiagnosticId::InvalidLoopKeyword => "invalid-loop-keyword", DiagnosticId::InvalidThrow => "invalid-throw", DiagnosticId::MissingField => "missing-field", - DiagnosticId::InvalidRef => "invalid-ref", DiagnosticId::InvalidPattern => "invalid-pattern", - DiagnosticId::InvalidField => "invalid-field", - DiagnosticId::MissingThrow => "missing-throw", DiagnosticId::Unreachable => "unreachable", - DiagnosticId::MovedVariable => "moved-variable", - DiagnosticId::InvalidMove => "invalid-move", + DiagnosticId::Moved => "moved", DiagnosticId::InvalidMatch => "invalid-match", DiagnosticId::LimitReached => "limit-reached", DiagnosticId::MissingMain => "missing-main", @@ -304,7 +288,7 @@ impl Diagnostics { location: SourceLocation, ) { self.error( - DiagnosticId::PrivateSymbol, + DiagnosticId::InvalidSymbol, format!("The field '{}' is private", name), file, location, @@ -321,7 +305,7 @@ impl Diagnostics { self.error( DiagnosticId::InvalidType, format!( - "Incorrect type: expected '{}', found '{}'", + "Expected a value of type '{}', found '{}'", expected, given ), file, @@ -392,20 +376,6 @@ impl Diagnostics { ); } - pub(crate) fn throw_not_allowed( - &mut self, - file: PathBuf, - location: SourceLocation, - ) { - self.error( - DiagnosticId::InvalidThrow, - "Throwing isn't allowed, as the surrounding closure or method \ - doesn't specify a throw type", - file, - location, - ); - } - pub(crate) fn incorrect_call_arguments( &mut self, given: usize, @@ -539,37 +509,6 @@ impl Diagnostics { ); } - pub(crate) fn never_throws( - &mut self, - file: PathBuf, - location: SourceLocation, - ) { - self.error( - DiagnosticId::InvalidTry, - "This expression never throws", - file, - location, - ); - } - - pub(crate) fn missing_throw( - &mut self, - name: String, - file: PathBuf, - location: SourceLocation, - ) { - self.error( - DiagnosticId::MissingThrow, - format!( - "A value of type '{}' is expected to be thrown, \ - but no value is ever thrown", - name - ), - file, - location, - ); - } - pub(crate) fn unreachable( &mut self, file: PathBuf, @@ -602,33 +541,35 @@ impl Diagnostics { ); } - pub(crate) fn unsendable_type( + pub(crate) fn unsendable_argument( &mut self, - name: String, + argument: String, file: PathBuf, location: SourceLocation, ) { self.error( DiagnosticId::InvalidType, format!( - "Values of type '{}' can't be sent between processes", - name + "The receiver of this call requires sendable arguments, \ + but '{}' isn't sendable", + argument, ), file, location, ); } - pub(crate) fn unsendable_type_in_recover( + pub(crate) fn unsendable_return_type( &mut self, name: String, file: PathBuf, location: SourceLocation, ) { self.error( - DiagnosticId::InvalidType, + DiagnosticId::InvalidCall, format!( - "Values of type '{}' can't be captured by recover expressions", + "The receiver of this call requires a sendable return type, \ + but '{}' isn't sendable", name ), file, @@ -636,61 +577,57 @@ impl Diagnostics { ); } - pub(crate) fn unsendable_field_value( + pub(crate) fn unsendable_async_type( &mut self, - field_name: &str, - type_name: String, + name: String, file: PathBuf, location: SourceLocation, ) { self.error( - DiagnosticId::InvalidSymbol, + DiagnosticId::InvalidType, format!( - "The field '{}' can't be assigned a value of type '{}', \ - as it's not sendable", - field_name, type_name + "Values of type '{}' can't be sent between processes", + name ), file, location, ); } - pub(crate) fn unsendable_return_type( + pub(crate) fn unsendable_type_in_recover( &mut self, - name: &str, - type_name: String, + name: String, file: PathBuf, location: SourceLocation, ) { self.error( - DiagnosticId::InvalidCall, + DiagnosticId::InvalidType, format!( - "The method '{}' isn't available because its receiver is a \ - unique value, and the return type ('{}') isn't sendable", - name, type_name + "Values of type '{}' can't be captured by recover expressions", + name ), file, location, ); } - pub(crate) fn unsendable_throw_type( + pub(crate) fn unsendable_field_value( &mut self, - name: &str, + field_name: &str, type_name: String, file: PathBuf, location: SourceLocation, ) { self.error( - DiagnosticId::InvalidCall, + DiagnosticId::InvalidSymbol, format!( - "The method '{}' isn't available because its receiver is a \ - unique value, and the throw type ('{}') isn't sendable", - name, type_name + "The field '{}' can't be assigned a value of type '{}', \ + as it's not sendable", + field_name, type_name ), file, location, - ) + ); } pub(crate) fn self_in_closure_in_recover( @@ -732,7 +669,7 @@ impl Diagnostics { location: SourceLocation, ) { self.error( - DiagnosticId::MovedVariable, + DiagnosticId::Moved, format!("'{}' can't be used as it has been moved", name), file, location, @@ -746,7 +683,7 @@ impl Diagnostics { location: SourceLocation, ) { self.error( - DiagnosticId::MovedVariable, + DiagnosticId::Moved, format!("'{}' can't be used, as 'self' has been moved", name), file, location, @@ -760,7 +697,7 @@ impl Diagnostics { location: SourceLocation, ) { self.error( - DiagnosticId::InvalidMove, + DiagnosticId::Moved, format!( "This closure can't capture '{}', as '{}' has been moved", name, name, @@ -777,7 +714,7 @@ impl Diagnostics { location: SourceLocation, ) { self.error( - DiagnosticId::InvalidMove, + DiagnosticId::Moved, format!( "'{}' can't be moved inside a loop, as its value \ would be unavailable in the next iteration", @@ -805,20 +742,38 @@ impl Diagnostics { ); } - pub(crate) fn cant_infer_throw_type( + pub(crate) fn cant_assign_type( &mut self, + name: &str, file: PathBuf, location: SourceLocation, ) { self.error( DiagnosticId::InvalidType, - "The throw type of this expression can't be inferred", + format!( + "Values of type '{}' can't be assigned to variables or fields", + name + ), + file, + location, + ) + } + + pub(crate) fn string_literal_too_large( + &mut self, + limit: usize, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::LimitReached, + format!("String literals can't be greater than {} bytes", limit), file, location, ); } - pub(crate) fn cant_assign_type( + pub(crate) fn type_parameter_already_mutable( &mut self, name: &str, file: PathBuf, @@ -826,24 +781,71 @@ impl Diagnostics { ) { self.error( DiagnosticId::InvalidType, + format!("The type parameter '{}' is already mutable", name), + file, + location, + ); + } + + pub(crate) fn invalid_try( + &mut self, + name: String, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::InvalidThrow, format!( - "Values of type '{}' can't be assigned to variables or fields", + "This expression must return an 'Option' or 'Result', \ + found '{}'", name ), file, location, - ) + ); } - pub(crate) fn string_literal_too_large( + pub(crate) fn try_not_available( &mut self, - limit: usize, file: PathBuf, location: SourceLocation, ) { self.error( - DiagnosticId::LimitReached, - format!("String literals can't be greater than {} bytes", limit), + DiagnosticId::InvalidThrow, + "'try' can only be used in methods that return an \ + 'Option' or 'Result'", + file, + location, + ); + } + + pub(crate) fn throw_not_available( + &mut self, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::InvalidThrow, + "'throw' can only be used in methods that return a 'Result'", + file, + location, + ); + } + + pub(crate) fn invalid_throw( + &mut self, + error: String, + returns: String, + file: PathBuf, + location: SourceLocation, + ) { + self.error( + DiagnosticId::InvalidThrow, + format!( + "Can't throw '{}' as the surrounding method \ + returns a '{}'", + error, returns, + ), file, location, ); diff --git a/compiler/src/hir.rs b/compiler/src/hir.rs index 92e5efa00..6956e79c1 100644 --- a/compiler/src/hir.rs +++ b/compiler/src/hir.rs @@ -11,9 +11,7 @@ use ::ast::source_location::SourceLocation; use std::path::PathBuf; use std::str::FromStr; -const SET_INDEX_METHOD: &str = "set_index"; const BUILTIN_RECEIVER: &str = "_INKO"; -const TRY_BINDING_VAR: &str = "$error"; #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct IntLiteral { @@ -115,16 +113,6 @@ pub(crate) struct Call { pub(crate) receiver: Option, pub(crate) name: Identifier, pub(crate) arguments: Vec, - pub(crate) else_block: Option, - pub(crate) location: SourceLocation, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct AsyncCall { - pub(crate) info: Option, - pub(crate) receiver: Expression, - pub(crate) name: Identifier, - pub(crate) arguments: Vec, pub(crate) location: SourceLocation, } @@ -133,7 +121,6 @@ pub(crate) struct BuiltinCall { pub(crate) info: Option, pub(crate) name: Identifier, pub(crate) arguments: Vec, - pub(crate) else_block: Option, pub(crate) location: SourceLocation, } @@ -179,7 +166,6 @@ pub(crate) struct AssignSetter { pub(crate) receiver: Expression, pub(crate) name: Identifier, pub(crate) value: Expression, - pub(crate) else_block: Option, pub(crate) location: SourceLocation, } @@ -226,7 +212,6 @@ pub(crate) struct DefineInstanceMethod { pub(crate) name: Identifier, pub(crate) type_parameters: Vec, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) body: Vec, pub(crate) location: SourceLocation, @@ -239,7 +224,6 @@ pub(crate) struct DefineModuleMethod { pub(crate) name: Identifier, pub(crate) type_parameters: Vec, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) body: Vec, pub(crate) location: SourceLocation, @@ -253,7 +237,6 @@ pub(crate) struct DefineRequiredMethod { pub(crate) name: Identifier, pub(crate) type_parameters: Vec, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) method_id: Option, pub(crate) location: SourceLocation, @@ -265,7 +248,6 @@ pub(crate) struct DefineStaticMethod { pub(crate) name: Identifier, pub(crate) type_parameters: Vec, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) body: Vec, pub(crate) location: SourceLocation, @@ -279,7 +261,6 @@ pub(crate) struct DefineAsyncMethod { pub(crate) name: Identifier, pub(crate) type_parameters: Vec, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) body: Vec, pub(crate) location: SourceLocation, @@ -383,6 +364,7 @@ pub(crate) struct ReopenClass { pub(crate) class_id: Option, pub(crate) class_name: Constant, pub(crate) body: Vec, + pub(crate) bounds: Vec, pub(crate) location: SourceLocation, } @@ -397,6 +379,7 @@ pub(crate) enum ReopenClassExpression { pub(crate) struct TypeBound { pub(crate) name: Constant, pub(crate) requirements: Vec, + pub(crate) mutable: bool, pub(crate) location: SourceLocation, } @@ -419,11 +402,11 @@ pub(crate) struct Scope { } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct Index { - pub(crate) info: Option, - pub(crate) receiver: Expression, - pub(crate) index: Expression, +pub(crate) struct Try { + pub(crate) expression: Expression, pub(crate) location: SourceLocation, + pub(crate) kind: types::ThrowKind, + pub(crate) return_type: types::TypeRef, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -435,7 +418,6 @@ pub(crate) enum Expression { AssignSetter(Box), AssignVariable(Box), ReplaceVariable(Box), - AsyncCall(Box), Break(Box), BuiltinCall(Box), Call(Box), @@ -446,10 +428,8 @@ pub(crate) enum Expression { FieldRef(Box), Float(Box), IdentifierRef(Box), - Index(Box), ClassLiteral(Box), Int(Box), - Invalid(Box), Loop(Box), Match(Box), Mut(Box), @@ -466,6 +446,7 @@ pub(crate) enum Expression { Tuple(Box), TypeCast(Box), Recover(Box), + Try(Box), } impl Expression { @@ -478,7 +459,6 @@ impl Expression { Expression::AssignSetter(ref n) => &n.location, Expression::AssignVariable(ref n) => &n.location, Expression::ReplaceVariable(ref n) => &n.location, - Expression::AsyncCall(ref n) => &n.location, Expression::Break(ref n) => &n.location, Expression::BuiltinCall(ref n) => &n.location, Expression::Call(ref n) => &n.location, @@ -489,10 +469,8 @@ impl Expression { Expression::FieldRef(ref n) => &n.location, Expression::Float(ref n) => &n.location, Expression::IdentifierRef(ref n) => &n.location, - Expression::Index(ref n) => &n.location, Expression::ClassLiteral(ref n) => &n.location, Expression::Int(ref n) => &n.location, - Expression::Invalid(ref n) => n, Expression::Loop(ref n) => &n.location, Expression::Match(ref n) => &n.location, Expression::Mut(ref n) => &n.location, @@ -509,6 +487,7 @@ impl Expression { Expression::Tuple(ref n) => &n.location, Expression::TypeCast(ref n) => &n.location, Expression::Recover(ref n) => &n.location, + Expression::Try(ref n) => &n.location, } } @@ -552,6 +531,15 @@ impl ConstExpression { Self::Invalid(ref l) => l, } } + + pub(crate) fn is_simple_literal(&self) -> bool { + matches!( + self, + ConstExpression::Int(_) + | ConstExpression::Float(_) + | ConstExpression::String(_) + ) + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -559,6 +547,7 @@ pub(crate) struct TypeParameter { pub(crate) type_parameter_id: Option, pub(crate) name: Constant, pub(crate) requirements: Vec, + pub(crate) mutable: bool, pub(crate) location: SourceLocation, } @@ -614,7 +603,6 @@ pub(crate) enum ReferrableType { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct ClosureType { pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) location: SourceLocation, pub(crate) resolved_type: types::TypeRef, @@ -741,7 +729,6 @@ pub(crate) struct Closure { pub(crate) resolved_type: types::TypeRef, pub(crate) moving: bool, pub(crate) arguments: Vec, - pub(crate) throw_type: Option, pub(crate) return_type: Option, pub(crate) body: Vec, pub(crate) location: SourceLocation, @@ -846,6 +833,7 @@ pub(crate) struct TypeCast { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct Throw { pub(crate) resolved_type: types::TypeRef, + pub(crate) return_type: types::TypeRef, pub(crate) value: Expression, pub(crate) location: SourceLocation, } @@ -857,13 +845,6 @@ pub(crate) struct Return { pub(crate) location: SourceLocation, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct ElseBlock { - pub(crate) body: Vec, - pub(crate) argument: Option, - pub(crate) location: SourceLocation, -} - #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct TuplePattern { pub(crate) field_ids: Vec, @@ -1088,7 +1069,6 @@ impl<'a> LowerToHir<'a> { type_parameters: self .optional_type_parameters(node.type_parameters), arguments: self.optional_method_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), body: self.optional_expressions(node.body), method_id: None, @@ -1202,7 +1182,6 @@ impl<'a> LowerToHir<'a> { type_parameters: self .optional_type_parameters(node.type_parameters), arguments: self.optional_method_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), body: self.optional_expressions(node.body), method_id: None, @@ -1223,7 +1202,6 @@ impl<'a> LowerToHir<'a> { type_parameters: self .optional_type_parameters(node.type_parameters), arguments: self.optional_method_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), body: self.optional_expressions(node.body), method_id: None, @@ -1235,8 +1213,6 @@ impl<'a> LowerToHir<'a> { &mut self, node: ast::DefineMethod, ) -> DefineInstanceMethod { - self.check_operator_requirements(&node); - DefineInstanceMethod { public: node.public, kind: match node.kind { @@ -1248,7 +1224,6 @@ impl<'a> LowerToHir<'a> { type_parameters: self .optional_type_parameters(node.type_parameters), arguments: self.optional_method_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), body: self.optional_expressions(node.body), method_id: None, @@ -1260,8 +1235,6 @@ impl<'a> LowerToHir<'a> { &mut self, node: ast::DefineMethod, ) -> Box { - self.check_operator_requirements(&node); - Box::new(DefineRequiredMethod { public: node.public, kind: match node.kind { @@ -1273,7 +1246,6 @@ impl<'a> LowerToHir<'a> { type_parameters: self .optional_type_parameters(node.type_parameters), arguments: self.optional_method_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), method_id: None, location: node.location, @@ -1281,7 +1253,7 @@ impl<'a> LowerToHir<'a> { } fn optional_type_bounds( - &self, + &mut self, node: Option, ) -> Vec { if let Some(types) = node { @@ -1291,12 +1263,30 @@ impl<'a> LowerToHir<'a> { } } - fn type_bound(&self, node: ast::TypeBound) -> TypeBound { - TypeBound { - name: self.constant(node.name), - requirements: self.type_names(node.requirements), - location: node.location, + fn type_bound(&mut self, node: ast::TypeBound) -> TypeBound { + let name = self.constant(node.name); + let mut mutable = false; + let mut requirements = Vec::new(); + + for req in node.requirements.values { + match req { + ast::Requirement::Trait(n) => { + requirements.push(self.type_name(n)) + } + ast::Requirement::Mutable(loc) if mutable => { + self.state.diagnostics.type_parameter_already_mutable( + &name.name, + self.file(), + loc, + ); + } + ast::Requirement::Mutable(_) => { + mutable = true; + } + } } + + TypeBound { name, requirements, mutable, location: node.location } } fn define_trait(&mut self, node: ast::DefineTrait) -> TopLevelExpression { @@ -1340,6 +1330,7 @@ impl<'a> LowerToHir<'a> { class_id: None, class_name: self.constant(node.class_name), body: self.reopen_class_expressions(node.body), + bounds: self.optional_type_bounds(node.bounds), location: node.location, })) } @@ -1512,7 +1503,6 @@ impl<'a> LowerToHir<'a> { fn closure_type(&self, node: ast::ClosureType) -> Box { Box::new(ClosureType { arguments: self.optional_types(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), location: node.location, resolved_type: types::TypeRef::Unknown, @@ -1553,9 +1543,36 @@ impl<'a> LowerToHir<'a> { fn type_parameter(&mut self, node: ast::TypeParameter) -> TypeParameter { let name = self.constant(node.name); let location = node.location; - let requirements = self.optional_type_names(node.requirements); + let mut mutable = false; + let mut requirements = Vec::new(); + + if let Some(reqs) = node.requirements { + for req in reqs.values { + match req { + ast::Requirement::Trait(n) => { + requirements.push(self.type_name(n)) + } + ast::Requirement::Mutable(loc) if mutable => { + self.state.diagnostics.type_parameter_already_mutable( + &name.name, + self.file(), + loc, + ); + } + ast::Requirement::Mutable(_) => { + mutable = true; + } + } + } + } - TypeParameter { type_parameter_id: None, name, requirements, location } + TypeParameter { + type_parameter_id: None, + name, + requirements, + location, + mutable, + } } fn optional_type_names( @@ -1717,7 +1734,6 @@ impl<'a> LowerToHir<'a> { location: loc.clone(), }, arguments: Vec::new(), - else_block: None, location: loc, })) } @@ -1884,7 +1900,7 @@ impl<'a> LowerToHir<'a> { Expression::Float(self.float_literal(*node)) } ast::Expression::Binary(node) => { - Expression::Call(self.binary(*node, None)) + Expression::Call(self.binary(*node)) } ast::Expression::Field(node) => { Expression::FieldRef(self.field_ref(*node)) @@ -1896,7 +1912,6 @@ impl<'a> LowerToHir<'a> { Expression::IdentifierRef(self.identifier_ref(*node)) } ast::Expression::Call(node) => self.call(*node), - ast::Expression::Async(node) => self.async_call(*node), ast::Expression::AssignVariable(node) => { Expression::AssignVariable(self.assign_variable(*node)) } @@ -1964,12 +1979,6 @@ impl<'a> LowerToHir<'a> { ast::Expression::TypeCast(node) => { Expression::TypeCast(self.type_cast(*node)) } - ast::Expression::Index(node) => { - Expression::Index(self.index_expression(*node)) - } - ast::Expression::SetIndex(node) => { - Expression::Call(self.set_index_expression(*node)) - } ast::Expression::Throw(node) => { Expression::Throw(self.throw_expression(*node)) } @@ -1977,7 +1986,6 @@ impl<'a> LowerToHir<'a> { Expression::Return(self.return_expression(*node)) } ast::Expression::Try(node) => self.try_expression(*node), - ast::Expression::TryPanic(node) => self.try_panic(*node), ast::Expression::If(node) => { Expression::Match(self.if_expression(*node)) } @@ -2005,11 +2013,7 @@ impl<'a> LowerToHir<'a> { } } - fn binary( - &mut self, - node: ast::Binary, - else_block: Option, - ) -> Box { + fn binary(&mut self, node: ast::Binary) -> Box { let op = self.binary_operator(&node.operator); Box::new(Call { @@ -2022,7 +2026,6 @@ impl<'a> LowerToHir<'a> { arguments: vec![Argument::Positional(Box::new( self.expression(node.right), ))], - else_block, location: node.location, }) } @@ -2067,7 +2070,6 @@ impl<'a> LowerToHir<'a> { info: None, name: self.identifier(node.name), arguments: self.optional_builtin_call_arguments(node.arguments), - else_block: None, location: node.location, })); } @@ -2077,7 +2079,6 @@ impl<'a> LowerToHir<'a> { receiver: node.receiver.map(|n| self.expression(n)), name: self.identifier(node.name), arguments: self.optional_call_arguments(node.arguments), - else_block: None, location: node.location, })) } @@ -2145,46 +2146,6 @@ impl<'a> LowerToHir<'a> { } } - fn async_call(&mut self, async_node: ast::Async) -> Expression { - let result = match self.expression(async_node.expression) { - Expression::Call(node) => { - if let Some(receiver) = node.receiver { - return Expression::AsyncCall(Box::new(AsyncCall { - info: None, - receiver, - name: node.name, - arguments: node.arguments, - location: async_node.location, - })); - } - - Expression::Call(node) - } - Expression::AssignSetter(node) => { - return Expression::AsyncCall(Box::new(AsyncCall { - info: None, - receiver: node.receiver, - name: Identifier { - name: node.name.name + "=", - location: node.name.location, - }, - arguments: vec![Argument::Positional(Box::new(node.value))], - location: node.location, - })); - } - expr => expr, - }; - - self.state.diagnostics.error( - DiagnosticId::InvalidCall, - "The 'async' keyword requires a method call with a receiver", - self.file(), - async_node.location, - ); - - result - } - fn assign_variable( &mut self, node: ast::AssignVariable, @@ -2256,7 +2217,6 @@ impl<'a> LowerToHir<'a> { arguments: vec![Argument::Positional(Box::new( self.expression(node.value), ))], - else_block: None, location: node.location.clone(), })), resolved_type: types::TypeRef::Unknown, @@ -2290,7 +2250,6 @@ impl<'a> LowerToHir<'a> { arguments: vec![Argument::Positional(Box::new( self.expression(node.value), ))], - else_block: None, location: node.location.clone(), })), resolved_type: types::TypeRef::Unknown, @@ -2304,7 +2263,6 @@ impl<'a> LowerToHir<'a> { receiver: self.expression(node.receiver), name: self.identifier(node.name), value: self.expression(node.value), - else_block: None, location: node.location, }) } @@ -2323,7 +2281,6 @@ impl<'a> LowerToHir<'a> { receiver: Some(setter_rec.clone()), name: name.clone(), arguments: Vec::new(), - else_block: None, location: getter_loc, })); @@ -2341,10 +2298,8 @@ impl<'a> LowerToHir<'a> { name: op.method_name().to_string(), location: node.operator.location, }, - else_block: None, location: node.location.clone(), })), - else_block: None, location: node.location, }) } @@ -2355,7 +2310,6 @@ impl<'a> LowerToHir<'a> { resolved_type: types::TypeRef::Unknown, moving: node.moving, arguments: self.optional_block_arguments(node.arguments), - throw_type: node.throw_type.map(|n| self.type_reference(n)), return_type: node.return_type.map(|n| self.type_reference(n)), body: self.expressions(node.body), location: node.location, @@ -2488,35 +2442,10 @@ impl<'a> LowerToHir<'a> { }) } - fn index_expression(&mut self, node: ast::Index) -> Box { - Box::new(Index { - info: None, - receiver: self.expression(node.receiver), - index: self.expression(node.index), - location: node.location, - }) - } - - fn set_index_expression(&mut self, node: ast::SetIndex) -> Box { - Box::new(Call { - kind: types::CallKind::Unknown, - receiver: Some(self.expression(node.receiver)), - name: Identifier { - name: SET_INDEX_METHOD.to_string(), - location: node.location.clone(), - }, - arguments: vec![ - Argument::Positional(Box::new(self.expression(node.index))), - Argument::Positional(Box::new(self.expression(node.value))), - ], - else_block: None, - location: node.location, - }) - } - fn throw_expression(&mut self, node: ast::Throw) -> Box { Box::new(Throw { resolved_type: types::TypeRef::Unknown, + return_type: types::TypeRef::Unknown, value: self.expression(node.value), location: node.location, }) @@ -2532,130 +2461,13 @@ impl<'a> LowerToHir<'a> { }) } - /// Desugars a `try` expression such that it always has an explicit `else`. - /// - /// This desugars this: - /// - /// try x - /// - /// Into this: - /// - /// try x else (error) throw error - /// - /// If an explicit `else` is already present, no desugaring is applied. fn try_expression(&mut self, node: ast::Try) -> Expression { - let else_block = if let Some(else_block) = node.else_block { - let body = self.expressions(else_block.body); - let binding = else_block.argument.map(|n| self.block_argument(n)); - - ElseBlock { body, argument: binding, location: else_block.location } - } else { - let location = node.location.clone(); - let throw = self.throw_variable(TRY_BINDING_VAR, location.clone()); - let binding = self.generated_variable_definition( - TRY_BINDING_VAR, - location.clone(), - ); - - ElseBlock { body: vec![throw], argument: Some(binding), location } - }; - - self.try_else(node.try_block, else_block, node.location) - } - - /// Desugars a `try!` expression into a `try` that panics. - /// - /// Expressions like this: - /// - /// try! x - /// - /// Are desugared into this: - /// - /// try x else (error) _INKO.panic(error.to_string) - fn try_panic(&mut self, node: ast::TryPanic) -> Expression { - let location = node.location.clone(); - let panic = self.hidden_panic(TRY_BINDING_VAR, location.clone()); - let binding = self - .generated_variable_definition(TRY_BINDING_VAR, location.clone()); - let else_block = - ElseBlock { body: vec![panic], argument: Some(binding), location }; - - self.try_else(node.try_block, else_block, node.location) - } - - fn try_else( - &mut self, - try_block: ast::TryBlock, - else_block: ElseBlock, - location: SourceLocation, - ) -> Expression { - match try_block.value { - ast::Expression::Identifier(n) => { - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: self.identifier(*n), - arguments: Vec::new(), - else_block: Some(else_block), - location, - })) - } - ast::Expression::Constant(n) => Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: Identifier { name: n.name, location: n.location }, - arguments: Vec::new(), - else_block: Some(else_block), - location, - })), - ast::Expression::Call(call) => { - if self.is_builtin_call(&call) { - if !self.module.is_std(&self.state.db) { - self.state.diagnostics.invalid_builtin_function( - self.file(), - call.location.clone(), - ); - } - - Expression::BuiltinCall(Box::new(BuiltinCall { - info: None, - name: self.identifier(call.name), - arguments: self - .optional_builtin_call_arguments(call.arguments), - else_block: Some(else_block), - location, - })) - } else { - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: call.receiver.map(|n| self.expression(n)), - name: self.identifier(call.name), - arguments: self.optional_call_arguments(call.arguments), - else_block: Some(else_block), - location, - })) - } - } - ast::Expression::AssignSetter(node) => { - Expression::AssignSetter(Box::new(AssignSetter { - kind: types::CallKind::Unknown, - receiver: self.expression(node.receiver), - name: self.identifier(node.name), - value: self.expression(node.value), - else_block: Some(else_block), - location, - })) - } - ast::Expression::Binary(node) => { - Expression::Call(self.binary(*node, Some(else_block))) - } - _ => { - let loc = try_block.value.location().clone(); - - self.state.diagnostics.never_throws(self.file(), loc.clone()); - Expression::Invalid(Box::new(loc)) - } - } + Expression::Try(Box::new(Try { + expression: self.expression(node.expression), + kind: types::ThrowKind::Unknown, + location: node.location, + return_type: types::TypeRef::Unknown, + })) } fn if_expression(&mut self, node: ast::If) -> Box { @@ -2914,61 +2726,6 @@ impl<'a> LowerToHir<'a> { nodes.into_iter().map(|n| self.pattern(n)).collect() } - fn throw_variable( - &self, - name: &str, - location: SourceLocation, - ) -> Expression { - Expression::Throw(Box::new(Throw { - resolved_type: types::TypeRef::Unknown, - value: Expression::IdentifierRef(Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: name.to_string(), - location: location.clone(), - })), - location, - })) - } - - fn hidden_panic( - &self, - variable: &str, - location: SourceLocation, - ) -> Expression { - Expression::BuiltinCall(Box::new(BuiltinCall { - info: None, - name: Identifier { - name: types::CompilerMacro::PanicThrown.name().to_string(), - location: location.clone(), - }, - arguments: vec![Expression::IdentifierRef(Box::new( - IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: variable.to_string(), - location: location.clone(), - }, - ))], - else_block: None, - location, - })) - } - - fn generated_variable_definition( - &self, - name: &str, - location: SourceLocation, - ) -> BlockArgument { - BlockArgument { - variable_id: None, - name: Identifier { - name: name.to_string(), - location: location.clone(), - }, - value_type: None, - location, - } - } - fn break_expression(&self, location: SourceLocation) -> Expression { Expression::Break(Box::new(Break { location })) } @@ -2989,21 +2746,6 @@ impl<'a> LowerToHir<'a> { location.clone(), ); } - - fn check_operator_requirements(&mut self, node: &ast::DefineMethod) { - if !node.operator { - return; - } - - if let Some(throws) = node.throw_type.as_ref() { - self.state.diagnostics.error( - DiagnosticId::InvalidMethod, - "Operator methods can't throw", - self.file(), - throws.location().clone(), - ); - } - } } #[cfg(test)] @@ -3374,7 +3116,7 @@ mod tests { #[test] fn test_lower_closure_type() { - let hir = lower_type("fn (A) !! B -> C"); + let hir = lower_type("fn (A) -> C"); assert_eq!( hir, @@ -3389,27 +3131,17 @@ mod tests { arguments: Vec::new(), location: cols(13, 13) }))], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "B".to_string(), - location: cols(19, 19) - }, - arguments: Vec::new(), - location: cols(19, 19) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "C".to_string(), - location: cols(24, 24) + location: cols(19, 19) }, arguments: Vec::new(), - location: cols(24, 24) + location: cols(19, 19) }))), - location: cols(9, 24), + location: cols(9, 19), resolved_type: types::TypeRef::Unknown, })) ); @@ -3417,8 +3149,7 @@ mod tests { #[test] fn test_lower_module_method() { - let (hir, diags) = - lower_top_expr("fn foo[A: X](a: B) !! C -> D { 10 }"); + let (hir, diags) = lower_top_expr("fn foo[A: X](a: B) -> D { 10 }"); assert_eq!(diags, 0); assert_eq!( @@ -3445,6 +3176,7 @@ mod tests { arguments: Vec::new(), location: cols(11, 11) }], + mutable: false, location: cols(8, 11) }], arguments: vec![MethodArgument { @@ -3464,33 +3196,23 @@ mod tests { })), location: cols(14, 17) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(23, 23) - }, - arguments: Vec::new(), - location: cols(23, 23) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(28, 28) + location: cols(23, 23) }, arguments: Vec::new(), - location: cols(28, 28) + location: cols(23, 23) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(32, 33) + location: cols(27, 28) }))], method_id: None, - location: cols(1, 35), + location: cols(1, 30), })), ); } @@ -3522,6 +3244,7 @@ mod tests { arguments: Vec::new(), location: cols(12, 12) }], + mutable: false, location: cols(9, 12) }], body: vec![ClassExpression::Field(Box::new(DefineField { @@ -3635,6 +3358,7 @@ mod tests { arguments: Vec::new(), location: cols(20, 20) }], + mutable: false, location: cols(17, 20) }], body: vec![ClassExpression::Field(Box::new(DefineField { @@ -3685,8 +3409,7 @@ mod tests { #[test] fn test_lower_class_with_static_method() { let hir = - lower_top_expr("class A { fn static a[A](b: B) !! C -> D { 10 } }") - .0; + lower_top_expr("class A { fn static a[A](b: B) -> D { 10 } }").0; assert_eq!( hir, @@ -3710,6 +3433,7 @@ mod tests { location: cols(23, 23) }, requirements: Vec::new(), + mutable: false, location: cols(23, 23) }], arguments: vec![MethodArgument { @@ -3729,36 +3453,26 @@ mod tests { })), location: cols(26, 29) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(35, 35) - }, - arguments: Vec::new(), - location: cols(35, 35) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(40, 40) + location: cols(35, 35) }, arguments: Vec::new(), - location: cols(40, 40) + location: cols(35, 35) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(44, 45) + location: cols(39, 40) }))], method_id: None, - location: cols(11, 47), + location: cols(11, 42), } ))], - location: cols(1, 49) + location: cols(1, 44) })), ); } @@ -3766,8 +3480,7 @@ mod tests { #[test] fn test_lower_class_with_async_method() { let hir = - lower_top_expr("class A { fn async a[A](b: B) !! C -> D { 10 } }") - .0; + lower_top_expr("class A { fn async a[A](b: B) -> D { 10 } }").0; assert_eq!( hir, @@ -3792,6 +3505,7 @@ mod tests { location: cols(22, 22) }, requirements: Vec::new(), + mutable: false, location: cols(22, 22) }], arguments: vec![MethodArgument { @@ -3811,44 +3525,33 @@ mod tests { })), location: cols(25, 28) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(34, 34) - }, - arguments: Vec::new(), - location: cols(34, 34) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(39, 39) + location: cols(34, 34) }, arguments: Vec::new(), - location: cols(39, 39) + location: cols(34, 34) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(43, 44) + location: cols(38, 39) }))], method_id: None, - location: cols(11, 46), + location: cols(11, 41), } ))], - location: cols(1, 48) + location: cols(1, 43) })), ); } #[test] fn test_lower_class_with_instance_method() { - let hir = - lower_top_expr("class A { fn a[A](b: B) !! C -> D { 10 } }").0; + let hir = lower_top_expr("class A { fn a[A](b: B) -> D { 10 } }").0; assert_eq!( hir, @@ -3873,6 +3576,7 @@ mod tests { location: cols(16, 16) }, requirements: Vec::new(), + mutable: false, location: cols(16, 16) }], arguments: vec![MethodArgument { @@ -3892,47 +3596,30 @@ mod tests { })), location: cols(19, 22) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(28, 28) - }, - arguments: Vec::new(), - location: cols(28, 28) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(33, 33) + location: cols(28, 28) }, arguments: Vec::new(), - location: cols(33, 33) + location: cols(28, 28) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(37, 38) + location: cols(32, 33) }))], method_id: None, - location: cols(11, 40) + location: cols(11, 35) } ))], - location: cols(1, 42) + location: cols(1, 37) })), ); } - #[test] - fn test_lower_instance_operator_method_with_throw() { - let diags = lower_top_expr("class A { fn + !! A {} }").1; - - assert_eq!(diags, 1); - } - #[test] fn test_lower_static_operator_method() { let diags = lower_top_expr("class A { fn static + {} }").1; @@ -3964,6 +3651,7 @@ mod tests { location: cols(9, 9) }, requirements: Vec::new(), + mutable: false, location: cols(9, 9) }], requirements: vec![TypeName { @@ -4005,7 +3693,7 @@ mod tests { #[test] fn test_lower_trait_with_required_method() { - let hir = lower_top_expr("trait A { fn a[A](b: B) !! C -> D }").0; + let hir = lower_top_expr("trait A { fn a[A](b: B) -> D }").0; assert_eq!( hir, @@ -4030,6 +3718,7 @@ mod tests { location: cols(16, 16) }, requirements: Vec::new(), + mutable: false, location: cols(16, 16) }], arguments: vec![MethodArgument { @@ -4049,31 +3738,21 @@ mod tests { })), location: cols(19, 22) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(28, 28) - }, - arguments: Vec::new(), - location: cols(28, 28) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(33, 33) + location: cols(28, 28) }, arguments: Vec::new(), - location: cols(33, 33) + location: cols(28, 28) }))), method_id: None, - location: cols(11, 33) + location: cols(11, 28) } ))], - location: cols(1, 35) + location: cols(1, 30) })) ); } @@ -4100,7 +3779,6 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, method_id: None, location: cols(11, 19) @@ -4133,7 +3811,6 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, @@ -4147,8 +3824,7 @@ mod tests { #[test] fn test_lower_trait_with_default_method() { - let hir = - lower_top_expr("trait A { fn a[A](b: B) !! C -> D { 10 } }").0; + let hir = lower_top_expr("trait A { fn a[A](b: B) -> D { 10 } }").0; assert_eq!( hir, @@ -4173,6 +3849,7 @@ mod tests { location: cols(16, 16) }, requirements: Vec::new(), + mutable: false, location: cols(16, 16) }], arguments: vec![MethodArgument { @@ -4192,56 +3869,67 @@ mod tests { })), location: cols(19, 22) }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "C".to_string(), - location: cols(28, 28) - }, - arguments: Vec::new(), - location: cols(28, 28) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "D".to_string(), - location: cols(33, 33) + location: cols(28, 28) }, arguments: Vec::new(), - location: cols(33, 33) + location: cols(28, 28) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(37, 38) + location: cols(32, 33) }))], method_id: None, - location: cols(11, 40) + location: cols(11, 35) } ))], - location: cols(1, 42) + location: cols(1, 37) })) ); } #[test] fn test_lower_reopen_empty_class() { - let hir = lower_top_expr("impl A {}").0; - assert_eq!( - hir, + lower_top_expr("impl A {}").0, TopLevelExpression::Reopen(Box::new(ReopenClass { class_id: None, class_name: Constant { name: "A".to_string(), location: cols(6, 6) }, + bounds: Vec::new(), body: Vec::new(), location: cols(1, 9) })) ); + + assert_eq!( + lower_top_expr("impl A if T: mut {}").0, + TopLevelExpression::Reopen(Box::new(ReopenClass { + class_id: None, + class_name: Constant { + name: "A".to_string(), + location: cols(6, 6) + }, + bounds: vec![TypeBound { + name: Constant { + name: "T".to_string(), + location: cols(11, 11) + }, + requirements: Vec::new(), + mutable: true, + location: cols(11, 16), + }], + body: Vec::new(), + location: cols(1, 16) + })) + ); } #[test] @@ -4266,13 +3954,13 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, location: cols(10, 18) } ))], + bounds: Vec::new(), location: cols(1, 20) })) ); @@ -4299,13 +3987,13 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, location: cols(10, 25), } ))], + bounds: Vec::new(), location: cols(1, 27) })) ); @@ -4333,13 +4021,13 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, location: cols(10, 24) } ))], + bounds: Vec::new(), location: cols(1, 26) })) ); @@ -4416,7 +4104,7 @@ mod tests { #[test] fn test_lower_trait_implementation_with_bounds() { - let hir = lower_top_expr("impl A for B if T: X {}").0; + let hir = lower_top_expr("impl A for B if T: X + mut {}").0; assert_eq!( hir, @@ -4449,11 +4137,12 @@ mod tests { }, arguments: Vec::new(), location: cols(20, 20) - }], - location: cols(17, 20) + },], + mutable: true, + location: cols(17, 26) }], body: Vec::new(), - location: cols(1, 23), + location: cols(1, 29), trait_instance: None, class_instance: None, })) @@ -4491,7 +4180,6 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, @@ -4535,7 +4223,6 @@ mod tests { }, type_parameters: Vec::new(), arguments: Vec::new(), - throw_type: None, return_type: None, body: Vec::new(), method_id: None, @@ -4760,7 +4447,6 @@ mod tests { location: cols(11, 12) }, arguments: Vec::new(), - else_block: None, location: cols(11, 12) })), StringValue::Text(Box::new(StringText { @@ -4837,7 +4523,6 @@ mod tests { name: Operator::Add.method_name().to_string(), location: cols(10, 10) }, - else_block: None, location: cols(8, 12) })) ); @@ -4927,7 +4612,6 @@ mod tests { location: cols(10, 11) })) ))], - else_block: None, location: cols(8, 12) })) ); @@ -4959,7 +4643,6 @@ mod tests { })), location: cols(10, 14) }))], - else_block: None, location: cols(8, 15) })) ); @@ -4985,7 +4668,6 @@ mod tests { location: cols(10, 10) }, arguments: Vec::new(), - else_block: None, location: cols(8, 10) })) ); @@ -5008,52 +4690,18 @@ mod tests { resolved_type: types::TypeRef::Unknown, location: cols(18, 19) }))], - else_block: None, location: cols(8, 20) })) ); } #[test] - fn test_lower_try_builtin_call() { - let hir = lower_expr("fn a { try _INKO.foo(10) else 0 }").0; - - assert_eq!( - hir, - Expression::BuiltinCall(Box::new(BuiltinCall { - info: None, - name: Identifier { - name: "foo".to_string(), - location: cols(18, 20) - }, - arguments: vec![Expression::Int(Box::new(IntLiteral { - value: 10, - resolved_type: types::TypeRef::Unknown, - location: cols(22, 23) - }))], - else_block: Some(ElseBlock { - body: vec![Expression::Int(Box::new(IntLiteral { - resolved_type: types::TypeRef::Unknown, - value: 0, - location: cols(31, 31) - }))], - argument: None, - location: cols(26, 31) - }), - location: cols(8, 31) - })) - ); - } - - #[test] - fn test_lower_try_builtin_call_outside_stdlib() { + fn test_lower_builtin_call_outside_stdlib() { let name = ModuleName::new("foo"); - let ast = Parser::new( - "fn a { try _INKO.foo(10) else 0 }".into(), - "test.inko".into(), - ) - .parse() - .expect("Failed to parse the module"); + let ast = + Parser::new("fn a { _INKO.foo(10) }".into(), "test.inko".into()) + .parse() + .expect("Failed to parse the module"); let ast = ParsedModule { ast, name }; let mut state = State::new(Config::new()); @@ -5081,114 +4729,11 @@ mod tests { resolved_type: types::TypeRef::Unknown, location: cols(21, 22) }))], - else_block: None, location: cols(8, 23) })) ); } - #[test] - fn test_lower_async_call() { - let (hir, diags) = lower_expr("fn a { async a.b(10) }"); - - assert_eq!(diags, 0); - assert_eq!( - hir, - Expression::AsyncCall(Box::new(AsyncCall { - info: None, - receiver: Expression::IdentifierRef(Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "a".to_string(), - location: cols(14, 14) - })), - name: Identifier { - name: "b".to_string(), - location: cols(16, 16) - }, - arguments: vec![Argument::Positional(Box::new( - Expression::Int(Box::new(IntLiteral { - value: 10, - resolved_type: types::TypeRef::Unknown, - location: cols(18, 19) - })) - ))], - location: cols(8, 20) - })) - ); - } - - #[test] - fn test_lower_async_setter() { - let (hir, diags) = lower_expr("fn a { async a.b = 10 }"); - - assert_eq!(diags, 0); - assert_eq!( - hir, - Expression::AsyncCall(Box::new(AsyncCall { - info: None, - receiver: Expression::IdentifierRef(Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "a".to_string(), - location: cols(14, 14) - })), - name: Identifier { - name: "b=".to_string(), - location: cols(16, 16) - }, - arguments: vec![Argument::Positional(Box::new( - Expression::Int(Box::new(IntLiteral { - value: 10, - resolved_type: types::TypeRef::Unknown, - location: cols(20, 21) - })) - ))], - location: cols(14, 21) - })) - ); - } - - #[test] - fn test_lower_async_call_without_receiver() { - let (hir, diags) = lower_expr("fn a { async a(10) }"); - - assert_eq!(diags, 1); - assert_eq!( - hir, - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: Identifier { - name: "a".to_string(), - location: cols(14, 14) - }, - arguments: vec![Argument::Positional(Box::new( - Expression::Int(Box::new(IntLiteral { - value: 10, - resolved_type: types::TypeRef::Unknown, - location: cols(16, 17) - })) - ))], - else_block: None, - location: cols(14, 18) - })) - ); - } - - #[test] - fn test_lower_async_call_with_identifier() { - let (hir, diags) = lower_expr("fn a { async a }"); - - assert_eq!(diags, 1); - assert_eq!( - hir, - Expression::IdentifierRef(Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "a".to_string(), - location: cols(14, 14) - })) - ); - } - #[test] fn test_lower_assign_variable() { let hir = lower_expr("fn a { a = 10 }").0; @@ -5307,7 +4852,6 @@ mod tests { name: Operator::Add.method_name().to_string(), location: cols(10, 11) }, - else_block: None, location: cols(8, 13) })), resolved_type: types::TypeRef::Unknown, @@ -5338,7 +4882,6 @@ mod tests { value: 1, location: cols(14, 14) })), - else_block: None, location: cols(8, 14) })) ); @@ -5377,7 +4920,6 @@ mod tests { location: cols(10, 10) }, arguments: Vec::new(), - else_block: None, location: cols(8, 10) }))), arguments: vec![Argument::Positional(Box::new( @@ -5391,10 +4933,8 @@ mod tests { name: Operator::Add.method_name().to_string(), location: cols(12, 13) }, - else_block: None, location: cols(8, 15) })), - else_block: None, location: cols(8, 15) })) ); @@ -5428,7 +4968,6 @@ mod tests { name: Operator::Add.method_name().to_string(), location: cols(11, 12) }, - else_block: None, location: cols(8, 14) })), resolved_type: types::TypeRef::Unknown, @@ -5439,7 +4978,7 @@ mod tests { #[test] fn test_lower_closure() { - let hir = lower_expr("fn a { fn (a: T) !! A -> B { 10 } }").0; + let hir = lower_expr("fn a { fn (a: T) -> B { 10 } }").0; assert_eq!( hir, @@ -5465,32 +5004,22 @@ mod tests { }))), location: cols(12, 15), }], - throw_type: Some(Type::Named(Box::new(TypeName { - source: None, - resolved_type: types::TypeRef::Unknown, - name: Constant { - name: "A".to_string(), - location: cols(21, 21) - }, - arguments: Vec::new(), - location: cols(21, 21) - }))), return_type: Some(Type::Named(Box::new(TypeName { source: None, resolved_type: types::TypeRef::Unknown, name: Constant { name: "B".to_string(), - location: cols(26, 26) + location: cols(21, 21) }, arguments: Vec::new(), - location: cols(26, 26) + location: cols(21, 21) }))), body: vec![Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, - location: cols(30, 31) + location: cols(25, 26) }))], - location: cols(8, 33) + location: cols(8, 28) })) ); } @@ -5743,70 +5272,6 @@ mod tests { ); } - #[test] - fn test_lower_index_expression() { - let hir = lower_expr("fn a { a[0] }").0; - - assert_eq!( - hir, - Expression::Index(Box::new(Index { - info: None, - receiver: Expression::IdentifierRef(Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "a".to_string(), - location: cols(8, 8) - })), - index: Expression::Int(Box::new(IntLiteral { - value: 0, - resolved_type: types::TypeRef::Unknown, - location: cols(10, 10) - })), - location: cols(8, 11) - })) - ); - } - - #[test] - fn test_lower_set_index_expression() { - let hir = lower_expr("fn a { a[0] = 1 }").0; - - assert_eq!( - hir, - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: Some(Expression::IdentifierRef(Box::new( - IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "a".to_string(), - location: cols(8, 8) - } - ))), - name: Identifier { - name: SET_INDEX_METHOD.to_string(), - location: cols(8, 15) - }, - arguments: vec![ - Argument::Positional(Box::new(Expression::Int(Box::new( - IntLiteral { - value: 0, - resolved_type: types::TypeRef::Unknown, - location: cols(10, 10) - } - )))), - Argument::Positional(Box::new(Expression::Int(Box::new( - IntLiteral { - value: 1, - resolved_type: types::TypeRef::Unknown, - location: cols(15, 15) - } - )))), - ], - else_block: None, - location: cols(8, 15) - })) - ); - } - #[test] fn test_lower_throw_expression() { let hir = lower_expr("fn a { throw 10 }").0; @@ -5815,6 +5280,7 @@ mod tests { hir, Expression::Throw(Box::new(Throw { resolved_type: types::TypeRef::Unknown, + return_type: types::TypeRef::Unknown, value: Expression::Int(Box::new(IntLiteral { value: 10, resolved_type: types::TypeRef::Unknown, @@ -5858,135 +5324,24 @@ mod tests { } #[test] - fn test_lower_try_without_else() { - let hir = lower_expr("fn a { try a }").0; - - assert_eq!( - hir, - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: Identifier { - name: "a".to_string(), - location: cols(12, 12) - }, - arguments: Vec::new(), - else_block: Some(ElseBlock { - body: vec![Expression::Throw(Box::new(Throw { - resolved_type: types::TypeRef::Unknown, - value: Expression::IdentifierRef(Box::new( - IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: TRY_BINDING_VAR.to_string(), - location: cols(8, 12) - } - )), - location: cols(8, 12) - }))], - argument: Some(BlockArgument { - variable_id: None, - name: Identifier { - name: TRY_BINDING_VAR.to_string(), - location: cols(8, 12) - }, - value_type: None, - location: cols(8, 12) - }), - location: cols(8, 12) - }), - location: cols(8, 12) - })) - ); - } - - #[test] - fn test_lower_try_with_else() { - let hir = lower_expr("fn a { try aa else (e) throw e }").0; + fn test_lower_try() { + let hir = lower_expr("fn a { try a() }").0; assert_eq!( hir, - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: Identifier { - name: "aa".to_string(), - location: cols(12, 13) - }, - arguments: Vec::new(), - else_block: Some(ElseBlock { - body: vec![Expression::Throw(Box::new(Throw { - resolved_type: types::TypeRef::Unknown, - value: Expression::IdentifierRef(Box::new( - IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: "e".to_string(), - location: cols(30, 30) - } - )), - location: cols(24, 30) - }))], - argument: Some(BlockArgument { - variable_id: None, - name: Identifier { - name: "e".to_string(), - location: cols(21, 21) - }, - value_type: None, - location: cols(21, 21) - }), - location: cols(15, 30) - }), - location: cols(8, 30) - })) - ); - } - - #[test] - fn test_lower_try_panic() { - let hir = lower_expr("fn a { try! aa }").0; - - assert_eq!( - hir, - Expression::Call(Box::new(Call { - kind: types::CallKind::Unknown, - receiver: None, - name: Identifier { - name: "aa".to_string(), - location: cols(13, 14) - }, - arguments: Vec::new(), - else_block: Some(ElseBlock { - body: vec![Expression::BuiltinCall(Box::new( - BuiltinCall { - info: None, - name: Identifier { - name: types::CompilerMacro::PanicThrown - .name() - .to_string(), - location: cols(8, 14) - }, - arguments: vec![Expression::IdentifierRef( - Box::new(IdentifierRef { - kind: types::IdentifierKind::Unknown, - name: TRY_BINDING_VAR.to_string(), - location: cols(8, 14) - }) - )], - else_block: None, - location: cols(8, 14) - } - ))], - argument: Some(BlockArgument { - variable_id: None, - name: Identifier { - name: TRY_BINDING_VAR.to_string(), - location: cols(8, 14) - }, - value_type: None, - location: cols(8, 14) - }), - location: cols(8, 14) - }), + Expression::Try(Box::new(Try { + expression: Expression::Call(Box::new(Call { + kind: types::CallKind::Unknown, + receiver: None, + name: Identifier { + name: "a".to_string(), + location: cols(12, 12) + }, + arguments: Vec::new(), + location: cols(12, 14) + })), + kind: types::ThrowKind::Unknown, + return_type: types::TypeRef::Unknown, location: cols(8, 14) })) ); @@ -6234,6 +5589,7 @@ mod tests { location: cols(19, 19) }, requirements: Vec::new(), + mutable: false, location: cols(19, 19) }], body: vec![ diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index fb7e84a64..0a7b77c90 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -1,14 +1,17 @@ #![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::enum_variant_names))] -mod codegen; mod diagnostics; mod hir; +mod linker; +mod llvm; mod mir; mod modules_parser; mod presenters; mod source_paths; mod state; +mod symbol_names; +pub mod target; mod type_check; #[cfg(test)] diff --git a/compiler/src/linker.rs b/compiler/src/linker.rs new file mode 100644 index 000000000..e1cde5729 --- /dev/null +++ b/compiler/src/linker.rs @@ -0,0 +1,108 @@ +use crate::config::Config; +use crate::target::OperatingSystem; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +fn runtime_library(config: &Config) -> Option { + let mut files = vec![format!("libinko-{}.a", &config.target)]; + + // When compiling for the native target we also support DIR/libinko.a, as + // this makes development of Inko easier by just using e.g. `./target/debug` + // as the search directory. + if config.target.is_native() { + files.push("libinko.a".to_string()); + } + + files.iter().find_map(|file| { + let path = config.runtime.join(file); + + if path.is_file() { + Some(path) + } else { + None + } + }) +} + +fn lld_is_available() -> bool { + Command::new("ld.lld") + .arg("--version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .and_then(|mut child| child.wait()) + .map_or(false, |status| status.success()) +} + +pub(crate) fn link( + config: &Config, + output: &Path, + paths: &[PathBuf], +) -> Result<(), String> { + // On Unix systems the necessary libraries/object files are all over the + // place. Instead of re-implementing the logic necessary to find these + // files, we rely on the system's compiler to do this for us. + // + // As we only use this executable for linking it doesn't really matter + // if this ends up using gcc, clang or something else, because we only + // use it as a wrapper around the linker executable. + let mut cmd = Command::new("cc"); + + // Object files must come before any of the libraries to link against, as + // certain linkers are very particular about the order of flags such as + // `-l`. + for path in paths { + cmd.arg(path); + } + + match config.target.os { + OperatingSystem::Linux + | OperatingSystem::Freebsd + | OperatingSystem::Openbsd => { + // macOS includes libm in the standard C library, so there's no need + // to explicitly include it. + cmd.arg("-lm"); + } + _ => {} + } + + cmd.arg("-o"); + cmd.arg(output); + + // On platforms where lld isn't the default (e.g. FreeBSD), we'll use it if + // available, speeding up the linking process. + if let OperatingSystem::Linux = config.target.os { + if lld_is_available() { + cmd.arg("-fuse-ld=lld"); + } + } + + let rt_path = runtime_library(config).ok_or_else(|| { + format!("No runtime is available for target '{}'", config.target) + })?; + + cmd.arg(&rt_path); + + cmd.stdin(Stdio::null()); + cmd.stderr(Stdio::piped()); + cmd.stdout(Stdio::null()); + + let child = cmd + .spawn() + .map_err(|err| format!("Failed to start the linker: {err}"))?; + + let output = child + .wait_with_output() + .map_err(|err| format!("Failed to wait for the linker: {err}"))?; + + if output.status.success() { + Ok(()) + } else { + Err(format!( + "The linker exited with status code {}:\n{}", + output.status.code().unwrap_or(0), + String::from_utf8_lossy(&output.stderr), + )) + } +} diff --git a/compiler/src/llvm.rs b/compiler/src/llvm.rs new file mode 100644 index 000000000..fda7f1bd5 --- /dev/null +++ b/compiler/src/llvm.rs @@ -0,0 +1,10 @@ +//! Lowering of Inko MIR into LLVM IR. + +pub(crate) mod builder; +pub(crate) mod constants; +pub(crate) mod context; +pub(crate) mod layouts; +pub(crate) mod method_hasher; +pub(crate) mod module; +pub(crate) mod passes; +pub(crate) mod runtime_function; diff --git a/compiler/src/llvm/builder.rs b/compiler/src/llvm/builder.rs new file mode 100644 index 000000000..b86ae833b --- /dev/null +++ b/compiler/src/llvm/builder.rs @@ -0,0 +1,775 @@ +use crate::llvm::constants::{ + INT_MASK, INT_SHIFT, MAX_INT, MIN_INT, UNTAG_MASK, +}; +use crate::llvm::context::Context; +use inkwell::basic_block::BasicBlock; +use inkwell::builder; +use inkwell::debug_info::{ + debug_metadata_version, AsDIScope, DICompileUnit, DIFlags, + DIFlagsConstants, DILocation, DIScope, DISubprogram, DWARFEmissionKind, + DWARFSourceLanguage, DebugInfoBuilder, +}; +use inkwell::module::{FlagBehavior, Module as InkwellModule}; +use inkwell::types::{ArrayType, BasicType, FunctionType, StructType}; +use inkwell::values::{ + AggregateValue, ArrayValue, BasicMetadataValueEnum, BasicValue, + BasicValueEnum, CallSiteValue, FloatValue, FunctionValue, + InstructionOpcode, InstructionValue, IntValue, PointerValue, +}; +use inkwell::{ + AddressSpace, AtomicOrdering, AtomicRMWBinOp, FloatPredicate, IntPredicate, +}; +use std::path::Path; + +/// A wrapper around an LLVM Builder that provides some additional methods. +pub(crate) struct Builder<'ctx> { + inner: builder::Builder<'ctx>, + pub(crate) function: FunctionValue<'ctx>, + pub(crate) context: &'ctx Context, +} + +impl<'ctx> Builder<'ctx> { + pub(crate) fn new( + context: &'ctx Context, + function: FunctionValue<'ctx>, + ) -> Self { + Self { inner: context.create_builder(), context, function } + } + + pub(crate) fn argument(&self, index: u32) -> BasicValueEnum<'ctx> { + self.function.get_nth_param(index).unwrap() + } + + pub(crate) fn arguments( + &self, + ) -> impl Iterator> { + self.function.get_param_iter() + } + + pub(crate) fn extract_field>( + &self, + receiver: R, + index: u32, + ) -> BasicValueEnum<'ctx> { + self.inner.build_extract_value(receiver, index, "").unwrap() + } + + pub(crate) fn load_field( + &self, + receiver_type: StructType<'ctx>, + receiver: PointerValue<'ctx>, + index: u32, + ) -> BasicValueEnum<'ctx> { + let vtype = receiver_type.get_field_type_at_index(index).unwrap(); + let field_ptr = self.field_address(receiver_type, receiver, index); + + self.inner.build_load(vtype, field_ptr, "") + } + + pub(crate) fn field_address( + &self, + receiver_type: StructType<'ctx>, + receiver: PointerValue<'ctx>, + index: u32, + ) -> PointerValue<'ctx> { + self.inner.build_struct_gep(receiver_type, receiver, index, "").unwrap() + } + + pub(crate) fn array_field_index_address( + &self, + receiver_type: StructType<'ctx>, + receiver: PointerValue<'ctx>, + field: u32, + index: IntValue<'ctx>, + ) -> PointerValue<'ctx> { + if !receiver_type + .get_field_type_at_index(field) + .map_or(false, |v| v.is_array_type()) + { + // In practise we'll never reach this point, but the check exists + // anyway to ensure this method doesn't segfault the compiler due to + // an invalid `getelementptr` instruction. + panic!("The field doesn't point to an array"); + } + + unsafe { + self.inner.build_gep( + receiver_type, + receiver, + &[self.u32_literal(0), self.u32_literal(field), index], + "", + ) + } + } + + pub(crate) fn load_array_index( + &self, + array_type: ArrayType<'ctx>, + array: PointerValue<'ctx>, + index: usize, + ) -> BasicValueEnum<'ctx> { + let ptr = unsafe { + self.inner.build_gep( + array_type, + array, + &[ + self.context.i32_type().const_int(0, false), + self.context.i32_type().const_int(index as _, false), + ], + "", + ) + }; + + self.inner.build_load(array_type.get_element_type(), ptr, "") + } + + pub(crate) fn store_array_field>( + &self, + array_type: ArrayType<'ctx>, + array: PointerValue<'ctx>, + index: u32, + value: V, + ) { + let ptr = unsafe { + self.inner.build_gep( + array_type, + array, + &[ + self.context.i32_type().const_int(0, false), + self.context.i32_type().const_int(index as _, false), + ], + "", + ) + }; + + self.store(ptr, value); + } + + pub(crate) fn store_field>( + &self, + receiver_type: StructType<'ctx>, + receiver: PointerValue<'ctx>, + index: u32, + value: V, + ) { + let field_ptr = self.field_address(receiver_type, receiver, index); + + self.store(field_ptr, value); + } + + pub(crate) fn store>( + &self, + variable: PointerValue<'ctx>, + value: V, + ) { + self.inner.build_store(variable, value); + } + + pub(crate) fn load>( + &self, + typ: T, + variable: PointerValue<'ctx>, + ) -> BasicValueEnum<'ctx> { + self.inner.build_load(typ, variable, "") + } + + pub(crate) fn load_untyped_pointer( + &self, + variable: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + self.load( + self.context.i8_type().ptr_type(AddressSpace::default()), + variable, + ) + .into_pointer_value() + } + + pub(crate) fn load_pointer>( + &self, + typ: T, + variable: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + self.load( + typ.as_basic_type_enum().ptr_type(AddressSpace::default()), + variable, + ) + .into_pointer_value() + } + + pub(crate) fn load_function_pointer( + &self, + typ: FunctionType<'ctx>, + variable: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + self.load(typ.ptr_type(AddressSpace::default()), variable) + .into_pointer_value() + } + + pub(crate) fn call( + &self, + function: FunctionValue<'ctx>, + arguments: &[BasicMetadataValueEnum<'ctx>], + ) -> BasicValueEnum<'ctx> { + self.inner + .build_call(function, arguments, "") + .try_as_basic_value() + .left() + .unwrap() + } + + pub(crate) fn indirect_call( + &self, + typ: FunctionType<'ctx>, + func: PointerValue<'ctx>, + args: &[BasicMetadataValueEnum<'ctx>], + ) -> CallSiteValue<'ctx> { + self.inner.build_indirect_call(typ, func, args, "") + } + + pub(crate) fn call_void( + &self, + function: FunctionValue<'ctx>, + arguments: &[BasicMetadataValueEnum<'ctx>], + ) { + self.inner.build_call(function, arguments, ""); + } + + pub(crate) fn pointer_to_int( + &self, + value: PointerValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_ptr_to_int(value, self.context.i64_type(), "") + } + + pub(crate) fn u8_literal(&self, value: u8) -> IntValue<'ctx> { + self.context.i8_type().const_int(value as u64, false) + } + + pub(crate) fn i64_literal(&self, value: i64) -> IntValue<'ctx> { + self.u64_literal(value as u64) + } + + pub(crate) fn u16_literal(&self, value: u16) -> IntValue<'ctx> { + self.context.i16_type().const_int(value as u64, false) + } + + pub(crate) fn u32_literal(&self, value: u32) -> IntValue<'ctx> { + self.context.i32_type().const_int(value as u64, false) + } + + pub(crate) fn u64_literal(&self, value: u64) -> IntValue<'ctx> { + self.context.i64_type().const_int(value, false) + } + + pub(crate) fn f64_literal(&self, value: f64) -> FloatValue<'ctx> { + self.context.f64_type().const_float(value) + } + + pub(crate) fn string_literal( + &self, + value: &str, + ) -> (PointerValue<'ctx>, IntValue<'ctx>) { + let string = + self.inner.build_global_string_ptr(value, "").as_pointer_value(); + let len = self.u64_literal(value.len() as _); + + (string, len) + } + + pub(crate) fn atomic_add( + &self, + pointer: PointerValue<'ctx>, + value: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner + .build_atomicrmw( + AtomicRMWBinOp::Add, + pointer, + value, + AtomicOrdering::AcquireRelease, + ) + .unwrap() + } + + pub(crate) fn atomic_sub( + &self, + pointer: PointerValue<'ctx>, + value: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner + .build_atomicrmw( + AtomicRMWBinOp::Sub, + pointer, + value, + AtomicOrdering::AcquireRelease, + ) + .unwrap() + } + + pub(crate) fn int_eq( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_compare(IntPredicate::EQ, lhs, rhs, "") + } + + pub(crate) fn int_gt( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_compare(IntPredicate::SGT, lhs, rhs, "") + } + + pub(crate) fn int_ge( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_compare(IntPredicate::SGE, lhs, rhs, "") + } + + pub(crate) fn int_lt( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_compare(IntPredicate::SLT, lhs, rhs, "") + } + + pub(crate) fn int_le( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_compare(IntPredicate::SLE, lhs, rhs, "") + } + + pub(crate) fn int_sub( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_sub(lhs, rhs, "") + } + + pub(crate) fn int_add( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_add(lhs, rhs, "") + } + + pub(crate) fn int_mul( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_mul(lhs, rhs, "") + } + + pub(crate) fn int_div( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_signed_div(lhs, rhs, "") + } + + pub(crate) fn int_rem( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_int_signed_rem(lhs, rhs, "") + } + + pub(crate) fn bit_and( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_and(lhs, rhs, "") + } + + pub(crate) fn bit_or( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_or(lhs, rhs, "") + } + + pub(crate) fn bit_xor( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_xor(lhs, rhs, "") + } + + pub(crate) fn bit_not(&self, value: IntValue<'ctx>) -> IntValue<'ctx> { + self.inner.build_not(value, "") + } + + pub(crate) fn left_shift( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_left_shift(lhs, rhs, "") + } + + pub(crate) fn right_shift( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_right_shift(lhs, rhs, false, "") + } + + pub(crate) fn signed_right_shift( + &self, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_right_shift(lhs, rhs, true, "") + } + + pub(crate) fn int_to_float( + &self, + value: IntValue<'ctx>, + ) -> FloatValue<'ctx> { + let typ = self.context.f64_type(); + + self.inner + .build_cast(InstructionOpcode::SIToFP, value, typ, "") + .into_float_value() + } + + pub(crate) fn int_to_int(&self, value: IntValue<'ctx>) -> IntValue<'ctx> { + self.inner.build_int_cast(value, self.context.i64_type(), "") + } + + pub(crate) fn int_to_pointer( + &self, + value: IntValue<'ctx>, + ) -> PointerValue<'ctx> { + self.inner.build_int_to_ptr(value, self.context.pointer_type(), "") + } + + pub(crate) fn float_add( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> FloatValue<'ctx> { + self.inner.build_float_add(lhs, rhs, "") + } + + pub(crate) fn float_sub( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> FloatValue<'ctx> { + self.inner.build_float_sub(lhs, rhs, "") + } + + pub(crate) fn float_div( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> FloatValue<'ctx> { + self.inner.build_float_div(lhs, rhs, "") + } + + pub(crate) fn float_mul( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> FloatValue<'ctx> { + self.inner.build_float_mul(lhs, rhs, "") + } + + pub(crate) fn float_rem( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> FloatValue<'ctx> { + self.inner.build_float_rem(lhs, rhs, "") + } + + pub(crate) fn float_eq( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::OEQ, lhs, rhs, "") + } + + pub(crate) fn float_lt( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::OLT, lhs, rhs, "") + } + + pub(crate) fn float_le( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::OLE, lhs, rhs, "") + } + + pub(crate) fn float_gt( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::OGT, lhs, rhs, "") + } + + pub(crate) fn float_ge( + &self, + lhs: FloatValue<'ctx>, + rhs: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::OGE, lhs, rhs, "") + } + + pub(crate) fn float_is_nan( + &self, + value: FloatValue<'ctx>, + ) -> IntValue<'ctx> { + self.inner.build_float_compare(FloatPredicate::UNO, value, value, "") + } + + pub(crate) fn tagged_int(&self, value: i64) -> Option> { + if (MIN_INT..=MAX_INT).contains(&value) { + let addr = (value << INT_SHIFT) as u64 | (INT_MASK as u64); + let int = self.i64_literal(addr as i64); + + Some(int.const_to_pointer(self.context.pointer_type())) + } else { + None + } + } + + pub(crate) fn bitcast, T: BasicType<'ctx>>( + &self, + value: V, + typ: T, + ) -> BasicValueEnum<'ctx> { + self.inner.build_bitcast(value, typ, "") + } + + pub(crate) fn untagged( + &self, + pointer: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + let tagged_addr = self.pointer_to_int(pointer); + let mask = self.u64_literal(UNTAG_MASK); + let addr = self.bit_and(tagged_addr, mask); + + self.int_to_pointer(addr) + } + + pub(crate) fn before_instruction( + &self, + instruction: InstructionValue<'ctx>, + ) { + self.inner.position_before(&instruction); + } + + pub(crate) fn first_block(&self) -> BasicBlock<'ctx> { + self.function.get_first_basic_block().unwrap() + } + + pub(crate) fn add_block(&self) -> BasicBlock<'ctx> { + self.context.append_basic_block(self.function) + } + + pub(crate) fn switch_to_block(&self, block: BasicBlock<'ctx>) { + self.inner.position_at_end(block); + } + + pub(crate) fn alloca>( + &self, + typ: T, + ) -> PointerValue<'ctx> { + self.inner.build_alloca(typ, "") + } + + pub(crate) fn jump(&self, block: BasicBlock<'ctx>) { + self.inner.build_unconditional_branch(block); + } + + pub(crate) fn return_value(&self, val: Option<&dyn BasicValue<'ctx>>) { + self.inner.build_return(val); + } + + pub(crate) fn branch( + &self, + condition: IntValue<'ctx>, + true_block: BasicBlock<'ctx>, + false_block: BasicBlock<'ctx>, + ) { + self.inner.build_conditional_branch(condition, true_block, false_block); + } + + pub(crate) fn switch( + &self, + value: IntValue<'ctx>, + cases: &[(IntValue<'ctx>, BasicBlock<'ctx>)], + fallback: BasicBlock<'ctx>, + ) { + self.inner.build_switch(value, fallback, cases); + } + + pub(crate) fn exhaustive_switch( + &self, + value: IntValue<'ctx>, + cases: &[(IntValue<'ctx>, BasicBlock<'ctx>)], + ) { + self.switch(value, cases, cases[0].1); + } + + pub(crate) fn unreachable(&self) { + self.inner.build_unreachable(); + } + + pub(crate) fn string_bytes(&self, value: &str) -> ArrayValue<'ctx> { + let bytes = value + .bytes() + .map(|v| self.context.i8_type().const_int(v as _, false)) + .collect::>(); + + self.context.i8_type().const_array(&bytes) + } + + pub(crate) fn new_stack_slot>( + &self, + value_type: T, + ) -> PointerValue<'ctx> { + let builder = Builder::new(self.context, self.function); + let block = self.first_block(); + + if let Some(ins) = block.get_first_instruction() { + builder.before_instruction(ins); + } else { + builder.switch_to_block(block); + } + + builder.alloca(value_type) + } + + pub(crate) fn debug_scope(&self) -> DIScope<'ctx> { + self.function.get_subprogram().unwrap().as_debug_info_scope() + } + + pub(crate) fn set_debug_location(&self, location: DILocation<'ctx>) { + self.inner.set_current_debug_location(location); + } + + pub(crate) fn set_debug_function(&self, function: DISubprogram) { + self.function.set_subprogram(function); + } +} + +/// A wrapper around the LLVM types used for building debugging information. +pub(crate) struct DebugBuilder<'ctx> { + inner: DebugInfoBuilder<'ctx>, + unit: DICompileUnit<'ctx>, + context: &'ctx Context, +} + +impl<'ctx> DebugBuilder<'ctx> { + pub(crate) fn new( + module: &InkwellModule<'ctx>, + context: &'ctx Context, + path: &Path, + ) -> DebugBuilder<'ctx> { + let version = + context.i32_type().const_int(debug_metadata_version() as _, false); + + module.add_basic_value_flag( + "Debug Info Version", + FlagBehavior::Warning, + version, + ); + + let file_name = + path.file_name().and_then(|p| p.to_str()).unwrap_or("unknown"); + let dir_name = + path.parent().and_then(|p| p.to_str()).unwrap_or("unknown"); + let (inner, unit) = module.create_debug_info_builder( + true, + DWARFSourceLanguage::C, + file_name, + dir_name, + "Inko", + false, + "", + 0, + "", + DWARFEmissionKind::Full, + 0, + false, + false, + "", + "", + ); + + DebugBuilder { inner, context, unit } + } + + pub(crate) fn new_location( + &self, + line: usize, + column: usize, + scope: DIScope<'ctx>, + ) -> DILocation<'ctx> { + self.inner.create_debug_location( + &self.context.inner, + line as u32, + column as u32, + scope, + None, + ) + } + + pub(crate) fn new_function( + &self, + name: &str, + mangled_name: &str, + line: usize, + private: bool, + optimised: bool, + ) -> DISubprogram<'ctx> { + let file = self.unit.get_file(); + let typ = + self.inner.create_subroutine_type(file, None, &[], DIFlags::PUBLIC); + let scope = self.unit.as_debug_info_scope(); + + self.inner.create_function( + scope, + name, + Some(mangled_name), + file, + line as u32, + typ, + private, + true, + line as u32, + DIFlags::PUBLIC, + optimised, + ) + } + + pub(crate) fn finalize(&self) { + self.inner.finalize(); + } +} diff --git a/compiler/src/llvm/constants.rs b/compiler/src/llvm/constants.rs new file mode 100644 index 000000000..c1a04934b --- /dev/null +++ b/compiler/src/llvm/constants.rs @@ -0,0 +1,74 @@ +/// The mask to use for tagged integers. +pub(crate) const INT_MASK: i64 = 0b001; + +/// The number of bits to shift for tagged integers. +pub(crate) const INT_SHIFT: usize = 1; + +/// The minimum integer value that can be stored as a tagged signed integer. +pub(crate) const MIN_INT: i64 = i64::MIN >> INT_SHIFT; + +/// The maximum integer value that can be stored as a tagged signed integer. +pub(crate) const MAX_INT: i64 = i64::MAX >> INT_SHIFT; + +/// The mask to use to check if a value is a tagged integer or reference. +pub(crate) const TAG_MASK: i64 = 0b11; + +/// The mask to apply to get rid of the tagging bits. +pub(crate) const UNTAG_MASK: u64 = (!TAG_MASK) as u64; + +/// The offset to apply to access a regular field. +/// +/// The object header occupies the first field (as an inline struct), so all +/// user-defined fields start at the next field. +pub(crate) const FIELD_OFFSET: usize = 1; + +/// The offset to apply to access a process field. +pub(crate) const PROCESS_FIELD_OFFSET: usize = 2; + +/// The mask to use for checking if a value is a reference. +pub(crate) const REF_MASK: i64 = 0b10; + +/// The field index of the `State` field that contains the `true` singleton. +pub(crate) const TRUE_INDEX: u32 = 0; + +/// The field index of the `State` field that contains the `false` singleton. +pub(crate) const FALSE_INDEX: u32 = 1; + +/// The field index of the `State` field that contains the `nil` singleton. +pub(crate) const NIL_INDEX: u32 = 2; + +pub(crate) const HEADER_CLASS_INDEX: u32 = 0; +pub(crate) const HEADER_KIND_INDEX: u32 = 1; +pub(crate) const HEADER_REFS_INDEX: u32 = 2; + +pub(crate) const BOXED_INT_VALUE_INDEX: u32 = 1; +pub(crate) const BOXED_FLOAT_VALUE_INDEX: u32 = 1; + +pub(crate) const CLASS_METHODS_COUNT_INDEX: u32 = 2; +pub(crate) const CLASS_METHODS_INDEX: u32 = 3; + +pub(crate) const METHOD_HASH_INDEX: u32 = 0; +pub(crate) const METHOD_FUNCTION_INDEX: u32 = 1; + +// The values used to represent the kind of a value/reference. These values +// must match the values used by `Kind` in the runtime library. +pub(crate) const OWNED_KIND: u8 = 0; +pub(crate) const REF_KIND: u8 = 1; +pub(crate) const ATOMIC_KIND: u8 = 2; +pub(crate) const PERMANENT_KIND: u8 = 3; +pub(crate) const INT_KIND: u8 = 4; +pub(crate) const FLOAT_KIND: u8 = 5; + +pub(crate) const LLVM_RESULT_VALUE_INDEX: u32 = 0; +pub(crate) const LLVM_RESULT_STATUS_INDEX: u32 = 1; + +pub(crate) const CONTEXT_STATE_INDEX: u32 = 0; +pub(crate) const CONTEXT_PROCESS_INDEX: u32 = 1; +pub(crate) const CONTEXT_ARGS_INDEX: u32 = 2; + +pub(crate) const MESSAGE_ARGUMENTS_INDEX: u32 = 2; + +pub(crate) const DROPPER_INDEX: u32 = 0; +pub(crate) const CLOSURE_CALL_INDEX: u32 = 1; +pub(crate) const HASH_KEY0_INDEX: u32 = 11; +pub(crate) const HASH_KEY1_INDEX: u32 = 12; diff --git a/compiler/src/llvm/context.rs b/compiler/src/llvm/context.rs new file mode 100644 index 000000000..886016647 --- /dev/null +++ b/compiler/src/llvm/context.rs @@ -0,0 +1,146 @@ +use inkwell::basic_block::BasicBlock; +use inkwell::builder::Builder; +use inkwell::module::Module; +use inkwell::types::{ + ArrayType, BasicTypeEnum, FloatType, IntType, PointerType, StructType, + VoidType, +}; +use inkwell::values::FunctionValue; +use inkwell::{context, AddressSpace}; +use std::mem::size_of; + +/// A wrapper around an LLVM Context that provides some additional methods. +pub(crate) struct Context { + pub(crate) inner: context::Context, +} + +impl Context { + pub(crate) fn new() -> Self { + Self { inner: context::Context::create() } + } + + pub(crate) fn pointer_type(&self) -> PointerType<'_> { + self.inner.i8_type().ptr_type(AddressSpace::default()) + } + + pub(crate) fn bool_type(&self) -> IntType { + self.inner.bool_type() + } + + pub(crate) fn i8_type(&self) -> IntType { + self.inner.i8_type() + } + + pub(crate) fn i16_type(&self) -> IntType { + self.inner.i16_type() + } + + pub(crate) fn i32_type(&self) -> IntType { + self.inner.i32_type() + } + + pub(crate) fn i64_type(&self) -> IntType { + self.inner.i64_type() + } + + pub(crate) fn f64_type(&self) -> FloatType { + self.inner.f64_type() + } + + pub(crate) fn void_type(&self) -> VoidType { + self.inner.void_type() + } + + pub(crate) fn rust_string_type(&self) -> ArrayType<'_> { + self.inner.i8_type().array_type(size_of::() as u32) + } + + pub(crate) fn rust_vec_type(&self) -> ArrayType<'_> { + self.inner.i8_type().array_type(size_of::>() as u32) + } + + pub(crate) fn opaque_struct<'a>(&'a self, name: &str) -> StructType<'a> { + self.inner.opaque_struct_type(name) + } + + pub(crate) fn struct_type<'a>( + &'a self, + fields: &[BasicTypeEnum], + ) -> StructType<'a> { + self.inner.struct_type(fields, false) + } + + pub(crate) fn class_type<'a>( + &'a self, + methods: usize, + name: &str, + method_type: StructType<'a>, + ) -> StructType<'a> { + let name_type = self.rust_string_type(); + let class_type = self.inner.opaque_struct_type(name); + + class_type.set_body( + &[ + // Name + name_type.into(), + // Instance size + self.inner.i32_type().into(), + // Number of methods + self.inner.i16_type().into(), + // The method table entries. We use an array instead of one + // field per method, as this allows us to generate indexes + // (using `getelementptr`) that are out of bounds. This is + // necessary for dynamic dispatch as we don't statically know + // the number of methods a receiver's class has. + method_type.array_type(methods as _).into(), + ], + false, + ); + class_type + } + + /// Returns the layout for a built-in type such as Int or String (i.e a type + /// with only a single value field). + pub(crate) fn builtin_type<'a>( + &'a self, + name: &str, + header: StructType<'a>, + value: BasicTypeEnum, + ) -> StructType<'a> { + let typ = self.opaque_struct(name); + + typ.set_body(&[header.into(), value], false); + typ + } + + pub(crate) fn append_basic_block<'a>( + &'a self, + function: FunctionValue<'a>, + ) -> BasicBlock<'a> { + self.inner.append_basic_block(function, "") + } + + pub(crate) fn create_builder(&self) -> Builder { + self.inner.create_builder() + } + + pub(crate) fn create_module(&self, name: &str) -> Module { + self.inner.create_module(name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_context_type_sizes() { + let ctx = Context::new(); + + // These tests exists just to make sure the layouts match that which the + // runtime expects. This would only ever fail if Rust suddenly changes + // the layout of String/Vec. + assert_eq!(ctx.rust_string_type().len(), 24); + assert_eq!(ctx.rust_vec_type().len(), 24); + } +} diff --git a/compiler/src/llvm/layouts.rs b/compiler/src/llvm/layouts.rs new file mode 100644 index 000000000..cdbcb9c3e --- /dev/null +++ b/compiler/src/llvm/layouts.rs @@ -0,0 +1,440 @@ +use crate::llvm::constants::{CLOSURE_CALL_INDEX, DROPPER_INDEX}; +use crate::llvm::context::Context; +use crate::llvm::method_hasher::MethodHasher; +use crate::mir::Mir; +use crate::state::State; +use crate::target::OperatingSystem; +use inkwell::types::{ + BasicMetadataTypeEnum, BasicTypeEnum, FunctionType, StructType, +}; +use inkwell::AddressSpace; +use std::cmp::max; +use std::collections::HashMap; +use types::{ + ClassId, MethodId, MethodSource, ARRAY_ID, BOOLEAN_ID, BYTE_ARRAY_ID, + CALL_METHOD, CHANNEL_ID, DROPPER_METHOD, FLOAT_ID, INT_ID, NIL_ID, + STRING_ID, +}; + +/// The size of an object header. +const HEADER_SIZE: u32 = 16; + +/// Method table sizes are multiplied by this value in an attempt to reduce the +/// amount of collisions when performing dynamic dispatch. +/// +/// While this increases the amount of memory needed per method table, it's not +/// really significant: each slot only takes up one word of memory. On a 64-bits +/// system this means you can fit a total of 131 072 slots in 1 MiB. In +/// addition, this cost is a one-time and constant cost, whereas collisions +/// introduce a cost that you may have to pay every time you perform dynamic +/// dispatch. +const METHOD_TABLE_FACTOR: usize = 4; + +/// The minimum number of slots in a method table. +/// +/// This value is used to ensure that even types with few methods have as few +/// collisions as possible. +/// +/// This value _must_ be a power of two. +const METHOD_TABLE_MIN_SIZE: usize = 64; + +/// Rounds the given value to the nearest power of two. +fn round_methods(mut value: usize) -> usize { + if value == 0 { + return 0; + } + + value -= 1; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + value |= value >> 32; + value += 1; + + value +} + +pub(crate) struct MethodInfo<'ctx> { + pub(crate) index: u16, + pub(crate) hash: u64, + pub(crate) collision: bool, + pub(crate) signature: FunctionType<'ctx>, + pub(crate) colliding: Vec, +} + +/// Types and layout information to expose to all modules. +pub(crate) struct Layouts<'ctx> { + /// The layout of an empty class. + /// + /// This is used for generating dynamic dispatch code, as we don't know the + /// exact class in such cases. + pub(crate) empty_class: StructType<'ctx>, + + /// The type to use for Inko methods (used for dynamic dispatch). + pub(crate) method: StructType<'ctx>, + + /// All MIR classes and their corresponding structure layouts. + pub(crate) classes: HashMap>, + + /// The structure layouts for all class instances. + pub(crate) instances: HashMap>, + + /// The structure layout of the runtime's `State` type. + pub(crate) state: StructType<'ctx>, + + /// The layout of object headers. + pub(crate) header: StructType<'ctx>, + + /// The layout of the runtime's result type. + pub(crate) result: StructType<'ctx>, + + /// The layout of the context type passed to async methods. + pub(crate) context: StructType<'ctx>, + + /// The layout to use for the type that stores the built-in type method + /// counts. + pub(crate) method_counts: StructType<'ctx>, + + /// Information about methods defined on classes, such as their signatures + /// and hash codes. + pub(crate) methods: HashMap>, + + /// The layout of messages sent to processes. + pub(crate) message: StructType<'ctx>, +} + +impl<'ctx> Layouts<'ctx> { + pub(crate) fn new( + state: &State, + mir: &Mir, + context: &'ctx Context, + ) -> Self { + let db = &state.db; + let space = AddressSpace::default(); + let mut class_layouts = HashMap::new(); + let mut instance_layouts = HashMap::new(); + let mut methods = HashMap::new(); + let header = context.struct_type(&[ + context.pointer_type().into(), // Class + context.i8_type().into(), // Kind + context.i32_type().into(), // References + ]); + + let method = context.struct_type(&[ + context.i64_type().into(), // Hash + context.pointer_type().into(), // Function pointer + ]); + + // We only include the fields that we need in the compiler. This is + // fine/safe is we only use the state type through pointers, so the + // exact size doesn't matter. + let state_layout = context.struct_type(&[ + context.pointer_type().into(), // true + context.pointer_type().into(), // false + context.pointer_type().into(), // nil + context.pointer_type().into(), // Int class + context.pointer_type().into(), // Float class + context.pointer_type().into(), // String class + context.pointer_type().into(), // Array class + context.pointer_type().into(), // Bool class + context.pointer_type().into(), // Nil class + context.pointer_type().into(), // ByteArray class + context.pointer_type().into(), // Channel class + context.pointer_type().into(), // hash_key0 + context.pointer_type().into(), // hash_key1 + ]); + + let context_layout = context.struct_type(&[ + state_layout.ptr_type(space).into(), // State + context.pointer_type().into(), // Process + context.pointer_type().into(), // Arguments pointer + ]); + + let result_layout = context.struct_type(&[ + context.pointer_type().into(), // Tag + context.pointer_type().into(), // Value + ]); + + instance_layouts.insert(ClassId::result(), result_layout); + + let method_counts_layout = context.struct_type(&[ + context.i16_type().into(), // Int + context.i16_type().into(), // Float + context.i16_type().into(), // String + context.i16_type().into(), // Array + context.i16_type().into(), // Bool + context.i16_type().into(), // Nil + context.i16_type().into(), // ByteArray + context.i16_type().into(), // Channel + ]); + + let message_layout = context.struct_type(&[ + context.pointer_type().into(), // Function + context.i8_type().into(), // Length + context.pointer_type().array_type(0).into(), // Arguments + ]); + + let mut method_hasher = MethodHasher::new(); + + // We need to define the method information for trait methods, as + // this information is necessary when generating dynamic dispatch code. + // + // This information is defined first so we can update the `collision` + // flag when generating this information for method implementations. + for mir_trait in mir.traits.values() { + for method in mir_trait + .id + .required_methods(db) + .into_iter() + .chain(mir_trait.id.default_methods(db)) + { + let name = method.name(db); + let hash = method_hasher.hash(name); + let mut args: Vec = vec![ + state_layout.ptr_type(space).into(), // State + context.pointer_type().into(), // Process + context.pointer_type().into(), // Receiver + ]; + + for _ in 0..method.number_of_arguments(db) { + args.push(context.pointer_type().into()); + } + + let signature = context.pointer_type().fn_type(&args, false); + + methods.insert( + method, + MethodInfo { + index: 0, + hash, + signature, + collision: false, + colliding: Vec::new(), + }, + ); + } + } + + for (id, mir_class) in &mir.classes { + // We size classes larger than actually needed in an attempt to + // reduce collisions when performing dynamic dispatch. + let methods_len = max( + round_methods(mir_class.methods.len()) * METHOD_TABLE_FACTOR, + METHOD_TABLE_MIN_SIZE, + ); + let name = + format!("{}::{}", id.module(db).name(db).as_str(), id.name(db)); + let class = context.class_type( + methods_len, + &format!("{}::class", name), + method, + ); + let instance = match id.0 { + INT_ID => context.builtin_type( + &name, + header, + context.i64_type().into(), + ), + FLOAT_ID => context.builtin_type( + &name, + header, + context.f64_type().into(), + ), + STRING_ID => context.builtin_type( + &name, + header, + context.pointer_type().into(), + ), + ARRAY_ID => context.builtin_type( + &name, + header, + context.rust_vec_type().into(), + ), + BOOLEAN_ID | NIL_ID => { + let typ = context.opaque_struct(&name); + + typ.set_body(&[header.into()], false); + typ + } + BYTE_ARRAY_ID => context.builtin_type( + &name, + header, + context.rust_vec_type().into(), + ), + CHANNEL_ID => context.builtin_type( + &name, + header, + context.pointer_type().into(), + ), + _ => { + // First we forward-declare the structures, as fields + // may need to refer to other classes regardless of + // ordering. + context.opaque_struct(&name) + } + }; + + let mut buckets = vec![false; methods_len]; + let max_bucket = methods_len.saturating_sub(1); + + // The slot for the dropper method has to be set first to ensure + // other methods are never hashed into this slot, regardless of the + // order we process them in. + if !buckets.is_empty() { + buckets[DROPPER_INDEX as usize] = true; + } + + // Define the method signatures once (so we can cheaply retrieve + // them whenever needed), and assign the methods to their method + // table slots. + for &method in &mir_class.methods { + let name = method.name(db); + let hash = method_hasher.hash(name); + let mut collision = false; + let index = if mir_class.id.kind(db).is_closure() { + // For closures we use a fixed layout so we can call its + // methods using virtual dispatch instead of dynamic + // dispatch. + match method.name(db).as_str() { + DROPPER_METHOD => DROPPER_INDEX as usize, + CALL_METHOD => CLOSURE_CALL_INDEX as usize, + _ => unreachable!(), + } + } else if name == DROPPER_METHOD { + // Droppers always go in slot 0 so we can efficiently call + // them even when types aren't statically known. + DROPPER_INDEX as usize + } else { + let mut index = hash as usize & (methods_len - 1); + + while buckets[index] { + collision = true; + index = (index + 1) & max_bucket; + } + + index + }; + + buckets[index] = true; + + // We track collisions so we can generate more optimal dynamic + // dispatch code if we statically know one method never collides + // with another method in the same class. + if collision { + if let MethodSource::Implementation(_, orig) = + method.source(db) + { + // We have to track the original method as defined in + // the trait, not the implementation defined for the + // class. This is because when we generate the dynamic + // dispatch code, we only know about the trait method. + methods.get_mut(&orig).unwrap().collision = true; + methods + .get_mut(&orig) + .unwrap() + .colliding + .push(mir_class.id); + } + } + + let typ = if method.is_async(db) { + context.void_type().fn_type( + &[context_layout.ptr_type(space).into()], + false, + ) + } else { + let mut args: Vec = vec![ + state_layout.ptr_type(space).into(), // State + context.pointer_type().into(), // Process + ]; + + if method.is_instance_method(db) { + args.push(context.pointer_type().into()); + } + + for _ in 0..method.number_of_arguments(db) { + args.push(context.pointer_type().into()); + } + + context.pointer_type().fn_type(&args, false) + }; + + methods.insert( + method, + MethodInfo { + index: index as u16, + hash, + signature: typ, + collision, + colliding: Vec::new(), + }, + ); + } + + class_layouts.insert(*id, class); + instance_layouts.insert(*id, instance); + } + + let process_size = + if let OperatingSystem::Linux = state.config.target.os { + // Mutexes are smaller on Linux, resulting in a smaller process + // size, so we have to take that into account when calculating + // field offsets. + 112 + } else { + 128 + }; + + for id in mir.classes.keys() { + if id.is_builtin() { + continue; + } + + let layout = instance_layouts[id]; + let mut fields: Vec = vec![header.into()]; + + // For processes we need to take into account the space between the + // header and the first field. We don't actually care about that + // state in the generated code, so we just insert a single member + // that covers it. + if id.kind(db).is_async() { + fields.push( + context + .i8_type() + .array_type(process_size - HEADER_SIZE) + .into(), + ); + } + + for _ in 0..id.number_of_fields(db) { + fields.push(context.pointer_type().into()); + } + + layout.set_body(&fields, false); + } + + Self { + empty_class: context.class_type(0, "", method), + method, + classes: class_layouts, + instances: instance_layouts, + state: state_layout, + header, + result: result_layout, + context: context_layout, + method_counts: method_counts_layout, + methods, + message: message_layout, + } + } + + pub(crate) fn methods(&self, class: ClassId) -> u32 { + self.classes[&class] + .get_field_type_at_index(3) + .unwrap() + .into_array_type() + .len() + } +} diff --git a/compiler/src/llvm/method_hasher.rs b/compiler/src/llvm/method_hasher.rs new file mode 100644 index 000000000..7b850d9ab --- /dev/null +++ b/compiler/src/llvm/method_hasher.rs @@ -0,0 +1,96 @@ +use fnv::{FnvHashMap, FnvHashSet}; + +/// A type for generating method hash codes. +/// +/// These hash codes are used as part of dynamic dispatch. Each method name is +/// given a globally unique hash code. We don't need to consider the entire +/// method's signature as Inko doesn't allow overloading of methods. +/// +/// The algorithm used by this hasher is FNV-1a, as it's one of the fastest +/// not-so-terrible hash function for small inputs. +pub(crate) struct MethodHasher<'a> { + hashes: FnvHashMap<&'a str, u64>, + used: FnvHashSet, +} + +impl<'a> MethodHasher<'a> { + pub(crate) fn new() -> MethodHasher<'a> { + // We can't predict how many unique method names there are without + // counting them, which would involve hashing, which in turn likely + // wouldn't make this hasher any faster. + // + // Instead we conservatively assume every program needs at least this + // many slots, reducing the amount of rehashing necessary without + // reserving way too much memory. + let size = 512; + + MethodHasher { + hashes: FnvHashMap::with_capacity_and_hasher( + size, + Default::default(), + ), + used: FnvHashSet::with_capacity_and_hasher( + size, + Default::default(), + ), + } + } + + pub(crate) fn hash(&mut self, name: &'a str) -> u64 { + if let Some(&hash) = self.hashes.get(name) { + return hash; + } + + let mut base = 0xcbf29ce484222325; + + for &byte in name.as_bytes() { + base = self.round(base, byte as u64); + } + + // Bytes are in the range from 0..255. By starting the extra value at + // 256 we're (hopefully) less likely to produce collisions with method + // names that are one byte longer than our current method name. + let mut extra = 256_u64; + let mut hash = base; + + // FNV isn't a perfect hash function, so collisions are possible. In + // this case we just add a number to the base hash until we produce a + // unique hash. + while self.used.contains(&hash) { + hash = self.round(base, extra); + extra = extra.wrapping_add(1); + } + + self.hashes.insert(name, hash); + self.used.insert(hash); + hash + } + + fn round(&self, hash: u64, value: u64) -> u64 { + (hash ^ value).wrapping_mul(0x100_0000_01b3) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hash() { + let mut hasher = MethodHasher::new(); + + assert_eq!(hasher.hash("foo"), hasher.hash("foo")); + assert_ne!(hasher.hash("foo"), hasher.hash("bar")); + } + + #[test] + fn test_hash_conflict() { + let mut hasher = MethodHasher::new(); + + let hash = hasher.hash("foo"); + + hasher.hashes.remove("foo"); + + assert_ne!(hasher.hash("foo"), hash); + } +} diff --git a/compiler/src/llvm/module.rs b/compiler/src/llvm/module.rs new file mode 100644 index 000000000..41fce6544 --- /dev/null +++ b/compiler/src/llvm/module.rs @@ -0,0 +1,148 @@ +use crate::llvm::builder::DebugBuilder; +use crate::llvm::context::Context; +use crate::llvm::layouts::Layouts; +use crate::llvm::runtime_function::RuntimeFunction; +use crate::mir::Constant; +use crate::symbol_names::SYMBOL_PREFIX; +use inkwell::intrinsics::Intrinsic; +use inkwell::types::BasicTypeEnum; +use inkwell::values::{BasicValue, FunctionValue, GlobalValue}; +use inkwell::{module, AddressSpace}; +use std::collections::HashMap; +use std::ops::Deref; +use std::path::Path; +use types::module_name::ModuleName; +use types::{ClassId, MethodId}; + +/// A wrapper around an LLVM Module that provides some additional methods. +pub(crate) struct Module<'a, 'ctx> { + pub(crate) inner: module::Module<'ctx>, + pub(crate) context: &'ctx Context, + pub(crate) name: ModuleName, + pub(crate) layouts: &'a Layouts<'ctx>, + pub(crate) literals: HashMap>, + pub(crate) debug_builder: DebugBuilder<'ctx>, +} + +impl<'a, 'ctx> Module<'a, 'ctx> { + pub(crate) fn new( + context: &'ctx Context, + layouts: &'a Layouts<'ctx>, + name: ModuleName, + path: &Path, + ) -> Self { + let inner = context.create_module(name.as_str()); + let debug_builder = DebugBuilder::new(&inner, context, path); + + Self { + inner, + context, + name, + layouts, + literals: HashMap::new(), + debug_builder, + } + } + + pub(crate) fn add_global(&self, name: &str) -> GlobalValue<'ctx> { + let typ = self.context.pointer_type(); + let space = AddressSpace::default(); + + self.inner.add_global(typ, Some(space), name) + } + + pub(crate) fn add_literal( + &mut self, + value: &Constant, + ) -> GlobalValue<'ctx> { + if let Some(&global) = self.literals.get(value) { + global + } else { + let name = format!( + "{}L_{}_{}", + SYMBOL_PREFIX, + self.name, + self.literals.len() + ); + + let global = self.add_global(&name); + + global.set_initializer( + &self.context.pointer_type().const_null().as_basic_value_enum(), + ); + + self.literals.insert(value.clone(), global); + global + } + } + + pub(crate) fn add_constant(&mut self, name: &str) -> GlobalValue<'ctx> { + self.inner.get_global(name).unwrap_or_else(|| self.add_global(name)) + } + + pub(crate) fn add_class( + &mut self, + id: ClassId, + name: &str, + ) -> GlobalValue<'ctx> { + self.inner.get_global(name).unwrap_or_else(|| { + let space = AddressSpace::default(); + let typ = self.layouts.classes[&id].ptr_type(space); + + self.inner.add_global(typ, Some(space), name) + }) + } + + pub(crate) fn add_method( + &self, + name: &str, + method: MethodId, + ) -> FunctionValue<'ctx> { + self.inner.get_function(name).unwrap_or_else(|| { + self.inner.add_function( + name, + self.layouts.methods[&method].signature, + None, + ) + }) + } + + pub(crate) fn add_setup_function(&self, name: &str) -> FunctionValue<'ctx> { + if let Some(func) = self.inner.get_function(name) { + func + } else { + let space = AddressSpace::default(); + let args = [self.layouts.state.ptr_type(space).into()]; + let typ = self.context.void_type().fn_type(&args, false); + + self.inner.add_function(name, typ, None) + } + } + + pub(crate) fn runtime_function( + &self, + function: RuntimeFunction, + ) -> FunctionValue<'ctx> { + self.inner + .get_function(function.name()) + .unwrap_or_else(|| function.build(self)) + } + + pub(crate) fn intrinsic( + &self, + name: &str, + args: &[BasicTypeEnum<'ctx>], + ) -> FunctionValue<'ctx> { + Intrinsic::find(name) + .and_then(|intr| intr.get_declaration(&self.inner, args)) + .unwrap() + } +} + +impl<'a, 'ctx> Deref for Module<'a, 'ctx> { + type Target = module::Module<'ctx>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs new file mode 100644 index 000000000..3f1ebe1ca --- /dev/null +++ b/compiler/src/llvm/passes.rs @@ -0,0 +1,5339 @@ +use crate::config::BuildDirectories; +use crate::llvm::builder::Builder; +use crate::llvm::constants::{ + ATOMIC_KIND, BOXED_FLOAT_VALUE_INDEX, BOXED_INT_VALUE_INDEX, + CLASS_METHODS_COUNT_INDEX, CLASS_METHODS_INDEX, CLOSURE_CALL_INDEX, + CONTEXT_ARGS_INDEX, CONTEXT_PROCESS_INDEX, CONTEXT_STATE_INDEX, + DROPPER_INDEX, FALSE_INDEX, FIELD_OFFSET, FLOAT_KIND, HASH_KEY0_INDEX, + HASH_KEY1_INDEX, HEADER_CLASS_INDEX, HEADER_KIND_INDEX, HEADER_REFS_INDEX, + INT_KIND, INT_MASK, INT_SHIFT, LLVM_RESULT_STATUS_INDEX, + LLVM_RESULT_VALUE_INDEX, MAX_INT, MESSAGE_ARGUMENTS_INDEX, + METHOD_FUNCTION_INDEX, METHOD_HASH_INDEX, MIN_INT, NIL_INDEX, OWNED_KIND, + PERMANENT_KIND, PROCESS_FIELD_OFFSET, REF_KIND, REF_MASK, TAG_MASK, + TRUE_INDEX, +}; +use crate::llvm::context::Context; +use crate::llvm::layouts::Layouts; +use crate::llvm::module::Module; +use crate::llvm::runtime_function::RuntimeFunction; +use crate::mir::{ + CloneKind, Constant, Instruction, LocationId, Method, Mir, RegisterId, +}; +use crate::state::State; +use crate::symbol_names::SymbolNames; +use crate::target::Architecture; +use inkwell::basic_block::BasicBlock; +use inkwell::passes::{PassManager, PassManagerBuilder}; +use inkwell::targets::{ + CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, +}; +use inkwell::types::{ + BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, +}; +use inkwell::values::{ + BasicMetadataValueEnum, BasicValue, BasicValueEnum, FloatValue, + FunctionValue, GlobalValue, IntValue, PointerValue, +}; +use inkwell::AddressSpace; +use inkwell::OptimizationLevel; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::path::Path; +use std::path::PathBuf; +use std::rc::Rc; +use types::module_name::ModuleName; +use types::{BuiltinFunction, ClassId, Database}; + +/// A compiler pass that compiles Inko MIR into object files using LLVM. +pub(crate) struct Compile<'a, 'b, 'ctx> { + db: &'a Database, + mir: &'a Mir, + module_index: usize, + layouts: &'a Layouts<'ctx>, + names: &'a SymbolNames, + context: &'ctx Context, + module: &'b mut Module<'a, 'ctx>, + + /// All native functions and the class IDs they belong to. + functions: HashMap>>, +} + +impl<'a, 'b, 'ctx> Compile<'a, 'b, 'ctx> { + /// Compiles all the modules into object files. + /// + /// The return value is a list of file paths of the object files. + pub(crate) fn run_all( + state: &'a State, + directories: &BuildDirectories, + mir: &'a Mir, + ) -> Result, String> { + let context = Context::new(); + let types = Layouts::new(state, mir, &context); + let names = SymbolNames::new(&state.db, mir); + let mut modules = Vec::with_capacity(mir.modules.len()); + + for module_index in 0..mir.modules.len() { + let mod_id = mir.modules[module_index].id; + let name = mod_id.name(&state.db).clone(); + let path = mod_id.file(&state.db); + let mut module = Module::new(&context, &types, name, &path); + + Compile { + db: &state.db, + mir, + module_index, + names: &names, + context: &context, + module: &mut module, + layouts: &types, + functions: HashMap::new(), + } + .run(); + + modules.push(module); + } + + let main_module = Module::new( + &context, + &types, + ModuleName::new("$main"), + Path::new("$main.inko"), + ); + + GenerateMain::new( + &state.db, + mir, + &types, + &names, + &context, + &main_module, + ) + .run(); + + modules.push(main_module); + + match state.config.target.arch { + Architecture::Amd64 => { + Target::initialize_x86(&InitializationConfig::default()); + } + Architecture::Arm64 => { + Target::initialize_aarch64(&InitializationConfig::default()); + } + } + + // LLVM's optimisation level controls which passes to run, but some/many + // of those may not be relevant to Inko, while slowing down compile + // times. Thus instead of using this knob, we provide our own list of + // passes. Swift and Rust (and possibly others) take a similar approach. + let opt = OptimizationLevel::None; + let reloc = RelocMode::PIC; + let model = CodeModel::Default; + let triple = TargetTriple::create(&state.config.target.llvm_triple()); + let target = Target::from_triple(&triple).unwrap(); + let target_machine = target + .create_target_machine(&triple, "", "", opt, reloc, model) + .unwrap(); + let layout = target_machine.get_target_data().get_data_layout(); + let pm_builder = PassManagerBuilder::create(); + let pm = PassManager::create(()); + + pm_builder.set_optimization_level(opt); + pm_builder.populate_module_pass_manager(&pm); + pm.add_promote_memory_to_register_pass(); + + for module in &modules { + module.set_data_layout(&layout); + module.set_triple(&triple); + pm.run_on(&module.inner); + } + + let mut paths = Vec::with_capacity(modules.len()); + + for module in &modules { + let path = directories + .objects + .join(format!("{}.o", module.name.normalized_name())); + + target_machine + .write_to_file(&module.inner, FileType::Object, path.as_path()) + .map_err(|err| { + format!("Failed to create {}: {}", path.display(), err) + })?; + + paths.push(path); + } + + Ok(paths) + } + + pub(crate) fn run(mut self) { + for &class_id in &self.mir.modules[self.module_index].classes { + for method_id in &self.mir.classes[&class_id].methods { + let func = LowerMethod::new( + self.db, + self.mir, + self.layouts, + self.context, + self.names, + self.module, + &self.mir.methods[method_id], + ) + .run(); + + self.functions + .entry(class_id) + .or_insert_with(Vec::new) + .push(func); + } + } + + self.generate_setup_function(); + self.module.debug_builder.finalize(); + } + + fn generate_setup_function(&mut self) { + let mod_id = self.mir.modules[self.module_index].id; + let space = AddressSpace::default(); + let fn_name = &self.names.setup_functions[&mod_id]; + let fn_val = self.module.add_setup_function(fn_name); + let builder = Builder::new(self.context, fn_val); + let entry_block = self.context.append_basic_block(fn_val); + + builder.switch_to_block(entry_block); + + let state_var = builder.alloca(self.layouts.state.ptr_type(space)); + let method_var = builder.alloca(self.layouts.method); + + builder.store(state_var, fn_val.get_nth_param(0).unwrap()); + + let body = self.context.append_basic_block(fn_val); + + builder.jump(body); + builder.switch_to_block(body); + + // Allocate all classes defined in this module, and store them in their + // corresponding globals. + for &class_id in &self.mir.modules[self.module_index].classes { + let raw_name = class_id.name(self.db); + let name_ptr = builder.string_literal(raw_name).0.into(); + let fields_len = self + .context + .i8_type() + .const_int(class_id.number_of_fields(self.db) as _, false) + .into(); + let methods_len = self + .context + .i16_type() + .const_int( + (self.layouts.methods(class_id) as usize) as _, + false, + ) + .into(); + + let class_new = if class_id.kind(self.db).is_async() { + self.module.runtime_function(RuntimeFunction::ClassProcess) + } else { + self.module.runtime_function(RuntimeFunction::ClassObject) + }; + + let layout = self.layouts.classes[&class_id]; + let global_name = &self.names.classes[&class_id]; + let global = self.module.add_class(class_id, global_name); + + // The class globals must have an initializer, otherwise LLVM treats + // them as external globals. + global.set_initializer( + &layout.ptr_type(space).const_null().as_basic_value_enum(), + ); + + let state = builder.load_pointer(self.layouts.state, state_var); + + // Built-in classes are defined in the runtime library, so we should + // look them up instead of creating a new one. + let class_ptr = if class_id.is_builtin() { + // The first three fields in the State type are the singletons, + // followed by the built-in classes, hence the offset of 3. + builder + .load_field(self.layouts.state, state, class_id.0 + 3) + .into_pointer_value() + } else { + builder + .call(class_new, &[name_ptr, fields_len, methods_len]) + .into_pointer_value() + }; + + for method in &self.mir.classes[&class_id].methods { + let info = &self.layouts.methods[method]; + let name = &self.names.methods[method]; + let func = self + .module + .get_function(name) + .unwrap() + .as_global_value() + .as_pointer_value(); + + let slot = builder.u32_literal(info.index as u32); + let method_addr = builder.array_field_index_address( + self.layouts.empty_class, + class_ptr, + CLASS_METHODS_INDEX, + slot, + ); + + let hash = builder.u64_literal(info.hash); + let layout = self.layouts.method; + let hash_idx = METHOD_HASH_INDEX; + let func_idx = METHOD_FUNCTION_INDEX; + + builder.store_field(layout, method_var, hash_idx, hash); + builder.store_field(layout, method_var, func_idx, func); + + let method = builder.load(layout, method_var); + + builder.store(method_addr, method); + } + + builder.store(global.as_pointer_value(), class_ptr); + } + + // Populate the globals for the constants defined in this module. + for &cid in &self.mir.modules[self.module_index].constants { + let name = &self.names.constants[&cid]; + let global = self.module.add_constant(name); + let value = &self.mir.constants[&cid]; + + global.set_initializer( + &self.context.pointer_type().const_null().as_basic_value_enum(), + ); + self.set_constant_global(&builder, state_var, value, global); + } + + // Populate the globals for the literals defined in this module. + for (value, global) in &self.module.literals { + self.set_constant_global(&builder, state_var, value, *global); + } + + builder.return_value(None); + } + + fn set_constant_global( + &self, + builder: &Builder<'ctx>, + state_var: PointerValue<'ctx>, + constant: &Constant, + global: GlobalValue<'ctx>, + ) -> PointerValue<'ctx> { + let global = global.as_pointer_value(); + let value = self.permanent_value(builder, state_var, constant); + + builder.store(global, value); + global + } + + fn permanent_value( + &self, + builder: &Builder<'ctx>, + state_var: PointerValue<'ctx>, + constant: &Constant, + ) -> BasicValueEnum<'ctx> { + let state = builder.load_pointer(self.layouts.state, state_var).into(); + + match constant { + Constant::Int(val) => { + if let Some(ptr) = builder.tagged_int(*val) { + ptr.into() + } else { + let val = builder.i64_literal(*val).into(); + let func = self + .module + .runtime_function(RuntimeFunction::IntBoxedPermanent); + + builder.call(func, &[state, val]) + } + } + Constant::Float(val) => { + let val = builder.context.f64_type().const_float(*val).into(); + let func = self + .module + .runtime_function(RuntimeFunction::FloatBoxedPermanent); + + builder.call(func, &[state, val]) + } + Constant::String(val) => { + let bytes_typ = + builder.context.i8_type().array_type(val.len() as _); + let bytes_var = builder.alloca(bytes_typ); + let bytes = builder.string_bytes(val); + + builder.store(bytes_var, bytes); + + let len = builder.u64_literal(val.len() as u64).into(); + let func = self + .module + .runtime_function(RuntimeFunction::StringNewPermanent); + + builder.call(func, &[state, bytes_var.into(), len]) + } + Constant::Array(values) => { + let len = builder.u64_literal(values.len() as u64).into(); + let new_func = self + .module + .runtime_function(RuntimeFunction::ArrayNewPermanent); + let push_func = + self.module.runtime_function(RuntimeFunction::ArrayPush); + let array = builder.call(new_func, &[state, len]); + + for val in values.iter() { + let ptr = self + .permanent_value(builder, state_var, val) + .into_pointer_value(); + + builder.call(push_func, &[state, array.into(), ptr.into()]); + } + + array + } + } + } +} + +/// A pass for lowering the MIR of a single method. +pub struct LowerMethod<'a, 'b, 'ctx> { + db: &'a Database, + mir: &'a Mir, + layouts: &'a Layouts<'ctx>, + + /// The MIR method that we're lowering to LLVM. + method: &'b Method, + + /// A map of method names to their mangled names. + /// + /// We cache these so we don't have to recalculate them on every reference. + names: &'a SymbolNames, + + /// The builder to use for generating instructions. + builder: Builder<'ctx>, + + /// The LLVM module the generated code belongs to. + module: &'b mut Module<'a, 'ctx>, + + /// MIR registers and their corresponding LLVM stack variables. + variables: HashMap>, + + /// The LLVM types for each MIR register. + variable_types: HashMap>, +} + +impl<'a, 'b, 'ctx> LowerMethod<'a, 'b, 'ctx> { + fn new( + db: &'a Database, + mir: &'a Mir, + layouts: &'a Layouts<'ctx>, + context: &'ctx Context, + names: &'a SymbolNames, + module: &'b mut Module<'a, 'ctx>, + method: &'b Method, + ) -> Self { + let function = module.add_method(&names.methods[&method.id], method.id); + let builder = Builder::new(context, function); + + LowerMethod { + db, + mir, + layouts, + method, + names, + module, + builder, + variables: HashMap::new(), + variable_types: HashMap::new(), + } + } + + fn run(&mut self) -> FunctionValue<'ctx> { + if self.method.id.is_async(self.db) { + self.async_method(); + } else { + self.regular_method(); + } + + self.builder.function + } + + fn regular_method(&mut self) { + let entry_block = self.builder.add_block(); + + self.builder.switch_to_block(entry_block); + + let space = AddressSpace::default(); + let state_var = + self.builder.new_stack_slot(self.layouts.state.ptr_type(space)); + let proc_var = + self.builder.new_stack_slot(self.builder.context.pointer_type()); + + // Build the stores for all the arguments, including the generated ones. + self.builder.store(state_var, self.builder.argument(0)); + self.builder.store(proc_var, self.builder.argument(1)); + + self.define_register_variables(); + + for (arg, reg) in + self.builder.arguments().skip(2).zip(self.method.arguments.iter()) + { + self.builder.store(self.variables[reg], arg); + } + + let (line, _) = self.mir.location(self.method.location).line_column(); + let debug_func = self.module.debug_builder.new_function( + self.method.id.name(self.db), + &self.names.methods[&self.method.id], + line, + self.method.id.is_private(self.db), + false, + ); + + self.builder.set_debug_function(debug_func); + self.method_body(state_var, proc_var); + } + + fn async_method(&mut self) { + let entry_block = self.builder.add_block(); + + self.builder.switch_to_block(entry_block); + + let space = AddressSpace::default(); + let state_typ = self.layouts.state.ptr_type(space); + let state_var = self.builder.new_stack_slot(state_typ); + let proc_var = + self.builder.new_stack_slot(self.builder.context.pointer_type()); + let num_args = self.method.arguments.len() as u32; + let args_type = + self.builder.context.pointer_type().array_type(num_args); + let args_var = self.builder.new_stack_slot(args_type.ptr_type(space)); + let ctx_var = + self.builder.new_stack_slot(self.layouts.context.ptr_type(space)); + + self.define_register_variables(); + + // Destructure the context into its components. This is necessary as the + // context only lives until the first yield. + self.builder.store(ctx_var, self.builder.argument(0)); + + let ctx = self.builder.load_pointer(self.layouts.context, ctx_var); + + self.builder.store( + state_var, + self.builder.load_field( + self.layouts.context, + ctx, + CONTEXT_STATE_INDEX, + ), + ); + self.builder.store( + proc_var, + self.builder.load_field( + self.layouts.context, + ctx, + CONTEXT_PROCESS_INDEX, + ), + ); + + let args = self + .builder + .load_field(self.layouts.context, ctx, CONTEXT_ARGS_INDEX) + .into_pointer_value(); + + self.builder.store(args_var, args); + + // For async methods we don't include the receiver in the message, as + // this is redundant, and keeps message sizes as compact as possible. + // Instead, we load the receiver from the context. + let self_var = self.variables[&self.method.arguments[0]]; + + self.builder.store( + self_var, + self.builder.load(self.builder.context.pointer_type(), proc_var), + ); + + // Populate the argument stack variables according to the values stored + // in the context structure. + for (index, reg) in self.method.arguments.iter().skip(1).enumerate() { + let var = self.variables[reg]; + let args = self.builder.load_pointer(args_type, args_var); + let val = self + .builder + .load_array_index(args_type, args, index) + .into_pointer_value(); + + self.builder.store(var, val); + } + + let (line, _) = self.mir.location(self.method.location).line_column(); + let debug_func = self.module.debug_builder.new_function( + self.method.id.name(self.db), + &self.names.methods[&self.method.id], + line, + self.method.id.is_private(self.db), + false, + ); + + self.builder.set_debug_function(debug_func); + self.method_body(state_var, proc_var); + } + + fn method_body( + &mut self, + state_var: PointerValue<'ctx>, + proc_var: PointerValue<'ctx>, + ) { + let mut queue = VecDeque::new(); + let mut visited = HashSet::new(); + let mut llvm_blocks = Vec::with_capacity(self.method.body.blocks.len()); + + for _ in 0..self.method.body.blocks.len() { + llvm_blocks.push(self.builder.add_block()); + } + + self.builder.jump(llvm_blocks[self.method.body.start_id.0]); + + queue.push_back(self.method.body.start_id); + visited.insert(self.method.body.start_id); + + while let Some(block_id) = queue.pop_front() { + let mir_block = &self.method.body.blocks[block_id.0]; + let llvm_block = llvm_blocks[block_id.0]; + + self.builder.switch_to_block(llvm_block); + + for ins in &mir_block.instructions { + self.instruction(&llvm_blocks, state_var, proc_var, ins); + } + + for &child in &mir_block.successors { + if visited.insert(child) { + queue.push_back(child); + } + } + } + } + + fn instruction( + &mut self, + all_blocks: &[BasicBlock], + state_var: PointerValue<'ctx>, + proc_var: PointerValue<'ctx>, + ins: &Instruction, + ) { + match ins { + Instruction::CallBuiltin(ins) => { + self.set_debug_location(ins.location); + + match ins.name { + BuiltinFunction::IntAdd => { + self.checked_int_operation( + "llvm.sadd.with.overflow", + state_var, + proc_var, + self.variables[&ins.register], + self.variables[&ins.arguments[0]], + self.variables[&ins.arguments[1]], + ); + } + BuiltinFunction::IntSub => { + self.checked_int_operation( + "llvm.ssub.with.overflow", + state_var, + proc_var, + self.variables[&ins.register], + self.variables[&ins.arguments[0]], + self.variables[&ins.arguments[1]], + ); + } + BuiltinFunction::IntMul => { + self.checked_int_operation( + "llvm.smul.with.overflow", + state_var, + proc_var, + self.variables[&ins.register], + self.variables[&ins.arguments[0]], + self.variables[&ins.arguments[1]], + ); + } + BuiltinFunction::IntDiv => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + + self.check_division_overflow(proc_var, lhs, rhs); + + let raw = self.builder.int_div(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntRem => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + + self.check_division_overflow(proc_var, lhs, rhs); + + let raw = self.builder.int_rem(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntBitAnd => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.bit_and(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntBitOr => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.bit_or(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntBitNot => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_int(val_var); + let raw = self.builder.bit_not(val); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntBitXor => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.bit_xor(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntEq => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_eq(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntGt => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_gt(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntGe => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_ge(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntLe => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_le(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntLt => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_lt(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntPow => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let lhs = self.read_int(lhs_var).into(); + let rhs = self.read_int(rhs_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::IntPow); + let raw = self + .builder + .call(func, &[proc, lhs, rhs]) + .into_int_value(); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatAdd => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_add(lhs, rhs); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatSub => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_sub(lhs, rhs); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatDiv => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_div(lhs, rhs); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatMul => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_mul(lhs, rhs); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatMod => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_rem( + self.builder.float_add( + self.builder.float_rem(lhs, rhs), + rhs, + ), + rhs, + ); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatCeil => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var); + let func = self.module.intrinsic( + "llvm.ceil", + &[self.builder.context.f64_type().into()], + ); + let raw = self + .builder + .call(func, &[val.into()]) + .into_float_value(); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatFloor => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var); + let func = self.module.intrinsic( + "llvm.floor", + &[self.builder.context.f64_type().into()], + ); + let raw = self + .builder + .call(func, &[val.into()]) + .into_float_value(); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatRound => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let lhs = self.read_float(lhs_var).into(); + let rhs = self.read_int(rhs_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::FloatRound); + let res = self.builder.call(func, &[state, lhs, rhs]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatEq => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let lhs = self.read_float(lhs_var).into(); + let rhs = self.read_float(rhs_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::FloatEq); + let res = self.builder.call(func, &[state, lhs, rhs]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatToBits => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var); + let bits = self + .builder + .bitcast(val, self.builder.context.i64_type()) + .into_int_value(); + let res = self.new_int(state_var, bits); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatFromBits => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_int(val_var); + let bits = self + .builder + .bitcast(val, self.builder.context.f64_type()) + .into_float_value(); + let res = self.new_float(state_var, bits); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatGt => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_gt(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatGe => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_ge(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatLt => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_lt(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatLe => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_float(lhs_var); + let rhs = self.read_float(rhs_var); + let raw = self.builder.float_le(lhs, rhs); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatIsInf => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var); + let fabs = self.module.intrinsic( + "llvm.fabs", + &[self.builder.context.f64_type().into()], + ); + + let pos_val = self + .builder + .call(fabs, &[val.into()]) + .into_float_value(); + + let pos_inf = self.builder.f64_literal(f64::INFINITY); + let cond = self.builder.float_eq(pos_val, pos_inf); + let res = self.new_bool(state_var, cond); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatIsNan => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var); + let raw = self.builder.float_is_nan(val); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatToInt => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_float(val_var).into(); + let func = self.module.intrinsic( + "llvm.fptosi.sat", + &[ + self.builder.context.i64_type().into(), + self.builder.context.f64_type().into(), + ], + ); + + let raw = + self.builder.call(func, &[val]).into_int_value(); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FloatToString => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.read_float(val_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::FloatToString); + let res = self.builder.call(func, &[state, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayCapacity => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let func_name = RuntimeFunction::ArrayCapacity; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayClear => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let func_name = RuntimeFunction::ArrayClear; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayDrop => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let func_name = RuntimeFunction::ArrayDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayGet => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let idx_var = self.variables[&ins.arguments[1]]; + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let index = self.read_int(idx_var).into(); + let func_name = RuntimeFunction::ArrayGet; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[array, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayLength => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let func_name = RuntimeFunction::ArrayLength; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayPop => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.builder.load_untyped_pointer(val_var); + let array = self.builder.untagged(val).into(); + let func_name = RuntimeFunction::ArrayPop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayPush => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let value_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let tagged = + self.builder.load_untyped_pointer(array_var); + let array = self.builder.untagged(tagged).into(); + let value = + self.builder.load_untyped_pointer(value_var).into(); + let func_name = RuntimeFunction::ArrayPush; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, array, value]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayRemove => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let idx_var = self.variables[&ins.arguments[1]]; + let val = self.builder.load_untyped_pointer(array_var); + let array = self.builder.untagged(val).into(); + let idx = self.read_int(idx_var).into(); + let func_name = RuntimeFunction::ArrayRemove; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[array, idx]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArrayReserve => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let amount_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.builder.load_untyped_pointer(array_var); + let array = self.builder.untagged(val).into(); + let amount = self.read_int(amount_var).into(); + let func_name = RuntimeFunction::ArrayReserve; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, array, amount]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ArraySet => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let value_var = self.variables[&ins.arguments[2]]; + let tagged = + self.builder.load_untyped_pointer(array_var); + let array = self.builder.untagged(tagged).into(); + let index = self.read_int(index_var).into(); + let value = + self.builder.load_untyped_pointer(value_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ArraySet); + let res = + self.builder.call(func, &[array, index, value]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayNew => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayNew); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayAppend => { + let reg_var = self.variables[&ins.register]; + let target_var = self.variables[&ins.arguments[0]]; + let source_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let target = self + .builder + .untagged( + self.builder.load_untyped_pointer(target_var), + ) + .into(); + let source = self + .builder + .untagged( + self.builder.load_untyped_pointer(source_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayAppend); + let res = + self.builder.call(func, &[state, target, source]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayClear => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayClear); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayClone => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayClone); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayCopyFrom => { + let reg_var = self.variables[&ins.register]; + let target_var = self.variables[&ins.arguments[0]]; + let source_var = self.variables[&ins.arguments[1]]; + let start_var = self.variables[&ins.arguments[2]]; + let length_var = self.variables[&ins.arguments[3]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let target = self + .builder + .untagged( + self.builder.load_untyped_pointer(target_var), + ) + .into(); + let source = self + .builder + .untagged( + self.builder.load_untyped_pointer(source_var), + ) + .into(); + let start = self.read_int(start_var).into(); + let length = self.read_int(length_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ByteArrayCopyFrom, + ); + let res = self.builder.call( + func, + &[state, target, source, start, length], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayDrainToString => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self.module.runtime_function( + RuntimeFunction::ByteArrayDrainToString, + ); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayToString => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self.module.runtime_function( + RuntimeFunction::ByteArrayToString, + ); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayDrop => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayDrop); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayEq => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let lhs = self + .builder + .untagged( + self.builder.load_untyped_pointer(lhs_var), + ) + .into(); + let rhs = self + .builder + .untagged( + self.builder.load_untyped_pointer(rhs_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayEq); + let res = self.builder.call(func, &[state, lhs, rhs]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayGet => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let index = self.read_int(index_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayGet); + let res = self.builder.call(func, &[array, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayLength => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayLength); + let res = self.builder.call(func, &[state, array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayPop => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayPop); + let res = self.builder.call(func, &[array]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayPush => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let value_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let value = self.read_int(value_var).into(); + let func_name = RuntimeFunction::ByteArrayPush; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, array, value]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayRemove => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let index = self.read_int(index_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayRemove); + let res = self.builder.call(func, &[array, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArrayResize => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let size_var = self.variables[&ins.arguments[1]]; + let fill_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let fill = self.read_int(fill_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArrayResize); + let res = self + .builder + .call(func, &[state, array, size, fill]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArraySet => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let value_var = self.variables[&ins.arguments[2]]; + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let index = self.read_int(index_var).into(); + let value = self.read_int(value_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArraySet); + let res = + self.builder.call(func, &[array, index, value]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ByteArraySlice => { + let reg_var = self.variables[&ins.register]; + let array_var = self.variables[&ins.arguments[0]]; + let start_var = self.variables[&ins.arguments[1]]; + let length_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let array = self + .builder + .untagged( + self.builder.load_untyped_pointer(array_var), + ) + .into(); + let start = self.read_int(start_var).into(); + let length = self.read_int(length_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::ByteArraySlice); + let res = self + .builder + .call(func, &[state, array, start, length]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessSpawn => { + let reg_var = self.variables[&ins.register]; + let program_var = self.variables[&ins.arguments[0]]; + let args_var = self.variables[&ins.arguments[1]]; + let env_var = self.variables[&ins.arguments[2]]; + let stdin_var = self.variables[&ins.arguments[3]]; + let stdout_var = self.variables[&ins.arguments[4]]; + let stderr_var = self.variables[&ins.arguments[5]]; + let dir_var = self.variables[&ins.arguments[6]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let program = self + .builder + .load_untyped_pointer(program_var) + .into(); + let args = + self.builder.load_untyped_pointer(args_var).into(); + let env = + self.builder.load_untyped_pointer(env_var).into(); + let stdin = self.read_int(stdin_var).into(); + let stdout = self.read_int(stdout_var).into(); + let stderr = self.read_int(stderr_var).into(); + let dir = + self.builder.load_untyped_pointer(dir_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessSpawn, + ); + let res = self.builder.call( + func, + &[ + proc, program, args, env, stdin, stdout, + stderr, dir, + ], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessDrop => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessDrop, + ); + let res = self.builder.call(func, &[state, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStderrClose => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStderrClose, + ); + let res = self.builder.call(func, &[state, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStderrRead => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStderrRead, + ); + let res = self + .builder + .call(func, &[state, proc, child, buf, size]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdinClose => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinClose, + ); + let res = self.builder.call(func, &[state, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdinFlush => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinFlush, + ); + let res = + self.builder.call(func, &[state, proc, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdinWriteBytes => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinWriteBytes, + ); + let res = + self.builder.call(func, &[state, proc, child, buf]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdinWriteString => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let input_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let input = + self.builder.load_untyped_pointer(input_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdinWriteString, + ); + let res = self + .builder + .call(func, &[state, proc, child, input]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdoutClose => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdoutClose, + ); + let res = self.builder.call(func, &[state, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessStdoutRead => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessStdoutRead, + ); + let res = self + .builder + .call(func, &[state, proc, child, buf, size]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessTryWait => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessTryWait, + ); + let res = self.builder.call(func, &[child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChildProcessWait => { + let reg_var = self.variables[&ins.register]; + let child_var = self.variables[&ins.arguments[0]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let child = + self.builder.load_untyped_pointer(child_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::ChildProcessWait, + ); + let res = self.builder.call(func, &[proc, child]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::CpuCores => { + let reg_var = self.variables[&ins.register]; + let func = self + .module + .runtime_function(RuntimeFunction::CpuCores); + let res = self.builder.call(func, &[]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::DirectoryCreate => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryCreate; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::DirectoryCreateRecursive => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = + RuntimeFunction::DirectoryCreateRecursive; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::DirectoryList => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryList; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::DirectoryRemove => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryRemove; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::DirectoryRemoveRecursive => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::DirectoryRemoveAll; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvArguments => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvArguments; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvExecutable => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvExecutable; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvGet => { + let reg_var = self.variables[&ins.register]; + let name_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let name = + self.builder.load_untyped_pointer(name_var).into(); + let func_name = RuntimeFunction::EnvGet; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, name]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvGetWorkingDirectory => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvGetWorkingDirectory; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvHomeDirectory => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvHomeDirectory; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvSetWorkingDirectory => { + let reg_var = self.variables[&ins.register]; + let dir_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let dir = + self.builder.load_untyped_pointer(dir_var).into(); + let func_name = RuntimeFunction::EnvSetWorkingDirectory; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, dir]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvTempDirectory => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvTempDirectory; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::EnvVariables => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::EnvVariables; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::Exit => { + let status_var = self.variables[&ins.arguments[0]]; + let status = self.read_int(status_var).into(); + let func_name = RuntimeFunction::Exit; + let func = self.module.runtime_function(func_name); + + self.builder.call_void(func, &[status]); + self.builder.unreachable(); + } + BuiltinFunction::FileCopy => { + let reg_var = self.variables[&ins.register]; + let from_var = self.variables[&ins.arguments[0]]; + let to_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let from = + self.builder.load_untyped_pointer(from_var).into(); + let to = + self.builder.load_untyped_pointer(to_var).into(); + let func_name = RuntimeFunction::FileCopy; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, from, to]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileDrop => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let file = + self.builder.load_untyped_pointer(file_var).into(); + let func_name = RuntimeFunction::FileDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, file]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileFlush => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let file = + self.builder.load_untyped_pointer(file_var).into(); + let func_name = RuntimeFunction::FileFlush; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, file]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileOpen => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let mode_var = self.variables[&ins.arguments[1]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let mode = self.read_int(mode_var).into(); + let func_name = RuntimeFunction::FileOpen; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[proc, path, mode]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileRead => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let file = + self.builder.load_untyped_pointer(file_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let func_name = RuntimeFunction::FileRead; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, file, buf, size]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileRemove => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::FileRemove; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileSeek => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let off_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let off = self.read_int(off_var).into(); + let func_name = RuntimeFunction::FileSeek; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, path, off]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileSize => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::FileSize; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileWriteBytes => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let file = + self.builder.load_untyped_pointer(file_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let func_name = RuntimeFunction::FileWriteBytes; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, file, buf]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::FileWriteString => { + let reg_var = self.variables[&ins.register]; + let file_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let file = + self.builder.load_untyped_pointer(file_var).into(); + let buf = + self.builder.load_untyped_pointer(buf_var).into(); + let func_name = RuntimeFunction::FileWriteString; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, file, buf]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelReceive => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let chan = + self.builder.load_untyped_pointer(chan_var).into(); + let func_name = RuntimeFunction::ChannelReceive; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[proc, chan]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelReceiveUntil => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let time_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let chan = + self.builder.load_untyped_pointer(chan_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::ChannelReceiveUntil; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, chan, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelDrop => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let chan = + self.builder.load_untyped_pointer(chan_var).into(); + let func_name = RuntimeFunction::ChannelDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, chan]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelWait => { + let reg_var = self.variables[&ins.register]; + let chans_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var); + let proc = self.builder.load_untyped_pointer(proc_var); + + // The standard library uses a reference in the wait() + // method, so we need to clear the reference bit before + // using the pointer. + let chans = self.builder.untagged( + self.builder.load_untyped_pointer(chans_var), + ); + let func_name = RuntimeFunction::ChannelWait; + let func = self.module.runtime_function(func_name); + let res = self.builder.call( + func, + &[state.into(), proc.into(), chans.into()], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelNew => { + let reg_var = self.variables[&ins.register]; + let cap_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let cap = self.read_int(cap_var).into(); + let func_name = RuntimeFunction::ChannelNew; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, cap]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelSend => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let msg_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let chan = + self.builder.load_untyped_pointer(chan_var).into(); + let msg = + self.builder.load_untyped_pointer(msg_var).into(); + let func_name = RuntimeFunction::ChannelSend; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, chan, msg]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ChannelTryReceive => { + let reg_var = self.variables[&ins.register]; + let chan_var = self.variables[&ins.arguments[0]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let chan = + self.builder.load_untyped_pointer(chan_var).into(); + let func_name = RuntimeFunction::ChannelTryReceive; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[proc, chan]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntRotateLeft => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var).into(); + let rhs = self.read_int(rhs_var).into(); + let func = self.module.intrinsic( + "llvm.fshl", + &[self.builder.context.i64_type().into()], + ); + let raw = self + .builder + .call(func, &[lhs, lhs, rhs]) + .into_int_value(); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntRotateRight => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var).into(); + let rhs = self.read_int(rhs_var).into(); + let func = self.module.intrinsic( + "llvm.fshr", + &[self.builder.context.i64_type().into()], + ); + let raw = self + .builder + .call(func, &[lhs, lhs, rhs]) + .into_int_value(); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntShl => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + + self.check_shift_bits(proc_var, lhs, rhs); + + let raw = self.builder.left_shift(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntShr => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + + self.check_shift_bits(proc_var, lhs, rhs); + + let raw = self.builder.signed_right_shift(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntUnsignedShr => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + + self.check_shift_bits(proc_var, lhs, rhs); + + let raw = self.builder.right_shift(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntToFloat => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let val = self.read_int(val_var); + let raw = self.builder.int_to_float(val); + let res = self.new_float(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntToString => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.arguments[0]]; + let func = self + .module + .runtime_function(RuntimeFunction::IntToString); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let val = self.read_int(val_var).into(); + let ret = self.builder.call(func, &[state, val]); + + self.builder.store(reg_var, ret); + } + BuiltinFunction::IntWrappingAdd => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_add(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntWrappingMul => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_mul(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::IntWrappingSub => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let raw = self.builder.int_sub(lhs, rhs); + let res = self.new_int(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ObjectEq => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let lhs = self.builder.load_untyped_pointer(lhs_var); + let rhs = self.builder.load_untyped_pointer(rhs_var); + let raw = self.builder.int_eq( + self.builder.pointer_to_int(lhs), + self.builder.pointer_to_int(rhs), + ); + let res = self.new_bool(state_var, raw); + + self.builder.store(reg_var, res); + } + BuiltinFunction::Panic => { + let val_var = self.variables[&ins.arguments[0]]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::ProcessPanic; + let func = self.module.runtime_function(func_name); + + self.builder.call_void(func, &[proc, val]); + self.builder.unreachable(); + } + BuiltinFunction::PathAccessedAt => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathAccessedAt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathCreatedAt => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathCreatedAt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathModifiedAt => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathModifiedAt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathExpand => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathExpand; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathExists => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathExists; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathIsDirectory => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathIsDirectory; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::PathIsFile => { + let reg_var = self.variables[&ins.register]; + let path_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let path = + self.builder.load_untyped_pointer(path_var).into(); + let func_name = RuntimeFunction::PathIsFile; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, path]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStackFrameLine => { + let reg_var = self.variables[&ins.register]; + let trace_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let func_name = RuntimeFunction::ProcessStackFrameLine; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let trace = + self.builder.load_untyped_pointer(trace_var).into(); + let index = self.read_int(index_var).into(); + let res = + self.builder.call(func, &[state, trace, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStackFrameName => { + let reg_var = self.variables[&ins.register]; + let trace_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let func_name = RuntimeFunction::ProcessStackFrameName; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let trace = + self.builder.load_untyped_pointer(trace_var).into(); + let index = self.read_int(index_var).into(); + let res = + self.builder.call(func, &[state, trace, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStackFramePath => { + let reg_var = self.variables[&ins.register]; + let trace_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let func_name = RuntimeFunction::ProcessStackFramePath; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let trace = + self.builder.load_untyped_pointer(trace_var).into(); + let index = self.read_int(index_var).into(); + let res = + self.builder.call(func, &[state, trace, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStacktrace => { + let reg_var = self.variables[&ins.register]; + let func_name = RuntimeFunction::ProcessStacktrace; + let func = self.module.runtime_function(func_name); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let res = self.builder.call(func, &[proc]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStacktraceDrop => { + let reg_var = self.variables[&ins.register]; + let trace_var = self.variables[&ins.arguments[0]]; + let func_name = RuntimeFunction::ProcessStacktraceDrop; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let trace = + self.builder.load_untyped_pointer(trace_var).into(); + let res = self.builder.call(func, &[state, trace]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessStacktraceLength => { + let reg_var = self.variables[&ins.register]; + let trace_var = self.variables[&ins.arguments[0]]; + let func_name = + RuntimeFunction::ProcessStacktraceLength; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let trace = + self.builder.load_untyped_pointer(trace_var).into(); + let res = self.builder.call(func, &[state, trace]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::ProcessSuspend => { + let reg_var = self.variables[&ins.register]; + let time_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::ProcessSuspend; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomBytes => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let size_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let size = self.read_int(size_var).into(); + let func_name = RuntimeFunction::RandomBytes; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, rng, size]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomDrop => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let func_name = RuntimeFunction::RandomDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, rng]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomFloat => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let func_name = RuntimeFunction::RandomFloat; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, rng]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomFloatRange => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let min_var = self.variables[&ins.arguments[1]]; + let max_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let min = self.read_float(min_var).into(); + let max = self.read_float(max_var).into(); + let func_name = RuntimeFunction::RandomFloatRange; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, rng, min, max]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomFromInt => { + let reg_var = self.variables[&ins.register]; + let seed_var = self.variables[&ins.arguments[0]]; + let seed = self.read_int(seed_var).into(); + let func_name = RuntimeFunction::RandomFromInt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[seed]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomInt => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let func_name = RuntimeFunction::RandomInt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, rng]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomIntRange => { + let reg_var = self.variables[&ins.register]; + let rng_var = self.variables[&ins.arguments[0]]; + let min_var = self.variables[&ins.arguments[1]]; + let max_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let rng = + self.builder.load_untyped_pointer(rng_var).into(); + let min = self.read_int(min_var).into(); + let max = self.read_int(max_var).into(); + let func_name = RuntimeFunction::RandomIntRange; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, rng, min, max]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::RandomNew => { + let reg_var = self.variables[&ins.register]; + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let func_name = RuntimeFunction::RandomNew; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[proc]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketAccept => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let time_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketAccept; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, sock, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketAddressPairAddress => { + let reg_var = self.variables[&ins.register]; + let pair_var = self.variables[&ins.arguments[0]]; + let pair = + self.builder.load_untyped_pointer(pair_var).into(); + let func_name = + RuntimeFunction::SocketAddressPairAddress; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[pair]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketAddressPairDrop => { + let reg_var = self.variables[&ins.register]; + let pair_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let pair = + self.builder.load_untyped_pointer(pair_var).into(); + let func_name = RuntimeFunction::SocketAddressPairDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, pair]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketAddressPairPort => { + let reg_var = self.variables[&ins.register]; + let pair_var = self.variables[&ins.arguments[0]]; + let pair = + self.builder.load_untyped_pointer(pair_var).into(); + let func_name = RuntimeFunction::SocketAddressPairPort; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[pair]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketNew => { + let reg_var = self.variables[&ins.register]; + let proto_var = self.variables[&ins.arguments[0]]; + let kind_var = self.variables[&ins.arguments[1]]; + let proto = self.read_int(proto_var).into(); + let kind = self.read_int(kind_var).into(); + let func_name = RuntimeFunction::SocketNew; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[proto, kind]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketBind => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let addr_var = self.variables[&ins.arguments[1]]; + let port_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let addr = + self.builder.load_untyped_pointer(addr_var).into(); + let port = self.read_int(port_var).into(); + let func_name = RuntimeFunction::SocketBind; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, sock, addr, port]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketConnect => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let addr_var = self.variables[&ins.arguments[1]]; + let port_var = self.variables[&ins.arguments[2]]; + let time_var = self.variables[&ins.arguments[3]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let addr = + self.builder.load_untyped_pointer(addr_var).into(); + let port = self.read_int(port_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketConnect; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, sock, addr, port, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketDrop => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketListen => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = self.read_int(val_var).into(); + let func_name = RuntimeFunction::SocketListen; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketLocalAddress => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketLocalAddress; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketPeerAddress => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketPeerAddress; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketRead => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let time_var = self.variables[&ins.arguments[3]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketRead; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, sock, buf, size, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketReceiveFrom => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let size_var = self.variables[&ins.arguments[2]]; + let time_var = self.variables[&ins.arguments[3]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketReceiveFrom; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, sock, buf, size, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSendBytesTo => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let addr_var = self.variables[&ins.arguments[2]]; + let port_var = self.variables[&ins.arguments[3]]; + let time_var = self.variables[&ins.arguments[4]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let addr = + self.builder.load_untyped_pointer(addr_var).into(); + let port = self.read_int(port_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketSendBytesTo; + let func = self.module.runtime_function(func_name); + let res = self.builder.call( + func, + &[state, proc, sock, buf, addr, port, time], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSendStringTo => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let addr_var = self.variables[&ins.arguments[2]]; + let port_var = self.variables[&ins.arguments[3]]; + let time_var = self.variables[&ins.arguments[4]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = + self.builder.load_untyped_pointer(buf_var).into(); + let addr = + self.builder.load_untyped_pointer(addr_var).into(); + let port = self.read_int(port_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketSendStringTo; + let func = self.module.runtime_function(func_name); + let res = self.builder.call( + func, + &[state, proc, sock, buf, addr, port, time], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetBroadcast => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetBroadcast; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetKeepalive => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetKeepalive; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetLinger => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = self.read_int(val_var).into(); + let func_name = RuntimeFunction::SocketSetLinger; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetNodelay => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetNodelay; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetOnlyV6 => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetOnlyV6; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetRecvSize => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = self.read_int(val_var).into(); + let func_name = RuntimeFunction::SocketSetRecvSize; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetReuseAddress => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetReuseAddress; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetReusePort => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = + self.builder.load_untyped_pointer(val_var).into(); + let func_name = RuntimeFunction::SocketSetReusePort; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetSendSize => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = self.read_int(val_var).into(); + let func_name = RuntimeFunction::SocketSetSendSize; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketSetTtl => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let val_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let val = self.read_int(val_var).into(); + let func_name = RuntimeFunction::SocketSetTtl; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock, val]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketShutdownRead => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketShutdownRead; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketShutdownReadWrite => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = + RuntimeFunction::SocketShutdownReadWrite; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketShutdownWrite => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketShutdownWrite; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketTryClone => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let func_name = RuntimeFunction::SocketTryClone; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[sock]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketWriteBytes => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let time_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketWriteBytes; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, sock, buf, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::SocketWriteString => { + let reg_var = self.variables[&ins.register]; + let sock_var = self.variables[&ins.arguments[0]]; + let buf_var = self.variables[&ins.arguments[1]]; + let time_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let sock = + self.builder.load_untyped_pointer(sock_var).into(); + let buf = + self.builder.load_untyped_pointer(buf_var).into(); + let time = self.read_int(time_var).into(); + let func_name = RuntimeFunction::SocketWriteString; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, proc, sock, buf, time]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StderrFlush => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let func_name = RuntimeFunction::StderrFlush; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StderrWriteBytes => { + let reg_var = self.variables[&ins.register]; + let input_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let input = self + .builder + .untagged( + self.builder.load_untyped_pointer(input_var), + ) + .into(); + let func = self.module.runtime_function( + RuntimeFunction::StderrWriteBytes, + ); + + let ret = + self.builder.call(func, &[state, proc, input]); + + self.builder.store(reg_var, ret); + } + BuiltinFunction::StderrWriteString => { + let reg_var = self.variables[&ins.register]; + let input_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let input = + self.builder.load_untyped_pointer(input_var).into(); + let func = self.module.runtime_function( + RuntimeFunction::StderrWriteString, + ); + + let ret = + self.builder.call(func, &[state, proc, input]); + + self.builder.store(reg_var, ret); + } + BuiltinFunction::StdinRead => { + let reg_var = self.variables[&ins.register]; + let buf_var = self.variables[&ins.arguments[0]]; + let size_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let size = self.read_int(size_var).into(); + let func_name = RuntimeFunction::StdinRead; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, buf, size]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StdoutFlush => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let func_name = RuntimeFunction::StdoutFlush; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StdoutWriteBytes => { + let reg_var = self.variables[&ins.register]; + let buf_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let buf = self + .builder + .untagged( + self.builder.load_untyped_pointer(buf_var), + ) + .into(); + let func_name = RuntimeFunction::StdoutWriteBytes; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, proc, buf]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StdoutWriteString => { + let reg_var = self.variables[&ins.register]; + let input_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let input = + self.builder.load_untyped_pointer(input_var).into(); + let func_name = RuntimeFunction::StdoutWriteString; + let func = self.module.runtime_function(func_name); + let res = + self.builder.call(func, &[state, proc, input]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringByte => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let index_var = self.variables[&ins.arguments[1]]; + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let index = self.read_int(index_var).into(); + let func_name = RuntimeFunction::StringByte; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[string, index]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringCharacters => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringCharacters; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringCharactersDrop => { + let reg_var = self.variables[&ins.register]; + let iter_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let iter = + self.builder.load_untyped_pointer(iter_var).into(); + let func_name = RuntimeFunction::StringCharactersDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, iter]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringCharactersNext => { + let reg_var = self.variables[&ins.register]; + let iter_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let iter = + self.builder.load_untyped_pointer(iter_var).into(); + let func_name = RuntimeFunction::StringCharactersNext; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, iter]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringConcat => { + let reg_var = self.variables[&ins.register]; + let len = + self.builder.i64_literal(ins.arguments.len() as _); + let temp_type = self + .builder + .context + .pointer_type() + .array_type(ins.arguments.len() as _); + let temp_var = self.builder.new_stack_slot(temp_type); + + for (idx, reg) in ins.arguments.iter().enumerate() { + let val = self + .builder + .load_untyped_pointer(self.variables[reg]); + + self.builder.store_array_field( + temp_type, temp_var, idx as _, val, + ); + } + + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let func_name = RuntimeFunction::StringConcat; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, temp_var.into(), len.into()]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringConcatArray => { + let reg_var = self.variables[&ins.register]; + let ary_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let ary = + self.builder.load_untyped_pointer(ary_var).into(); + let func_name = RuntimeFunction::StringConcatArray; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, ary]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringDrop => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringDrop; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringEq => { + let reg_var = self.variables[&ins.register]; + let lhs_var = self.variables[&ins.arguments[0]]; + let rhs_var = self.variables[&ins.arguments[1]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let lhs = + self.builder.load_untyped_pointer(lhs_var).into(); + let rhs = + self.builder.load_untyped_pointer(rhs_var).into(); + let func = self + .module + .runtime_function(RuntimeFunction::StringEquals); + let ret = self.builder.call(func, &[state, lhs, rhs]); + + self.builder.store(reg_var, ret); + } + BuiltinFunction::StringSize => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringSize; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringSliceBytes => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let start_var = self.variables[&ins.arguments[1]]; + let len_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let start = self.read_int(start_var).into(); + let len = self.read_int(len_var).into(); + let func_name = RuntimeFunction::StringSliceBytes; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, string, start, len]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringToByteArray => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringToByteArray; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringToFloat => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let start_var = self.variables[&ins.arguments[1]]; + let end_var = self.variables[&ins.arguments[2]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let start = self.read_int(start_var).into(); + let end = self.read_int(end_var).into(); + let func_name = RuntimeFunction::StringToFloat; + let func = self.module.runtime_function(func_name); + let res = self + .builder + .call(func, &[state, string, start, end]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringToInt => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let radix_var = self.variables[&ins.arguments[1]]; + let start_var = self.variables[&ins.arguments[2]]; + let end_var = self.variables[&ins.arguments[3]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let proc = + self.builder.load_untyped_pointer(proc_var).into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let radix = self.read_int(radix_var).into(); + let start = self.read_int(start_var).into(); + let end = self.read_int(end_var).into(); + let func_name = RuntimeFunction::StringToInt; + let func = self.module.runtime_function(func_name); + let res = self.builder.call( + func, + &[state, proc, string, radix, start, end], + ); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringToLower => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringToLower; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::StringToUpper => { + let reg_var = self.variables[&ins.register]; + let string_var = self.variables[&ins.arguments[0]]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let string = self + .builder + .load_untyped_pointer(string_var) + .into(); + let func_name = RuntimeFunction::StringToUpper; + let func = self.module.runtime_function(func_name); + let res = self.builder.call(func, &[state, string]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::TimeMonotonic => { + let reg_var = self.variables[&ins.register]; + let func_name = RuntimeFunction::TimeMonotonic; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::TimeSystem => { + let reg_var = self.variables[&ins.register]; + let func_name = RuntimeFunction::TimeSystem; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::TimeSystemOffset => { + let reg_var = self.variables[&ins.register]; + let func_name = RuntimeFunction::TimeSystemOffset; + let func = self.module.runtime_function(func_name); + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let res = self.builder.call(func, &[state]); + + self.builder.store(reg_var, res); + } + BuiltinFunction::HashKey0 => { + let reg_var = self.variables[&ins.register]; + let typ = self.layouts.state; + let state = self.builder.load_pointer(typ, state_var); + let index = HASH_KEY0_INDEX; + let res = self.builder.load_field(typ, state, index); + + self.builder.store(reg_var, res); + } + BuiltinFunction::HashKey1 => { + let reg_var = self.variables[&ins.register]; + let typ = self.layouts.state; + let state = self.builder.load_pointer(typ, state_var); + let index = HASH_KEY1_INDEX; + let res = self.builder.load_field(typ, state, index); + + self.builder.store(reg_var, res); + } + BuiltinFunction::Moved => unreachable!(), + } + } + Instruction::Goto(ins) => { + self.builder.jump(all_blocks[ins.block.0]); + } + Instruction::Return(ins) => { + let var = self.variables[&ins.register]; + let val = + self.builder.load(self.builder.context.pointer_type(), var); + + self.builder.return_value(Some(&val)); + } + Instruction::Array(ins) => { + let reg_var = self.variables[&ins.register]; + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let len = + self.builder.u64_literal(ins.values.len() as u64).into(); + let new_func = + self.module.runtime_function(RuntimeFunction::ArrayNew); + let push_func = + self.module.runtime_function(RuntimeFunction::ArrayPush); + let array = self.builder.call(new_func, &[state, len]); + + for reg in ins.values.iter() { + let var = self.variables[reg]; + let val = self + .builder + .load(self.builder.context.pointer_type(), var) + .into_pointer_value() + .into(); + + self.builder.call(push_func, &[state, array.into(), val]); + } + + self.builder.store(reg_var, array); + } + Instruction::Branch(ins) => { + let cond_var = self.variables[&ins.condition]; + let cond_ptr = self.builder.load_untyped_pointer(cond_var); + + // Load the `true` singleton from `State`. + let state = + self.builder.load_pointer(self.layouts.state, state_var); + let bool_ptr = self + .builder + .load_field(self.layouts.state, state, TRUE_INDEX) + .into_pointer_value(); + + // Since our booleans are heap objects we have to + // compare pointer addresses, and as such first have to + // cast our pointers to ints. + let cond_int = self.builder.pointer_to_int(cond_ptr); + let bool_int = self.builder.pointer_to_int(bool_ptr); + let cond = self.builder.int_eq(cond_int, bool_int); + + self.builder.branch( + cond, + all_blocks[ins.if_true.0], + all_blocks[ins.if_false.0], + ); + } + Instruction::Switch(ins) => { + let reg_var = self.variables[&ins.register]; + let val = self.builder.load_untyped_pointer(reg_var); + let addr = self.builder.pointer_to_int(val); + let shift = self.builder.i64_literal(INT_SHIFT as i64); + let untagged = self.builder.signed_right_shift(addr, shift); + let mut cases = Vec::with_capacity(ins.blocks.len()); + + for (index, block) in ins.blocks.iter().enumerate() { + cases.push(( + self.builder.u64_literal(index as u64), + all_blocks[block.0], + )); + } + + self.builder.exhaustive_switch(untagged, &cases); + } + Instruction::SwitchKind(ins) => { + let val_var = self.variables[&ins.register]; + let kind_var = self.kind_of(val_var); + let kind = self + .builder + .load(self.builder.context.i8_type(), kind_var) + .into_int_value(); + + // Now we can generate the switch that jumps to the correct + // block based on the value kind. + let owned_block = all_blocks[ins.blocks[0].0]; + let ref_block = all_blocks[ins.blocks[1].0]; + let atomic_block = all_blocks[ins.blocks[2].0]; + let perm_block = all_blocks[ins.blocks[3].0]; + let int_block = all_blocks[ins.blocks[4].0]; + let float_block = all_blocks[ins.blocks[5].0]; + let cases = [ + (self.builder.u8_literal(OWNED_KIND), owned_block), + (self.builder.u8_literal(REF_KIND), ref_block), + (self.builder.u8_literal(ATOMIC_KIND), atomic_block), + (self.builder.u8_literal(PERMANENT_KIND), perm_block), + (self.builder.u8_literal(INT_KIND), int_block), + (self.builder.u8_literal(FLOAT_KIND), float_block), + ]; + + self.builder.exhaustive_switch(kind, &cases); + } + Instruction::Nil(ins) => { + let result = self.variables[&ins.register]; + let state = + self.builder.load_pointer(self.layouts.state, state_var); + let val = self.builder.load_field( + self.layouts.state, + state, + NIL_INDEX, + ); + + self.builder.store(result, val); + } + Instruction::True(ins) => { + let result = self.variables[&ins.register]; + let state = + self.builder.load_pointer(self.layouts.state, state_var); + let val = self.builder.load_field( + self.layouts.state, + state, + TRUE_INDEX, + ); + + self.builder.store(result, val); + } + Instruction::False(ins) => { + let result = self.variables[&ins.register]; + let state = + self.builder.load_pointer(self.layouts.state, state_var); + let val = self.builder.load_field( + self.layouts.state, + state, + FALSE_INDEX, + ); + + self.builder.store(result, val); + } + Instruction::Int(ins) => { + let var = self.variables[&ins.register]; + + if let Some(ptr) = self.builder.tagged_int(ins.value) { + self.builder.store(var, ptr); + } else { + let global = self + .module + .add_literal(&Constant::Int(ins.value)) + .as_pointer_value(); + let value = self.builder.load_untyped_pointer(global); + + self.builder.store(var, value); + } + } + Instruction::Float(ins) => { + let var = self.variables[&ins.register]; + let global = self + .module + .add_literal(&Constant::Float(ins.value)) + .as_pointer_value(); + let value = self.builder.load_untyped_pointer(global); + + self.builder.store(var, value); + } + Instruction::String(ins) => { + let var = self.variables[&ins.register]; + let global = self + .module + .add_literal(&Constant::String(Rc::new(ins.value.clone()))) + .as_pointer_value(); + let value = self.builder.load_untyped_pointer(global); + + self.builder.store(var, value); + } + Instruction::MoveRegister(ins) => { + let source = self.variables[&ins.source]; + let target = self.variables[&ins.target]; + let typ = self.variable_types[&ins.source]; + + self.builder.store(target, self.builder.load(typ, source)); + } + Instruction::CallStatic(ins) => { + self.set_debug_location(ins.location); + + let func_name = &self.names.methods[&ins.method]; + let func = self.module.add_method(func_name, ins.method); + let mut args: Vec = vec![ + self.builder + .load_pointer(self.layouts.state, state_var) + .into(), + self.builder.load_untyped_pointer(proc_var).into(), + ]; + + for reg in &ins.arguments { + args.push( + self.builder + .load_untyped_pointer(self.variables[reg]) + .into(), + ); + } + + self.call(ins.register, func, &args); + } + Instruction::CallInstance(ins) => { + self.set_debug_location(ins.location); + + let rec_var = self.variables[&ins.receiver]; + let func_name = &self.names.methods[&ins.method]; + let func = self.module.add_method(func_name, ins.method); + let mut args: Vec = vec![ + self.builder + .load_pointer(self.layouts.state, state_var) + .into(), + self.builder.load_untyped_pointer(proc_var).into(), + self.builder.load_untyped_pointer(rec_var).into(), + ]; + + for reg in &ins.arguments { + args.push( + self.builder + .load_untyped_pointer(self.variables[reg]) + .into(), + ); + } + + self.call(ins.register, func, &args); + } + Instruction::CallDynamic(ins) => { + self.set_debug_location(ins.location); + + // For dynamic dispatch we use hashing as described in + // https://thume.ca/2019/07/29/shenanigans-with-hash-tables/. + // + // Probing is only performed if collisions are known to be + // possible for a certain hash. + let loop_start = self.builder.add_block(); + let after_loop = self.builder.add_block(); + + let index_type = self.builder.context.i64_type(); + let index_var = self.builder.new_stack_slot(index_type); + let rec_var = self.variables[&ins.receiver]; + + let rec = self.builder.load_untyped_pointer(rec_var); + let info = &self.layouts.methods[&ins.method]; + let rec_class = self.class_of(rec); + let rec_type = self.layouts.empty_class; + + // (class.method_slots - 1) as u64 + let len = self.builder.int_to_int( + self.builder.int_sub( + self.builder + .load_field( + rec_type, + rec_class, + CLASS_METHODS_COUNT_INDEX, + ) + .into_int_value(), + self.builder.u16_literal(1), + ), + ); + + let hash = self.builder.u64_literal(info.hash); + + self.builder.store(index_var, hash); + + let space = AddressSpace::default(); + let func_type = info.signature; + let func_var = + self.builder.new_stack_slot(func_type.ptr_type(space)); + + self.builder.jump(loop_start); + + // The start of the probing loop (probing is necessary). + self.builder.switch_to_block(loop_start); + + // slot = index & len + let index = + self.builder.load(index_type, index_var).into_int_value(); + let slot = self.builder.bit_and(index, len); + let method_addr = self.builder.array_field_index_address( + rec_type, + rec_class, + CLASS_METHODS_INDEX, + slot, + ); + let method = self + .builder + .load(self.layouts.method, method_addr) + .into_struct_value(); + + // We only generate the probing code when it's actually + // necessary. In practise most dynamic dispatch call sites won't + // need probing. + if info.collision { + let ne_block = self.builder.add_block(); + + // method.hash == hash + let mhash = self + .builder + .extract_field(method, METHOD_HASH_INDEX) + .into_int_value(); + let hash_eq = self.builder.int_eq(mhash, hash); + + self.builder.branch(hash_eq, after_loop, ne_block); + + // The block to jump to when the hash codes didn't match. + self.builder.switch_to_block(ne_block); + self.builder.store( + index_var, + self.builder + .int_add(index, self.builder.u64_literal(1)), + ); + self.builder.jump(loop_start); + } else { + self.builder.jump(after_loop); + } + + // The block to jump to at the end of the loop, used for + // calling the native function. + self.builder.switch_to_block(after_loop); + + self.builder.store( + func_var, + self.builder.extract_field(method, METHOD_FUNCTION_INDEX), + ); + + let mut args: Vec = vec![ + self.builder + .load_pointer(self.layouts.state, state_var) + .into(), + self.builder.load_untyped_pointer(proc_var).into(), + rec.into(), + ]; + + for reg in &ins.arguments { + let val = self + .builder + .load_untyped_pointer(self.variables[reg]) + .into(); + + args.push(val); + } + + let func_val = + self.builder.load_function_pointer(func_type, func_var); + + self.indirect_call(ins.register, func_type, func_val, &args); + } + Instruction::CallClosure(ins) => { + self.set_debug_location(ins.location); + + let rec_var = self.variables[&ins.receiver]; + let space = AddressSpace::default(); + + // For closures we generate the signature on the fly, as the + // method for `call` isn't always clearly defined: for an + // argument typed as a closure, we don't know what the actual + // method is, thus we can't retrieve an existing signature. + let mut sig_args: Vec = vec![ + self.layouts.state.ptr_type(space).into(), // State + self.builder.context.pointer_type().into(), // Process + self.builder.context.pointer_type().into(), // Closure + ]; + + for _ in &ins.arguments { + sig_args.push(self.builder.context.pointer_type().into()); + } + + // Load the method from the method table. + let rec = self.builder.load_untyped_pointer(rec_var); + let untagged = self.builder.untagged(rec); + let class = self + .builder + .load_field( + self.layouts.header, + untagged, + HEADER_CLASS_INDEX, + ) + .into_pointer_value(); + + let mut args: Vec = vec![ + self.builder + .load_pointer(self.layouts.state, state_var) + .into(), + self.builder.load_untyped_pointer(proc_var).into(), + rec.into(), + ]; + + for reg in &ins.arguments { + args.push( + self.builder + .load_untyped_pointer(self.variables[reg]) + .into(), + ); + } + + let slot = self.builder.u32_literal(CLOSURE_CALL_INDEX); + let method_addr = self.builder.array_field_index_address( + self.layouts.empty_class, + class, + CLASS_METHODS_INDEX, + slot, + ); + + let method = self + .builder + .load(self.layouts.method, method_addr) + .into_struct_value(); + + let func_val = self + .builder + .extract_field(method, METHOD_FUNCTION_INDEX) + .into_pointer_value(); + + let func_type = self + .builder + .context + .pointer_type() + .fn_type(&sig_args, false); + + self.indirect_call(ins.register, func_type, func_val, &args); + } + Instruction::CallDropper(ins) => { + self.set_debug_location(ins.location); + + let rec_var = self.variables[&ins.receiver]; + let space = AddressSpace::default(); + let sig_args: Vec = vec![ + self.layouts.state.ptr_type(space).into(), // State + self.builder.context.pointer_type().into(), // Process + self.builder.context.pointer_type().into(), // Receiver + ]; + + let rec = self.builder.load_untyped_pointer(rec_var); + let untagged = self.builder.untagged(rec); + let class = self + .builder + .load_field( + self.layouts.header, + untagged, + HEADER_CLASS_INDEX, + ) + .into_pointer_value(); + + let state = + self.builder.load_pointer(self.layouts.state, state_var); + let proc = self.builder.load_untyped_pointer(proc_var); + let args: Vec = + vec![state.into(), proc.into(), rec.into()]; + + let slot = self.builder.u32_literal(DROPPER_INDEX); + let method_addr = self.builder.array_field_index_address( + self.layouts.empty_class, + class, + CLASS_METHODS_INDEX, + slot, + ); + + let method = self + .builder + .load(self.layouts.method, method_addr) + .into_struct_value(); + + let func_val = self + .builder + .extract_field(method, METHOD_FUNCTION_INDEX) + .into_pointer_value(); + + let func_type = self + .builder + .context + .pointer_type() + .fn_type(&sig_args, false); + + self.indirect_call(ins.register, func_type, func_val, &args); + } + Instruction::Send(ins) => { + let rec_var = self.variables[&ins.receiver]; + let method_name = &self.names.methods[&ins.method]; + let method = self + .module + .add_method(method_name, ins.method) + .as_global_value() + .as_pointer_value() + .into(); + let len = + self.builder.u8_literal(ins.arguments.len() as u8).into(); + let message_new = + self.module.runtime_function(RuntimeFunction::MessageNew); + let send_message = self + .module + .runtime_function(RuntimeFunction::ProcessSendMessage); + let message = self + .builder + .call(message_new, &[method, len]) + .into_pointer_value(); + + // The receiver doesn't need to be stored in the message, as + // each async method sets `self` to the process running it. + for (index, reg) in ins.arguments.iter().enumerate() { + let val = + self.builder.load_untyped_pointer(self.variables[reg]); + let slot = self.builder.u32_literal(index as u32); + let addr = self.builder.array_field_index_address( + self.layouts.message, + message, + MESSAGE_ARGUMENTS_INDEX, + slot, + ); + + self.builder.store(addr, val); + } + + let state = self + .builder + .load_pointer(self.layouts.state, state_var) + .into(); + let sender = self.builder.load_untyped_pointer(proc_var).into(); + let receiver = + self.builder.load_untyped_pointer(rec_var).into(); + + self.builder.call_void( + send_message, + &[state, sender, receiver, message.into()], + ); + } + Instruction::GetField(ins) + if ins.class.kind(self.db).is_extern() => + { + let reg_var = self.variables[&ins.register]; + let rec_var = self.variables[&ins.receiver]; + let layout = self.layouts.instances[&ins.class]; + let index = ins.field.index(self.db) as u32; + let rec = + self.builder.load(layout, rec_var).into_struct_value(); + let field = self.builder.extract_field(rec, index); + + self.builder.store(reg_var, field); + } + Instruction::SetField(ins) + if ins.class.kind(self.db).is_extern() => + { + let rec_var = self.variables[&ins.receiver]; + let val_var = self.variables[&ins.value]; + let layout = self.layouts.instances[&ins.class]; + let index = ins.field.index(self.db) as u32; + let val = self.builder.load_untyped_pointer(val_var); + + self.builder.store_field(layout, rec_var, index, val); + } + Instruction::GetField(ins) => { + let reg_var = self.variables[&ins.register]; + let rec_var = self.variables[&ins.receiver]; + let base = if ins.class.kind(self.db).is_async() { + PROCESS_FIELD_OFFSET + } else { + FIELD_OFFSET + }; + + let index = (base + ins.field.index(self.db)) as u32; + let layout = self.layouts.instances[&ins.class]; + let rec = self + .builder + .untagged(self.builder.load_untyped_pointer(rec_var)); + let field = self.builder.load_field(layout, rec, index); + + self.builder.store(reg_var, field); + } + Instruction::SetField(ins) => { + let rec_var = self.variables[&ins.receiver]; + let val_var = self.variables[&ins.value]; + let base = if ins.class.kind(self.db).is_async() { + PROCESS_FIELD_OFFSET + } else { + FIELD_OFFSET + }; + + let index = (base + ins.field.index(self.db)) as u32; + let val = self.builder.load_untyped_pointer(val_var); + let layout = self.layouts.instances[&ins.class]; + let rec = self + .builder + .untagged(self.builder.load_untyped_pointer(rec_var)); + + self.builder.store_field(layout, rec, index, val); + } + Instruction::CheckRefs(ins) => { + let var = self.variables[&ins.register]; + let proc = self.builder.load_untyped_pointer(proc_var).into(); + let check = self.builder.load_untyped_pointer(var).into(); + let func = + self.module.runtime_function(RuntimeFunction::CheckRefs); + + self.builder.call_void(func, &[proc, check]); + } + Instruction::Free(ins) => { + let var = self.variables[&ins.register]; + let free = self.builder.load_untyped_pointer(var).into(); + let func = self.module.runtime_function(RuntimeFunction::Free); + + self.builder.call_void(func, &[free]); + } + Instruction::Clone(ins) => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.source]; + let val = self.builder.load_untyped_pointer(val_var); + + match ins.kind { + CloneKind::Float => { + let state = self + .builder + .load_pointer(self.layouts.state, state_var); + let func = self + .module + .runtime_function(RuntimeFunction::FloatClone); + let result = self + .builder + .call(func, &[state.into(), val.into()]) + .into_pointer_value(); + + self.builder.store(reg_var, result); + } + CloneKind::Int => { + let addr = self.builder.pointer_to_int(val); + let mask = self.builder.i64_literal(INT_MASK); + let bits = self.builder.bit_and(addr, mask); + let cond = self.builder.int_eq(bits, mask); + let after_block = self.builder.add_block(); + let tagged_block = self.builder.add_block(); + let heap_block = self.builder.add_block(); + + self.builder.branch(cond, tagged_block, heap_block); + + // The block to jump to when the Int is a tagged Int. + self.builder.switch_to_block(tagged_block); + self.builder.store(reg_var, val); + self.builder.jump(after_block); + + // The block to jump to when the Int is a boxed Int. + self.builder.switch_to_block(heap_block); + + let func = self + .module + .runtime_function(RuntimeFunction::IntClone); + let state = self + .builder + .load_pointer(self.layouts.state, state_var); + let result = self + .builder + .call(func, &[state.into(), val.into()]) + .into_pointer_value(); + + self.builder.store(reg_var, result); + self.builder.jump(after_block); + + self.builder.switch_to_block(after_block); + } + } + } + Instruction::Increment(ins) => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.value]; + let val = self.builder.load_untyped_pointer(val_var); + let header = self.builder.untagged(val); + let one = self.builder.u32_literal(1); + let old = self + .builder + .load_field(self.layouts.header, header, HEADER_REFS_INDEX) + .into_int_value(); + let new = self.builder.int_add(old, one); + + self.builder.store_field( + self.layouts.header, + header, + HEADER_REFS_INDEX, + new, + ); + + let old_addr = self.builder.pointer_to_int(val); + let mask = self.builder.i64_literal(REF_MASK); + let new_addr = self.builder.bit_or(old_addr, mask); + let ref_ptr = self.builder.int_to_pointer(new_addr); + + self.builder.store(reg_var, ref_ptr); + } + Instruction::Decrement(ins) => { + let var = self.variables[&ins.register]; + let header = self + .builder + .untagged(self.builder.load_untyped_pointer(var)); + + let old_refs = self + .builder + .load_field(self.layouts.header, header, HEADER_REFS_INDEX) + .into_int_value(); + let one = self.builder.u32_literal(1); + let new_refs = self.builder.int_sub(old_refs, one); + + self.builder.store_field( + self.layouts.header, + header, + HEADER_REFS_INDEX, + new_refs, + ); + } + Instruction::IncrementAtomic(ins) => { + let reg_var = self.variables[&ins.register]; + let val_var = self.variables[&ins.value]; + let val = self.builder.load_untyped_pointer(val_var); + let one = self.builder.u32_literal(1); + let field = self.builder.field_address( + self.layouts.header, + val, + HEADER_REFS_INDEX, + ); + + self.builder.atomic_add(field, one); + self.builder.store(reg_var, val); + } + Instruction::DecrementAtomic(ins) => { + let var = self.variables[&ins.register]; + let header = + self.builder.load_pointer(self.layouts.header, var); + let decr_block = self.builder.add_block(); + let drop_block = all_blocks[ins.if_true.0]; + let after_block = all_blocks[ins.if_false.0]; + let kind = self + .builder + .load_field(self.layouts.header, header, HEADER_KIND_INDEX) + .into_int_value(); + let perm_kind = self.builder.u8_literal(PERMANENT_KIND); + let is_perm = self.builder.int_eq(kind, perm_kind); + + self.builder.branch(is_perm, after_block, decr_block); + + // The block to jump to when the value isn't a permanent value, + // and its reference count should be decremented. + self.builder.switch_to_block(decr_block); + + let one = self.builder.u32_literal(1); + let refs = self.builder.field_address( + self.layouts.header, + header, + HEADER_REFS_INDEX, + ); + let old_refs = self.builder.atomic_sub(refs, one); + let is_zero = self.builder.int_eq(old_refs, one); + + self.builder.branch(is_zero, drop_block, after_block); + } + Instruction::Allocate(ins) + if ins.class.kind(self.db).is_extern() => + { + // Defining the alloca already reserves (uninitialised) memory, + // so there's nothing we actually need to do here. Setting the + // fields is done using separate instructions. + } + Instruction::Allocate(ins) => { + let reg_var = self.variables[&ins.register]; + let name = &self.names.classes[&ins.class]; + let global = + self.module.add_class(ins.class, name).as_pointer_value(); + let class = self.builder.load_untyped_pointer(global); + let func = + self.module.runtime_function(RuntimeFunction::Allocate); + let ptr = self.builder.call(func, &[class.into()]); + + self.builder.store(reg_var, ptr); + } + Instruction::Spawn(ins) => { + let reg_var = self.variables[&ins.register]; + let name = &self.names.classes[&ins.class]; + let global = + self.module.add_class(ins.class, name).as_pointer_value(); + let class = self.builder.load_untyped_pointer(global).into(); + let proc = self.builder.load_untyped_pointer(proc_var).into(); + let func = + self.module.runtime_function(RuntimeFunction::ProcessNew); + let ptr = self.builder.call(func, &[proc, class]); + + self.builder.store(reg_var, ptr); + } + Instruction::GetConstant(ins) => { + let var = self.variables[&ins.register]; + let name = &self.names.constants[&ins.id]; + let global = self.module.add_constant(name).as_pointer_value(); + let value = self.builder.load_untyped_pointer(global); + + self.builder.store(var, value); + } + Instruction::Reduce(ins) => { + let amount = self + .builder + .context + .i16_type() + .const_int(ins.amount as u64, false) + .into(); + let proc = self.builder.load_untyped_pointer(proc_var).into(); + let func = + self.module.runtime_function(RuntimeFunction::Reduce); + + self.builder.call_void(func, &[proc, amount]); + } + Instruction::Finish(ins) => { + let proc = self.builder.load_untyped_pointer(proc_var).into(); + let terminate = self + .builder + .context + .bool_type() + .const_int(ins.terminate as _, false) + .into(); + let func = self + .module + .runtime_function(RuntimeFunction::ProcessFinishMessage); + + self.builder.call_void(func, &[proc, terminate]); + self.builder.unreachable(); + } + Instruction::Reference(_) => unreachable!(), + Instruction::Drop(_) => unreachable!(), + } + } + + fn kind_of( + &mut self, + pointer_variable: PointerValue<'ctx>, + ) -> PointerValue<'ctx> { + // Instead of fiddling with phi nodes we just inject a new stack slot in + // the entry block and use that. clang takes a similar approach when + // building switch() statements. + let result = + self.builder.new_stack_slot(self.builder.context.i8_type()); + let int_block = self.builder.add_block(); + let ref_block = self.builder.add_block(); + let header_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + let pointer = self.builder.load_untyped_pointer(pointer_variable); + let addr = self.builder.pointer_to_int(pointer); + let mask = self.builder.i64_literal(TAG_MASK); + let bits = self.builder.bit_and(addr, mask); + + // This generates the equivalent of the following: + // + // match ptr as usize & MASK { + // INT_MASK => ... + // MASK => ... + // REF_MASK => ... + // _ => ... + // } + self.builder.switch( + bits, + &[ + (self.builder.i64_literal(INT_MASK), int_block), + // Uneven tagged integers will have both the first and second + // bit set to 1, so we also need to handle such values here. + (self.builder.i64_literal(TAG_MASK), int_block), + (self.builder.i64_literal(REF_MASK), ref_block), + ], + header_block, + ); + + // The case for when the value is a tagged integer. + self.builder.switch_to_block(int_block); + self.builder.store(result, self.builder.u8_literal(INT_KIND)); + self.builder.jump(after_block); + + // The case for when the value is a reference. + self.builder.switch_to_block(ref_block); + self.builder.store(result, self.builder.u8_literal(REF_KIND)); + self.builder.jump(after_block); + + // The fallback case where we read the kind from the object header. This + // generates the equivalent of `(*(ptr as *mut Header)).kind`. + self.builder.switch_to_block(header_block); + + let header_val = self + .builder + .load_field(self.layouts.header, pointer, HEADER_KIND_INDEX) + .into_int_value(); + + self.builder.store(result, header_val); + self.builder.jump(after_block); + self.builder.switch_to_block(after_block); + result + } + + fn class_of(&mut self, receiver: PointerValue<'ctx>) -> PointerValue<'ctx> { + let tagged_block = self.builder.add_block(); + let heap_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + let class_var = + self.builder.new_stack_slot(self.builder.context.pointer_type()); + let int_global = self + .module + .add_class(ClassId::int(), &self.names.classes[&ClassId::int()]); + + let addr = self.builder.pointer_to_int(receiver); + let mask = self.builder.i64_literal(INT_MASK); + let bits = self.builder.bit_and(addr, mask); + let is_tagged = self.builder.int_eq(bits, mask); + + self.builder.branch(is_tagged, tagged_block, heap_block); + + // The block to jump to when the receiver is a tagged integer. + self.builder.switch_to_block(tagged_block); + self.builder.store( + class_var, + self.builder.load_untyped_pointer(int_global.as_pointer_value()), + ); + self.builder.jump(after_block); + + // The block to jump to when the receiver is a heap object. In this case + // we read the class from the (untagged) header. + self.builder.switch_to_block(heap_block); + + let header = self.builder.untagged(receiver); + let class = self + .builder + .load_field(self.layouts.header, header, HEADER_CLASS_INDEX) + .into_pointer_value(); + + self.builder.store(class_var, class); + self.builder.jump(after_block); + + // The block to jump to to load the method pointer. + self.builder.switch_to_block(after_block); + self.builder.load_pointer(self.layouts.empty_class, class_var) + } + + fn read_int(&mut self, variable: PointerValue<'ctx>) -> IntValue<'ctx> { + let pointer = self.builder.load_untyped_pointer(variable); + let res_type = self.builder.context.i64_type(); + let res_var = self.builder.new_stack_slot(res_type); + let tagged_block = self.builder.add_block(); + let heap_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + + let addr = self.builder.pointer_to_int(pointer); + let mask = self.builder.i64_literal(INT_MASK); + let bits = self.builder.bit_and(addr, mask); + let cond = self.builder.int_eq(bits, mask); + + self.builder.branch(cond, tagged_block, heap_block); + + // The block to jump to when the Int is a tagged Int. + self.builder.switch_to_block(tagged_block); + + let shift = self.builder.i64_literal(INT_SHIFT as i64); + let untagged = self.builder.signed_right_shift(addr, shift); + + self.builder.store(res_var, untagged); + self.builder.jump(after_block); + + // The block to jump to when the Int is a heap Int. + self.builder.switch_to_block(heap_block); + + let layout = self.layouts.instances[&ClassId::int()]; + + self.builder.store( + res_var, + self.builder.load_field(layout, pointer, BOXED_INT_VALUE_INDEX), + ); + self.builder.jump(after_block); + + self.builder.switch_to_block(after_block); + self.builder.load(res_type, res_var).into_int_value() + } + + fn read_float(&mut self, variable: PointerValue<'ctx>) -> FloatValue<'ctx> { + let layout = self.layouts.instances[&ClassId::float()]; + let ptr = self.builder.load_pointer(layout, variable); + + self.builder + .load_field(layout, ptr, BOXED_FLOAT_VALUE_INDEX) + .into_float_value() + } + + fn new_float( + &mut self, + state_var: PointerValue<'ctx>, + value: FloatValue<'ctx>, + ) -> PointerValue<'ctx> { + let func = self.module.runtime_function(RuntimeFunction::FloatBoxed); + let state = self.builder.load_pointer(self.layouts.state, state_var); + + self.builder + .call(func, &[state.into(), value.into()]) + .into_pointer_value() + } + + fn checked_int_operation( + &mut self, + name: &str, + state_var: PointerValue<'ctx>, + proc_var: PointerValue<'ctx>, + reg_var: PointerValue<'ctx>, + lhs_var: PointerValue<'ctx>, + rhs_var: PointerValue<'ctx>, + ) { + let ok_block = self.builder.add_block(); + let err_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + let lhs = self.read_int(lhs_var); + let rhs = self.read_int(rhs_var); + let func = self.module.runtime_function(RuntimeFunction::IntOverflow); + let add = self + .module + .intrinsic(name, &[self.builder.context.i64_type().into()]); + + let res = self + .builder + .call(add, &[lhs.into(), rhs.into()]) + .into_struct_value(); + + // Check if we overflowed the operation. + let new_val = self + .builder + .extract_field(res, LLVM_RESULT_VALUE_INDEX) + .into_int_value(); + let overflow = self + .builder + .extract_field(res, LLVM_RESULT_STATUS_INDEX) + .into_int_value(); + + self.builder.branch(overflow, err_block, ok_block); + + // The block to jump to if the operation didn't overflow. + { + self.builder.switch_to_block(ok_block); + + let val = self.new_int(state_var, new_val); + + self.builder.store(reg_var, val); + self.builder.jump(after_block); + } + + // The block to jump to if the operation overflowed. + self.builder.switch_to_block(err_block); + + let proc = self.builder.load_untyped_pointer(proc_var); + + self.builder.call_void(func, &[proc.into(), lhs.into(), rhs.into()]); + self.builder.unreachable(); + self.builder.switch_to_block(after_block); + } + + fn new_int( + &mut self, + state_var: PointerValue<'ctx>, + value: IntValue<'ctx>, + ) -> PointerValue<'ctx> { + let res_var = + self.builder.new_stack_slot(self.builder.context.pointer_type()); + let tagged_block = self.builder.add_block(); + let heap_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + let and_block = self.builder.add_block(); + + let min = self.builder.i64_literal(MIN_INT); + let max = self.builder.i64_literal(MAX_INT); + + self.builder.branch( + self.builder.int_ge(value, min), + and_block, + heap_block, + ); + + // The block to jump to when we're larger than or equal to the minimum + // value for a tagged Int. + self.builder.switch_to_block(and_block); + self.builder.branch( + self.builder.int_le(value, max), + tagged_block, + heap_block, + ); + + // The block to jump to when the Int fits in a tagged pointer. + self.builder.switch_to_block(tagged_block); + + let shift = self.builder.i64_literal(INT_SHIFT as i64); + let mask = self.builder.i64_literal(INT_MASK); + let addr = + self.builder.bit_or(self.builder.left_shift(value, shift), mask); + + self.builder.store(res_var, self.builder.int_to_pointer(addr)); + self.builder.jump(after_block); + + // The block to jump to when the Int must be boxed. + self.builder.switch_to_block(heap_block); + + let func = self.module.runtime_function(RuntimeFunction::IntBoxed); + let state = self.builder.load_pointer(self.layouts.state, state_var); + let res = self.builder.call(func, &[state.into(), value.into()]); + + self.builder.store(res_var, res); + self.builder.jump(after_block); + + self.builder.switch_to_block(after_block); + self.builder.load_untyped_pointer(res_var) + } + + fn new_bool( + &mut self, + state_var: PointerValue<'ctx>, + value: IntValue<'ctx>, + ) -> PointerValue<'ctx> { + let result = + self.builder.new_stack_slot(self.builder.context.pointer_type()); + let state = self.builder.load_pointer(self.layouts.state, state_var); + let true_block = self.builder.add_block(); + let false_block = self.builder.add_block(); + let after_block = self.builder.add_block(); + + self.builder.branch(value, true_block, false_block); + + // The block to jump to when the condition is true. + self.builder.switch_to_block(true_block); + self.builder.store( + result, + self.builder.load_field(self.layouts.state, state, TRUE_INDEX), + ); + self.builder.jump(after_block); + + // The block to jump to when the condition is false. + self.builder.switch_to_block(false_block); + self.builder.store( + result, + self.builder.load_field(self.layouts.state, state, FALSE_INDEX), + ); + self.builder.jump(after_block); + + self.builder.switch_to_block(after_block); + self.builder.load_untyped_pointer(result) + } + + fn check_division_overflow( + &self, + process_var: PointerValue<'ctx>, + lhs: IntValue<'ctx>, + rhs: IntValue<'ctx>, + ) { + let min = self.builder.i64_literal(i64::MIN); + let minus_one = self.builder.i64_literal(-1); + let zero = self.builder.i64_literal(0); + let and_block = self.builder.add_block(); + let or_block = self.builder.add_block(); + let overflow_block = self.builder.add_block(); + let ok_block = self.builder.add_block(); + + // lhs == MIN AND rhs == -1 + self.builder.branch(self.builder.int_eq(lhs, min), and_block, or_block); + + self.builder.switch_to_block(and_block); + self.builder.branch( + self.builder.int_eq(rhs, minus_one), + overflow_block, + or_block, + ); + + // OR rhs == 0 + self.builder.switch_to_block(or_block); + self.builder.branch( + self.builder.int_eq(rhs, zero), + overflow_block, + ok_block, + ); + + // The block to jump to if an overflow would occur. + self.builder.switch_to_block(overflow_block); + + let func = self.module.runtime_function(RuntimeFunction::IntOverflow); + let proc = self.builder.load_untyped_pointer(process_var); + + self.builder.call_void(func, &[proc.into(), lhs.into(), rhs.into()]); + self.builder.unreachable(); + + // The block to jump to when it's safe to perform the + // operation. + self.builder.switch_to_block(ok_block); + } + + fn check_shift_bits( + &self, + process_var: PointerValue<'ctx>, + value: IntValue<'ctx>, + bits: IntValue<'ctx>, + ) { + let ok_block = self.builder.add_block(); + let err_block = self.builder.add_block(); + let min = self.builder.i64_literal((i64::BITS - 1) as _); + let cond = self.builder.int_gt(bits, min); + + self.builder.branch(cond, err_block, ok_block); + + // The block to jump to when the operation would overflow. + self.builder.switch_to_block(err_block); + + let func = self.module.runtime_function(RuntimeFunction::IntOverflow); + let proc = self.builder.load_untyped_pointer(process_var); + + self.builder.call_void(func, &[proc.into(), value.into(), bits.into()]); + self.builder.unreachable(); + + // The block to jump to when all is well. + self.builder.switch_to_block(ok_block); + } + + fn define_register_variables(&mut self) { + let space = AddressSpace::default(); + + for index in 0..self.method.registers.len() { + let id = RegisterId(index as _); + let typ = self.method.registers.value_type(id); + let alloca_typ = if let Some(id) = typ.class_id(self.db) { + let layout = self.layouts.instances[&id]; + + if id.kind(self.db).is_extern() { + layout.as_basic_type_enum() + } else { + layout.ptr_type(space).as_basic_type_enum() + } + } else { + self.builder.context.pointer_type().as_basic_type_enum() + }; + + self.variables.insert(id, self.builder.alloca(alloca_typ)); + self.variable_types.insert(id, alloca_typ); + } + } + + fn register_type(&self, register: RegisterId) -> types::TypeRef { + self.method.registers.value_type(register) + } + + fn call( + &self, + register: RegisterId, + function: FunctionValue<'ctx>, + arguments: &[BasicMetadataValueEnum], + ) { + let var = self.variables[®ister]; + + if self.register_type(register).is_never(self.db) { + self.builder.call_void(function, arguments); + self.builder.unreachable(); + } else { + self.builder.store(var, self.builder.call(function, arguments)); + } + } + + fn indirect_call( + &self, + register: RegisterId, + function_type: FunctionType<'ctx>, + function: PointerValue<'ctx>, + arguments: &[BasicMetadataValueEnum], + ) { + let var = self.variables[®ister]; + + if self.register_type(register).is_never(self.db) { + self.builder.indirect_call(function_type, function, arguments); + self.builder.unreachable(); + } else { + self.builder.store( + var, + self.builder + .indirect_call(function_type, function, arguments) + .try_as_basic_value() + .left() + .unwrap(), + ); + } + } + + fn set_debug_location(&self, location_id: LocationId) { + let scope = self.builder.debug_scope(); + let (line, col) = self.mir.location(location_id).line_column(); + let loc = self.module.debug_builder.new_location(line, col, scope); + + self.builder.set_debug_location(loc); + } +} + +/// A pass for generating the entry module and method (i.e. `main()`). +pub(crate) struct GenerateMain<'a, 'ctx> { + db: &'a Database, + mir: &'a Mir, + layouts: &'a Layouts<'ctx>, + names: &'a SymbolNames, + context: &'ctx Context, + module: &'a Module<'a, 'ctx>, + builder: Builder<'ctx>, +} + +impl<'a, 'ctx> GenerateMain<'a, 'ctx> { + fn new( + db: &'a Database, + mir: &'a Mir, + layouts: &'a Layouts<'ctx>, + names: &'a SymbolNames, + context: &'ctx Context, + module: &'a Module<'a, 'ctx>, + ) -> GenerateMain<'a, 'ctx> { + let typ = context.i32_type().fn_type(&[], false); + let function = module.add_function("main", typ, None); + let builder = Builder::new(context, function); + + GenerateMain { db, mir, layouts, names, context, module, builder } + } + + fn run(self) { + let space = AddressSpace::default(); + let entry_block = self.builder.add_block(); + + self.builder.switch_to_block(entry_block); + + let layout = self.layouts.method_counts; + let counts = self.builder.alloca(layout); + + self.set_method_count(counts, ClassId::int()); + self.set_method_count(counts, ClassId::float()); + self.set_method_count(counts, ClassId::string()); + self.set_method_count(counts, ClassId::array()); + self.set_method_count(counts, ClassId::boolean()); + self.set_method_count(counts, ClassId::nil()); + self.set_method_count(counts, ClassId::byte_array()); + self.set_method_count(counts, ClassId::channel()); + + let rt_new = self.module.runtime_function(RuntimeFunction::RuntimeNew); + let rt_start = + self.module.runtime_function(RuntimeFunction::RuntimeStart); + let rt_state = + self.module.runtime_function(RuntimeFunction::RuntimeState); + let rt_drop = + self.module.runtime_function(RuntimeFunction::RuntimeDrop); + let exit = self.module.runtime_function(RuntimeFunction::Exit); + + let runtime = + self.builder.call(rt_new, &[counts.into()]).into_pointer_value(); + let state = + self.builder.call(rt_state, &[runtime.into()]).into_pointer_value(); + + // Call all the module setup functions. This is used to populate + // constants, define classes, etc. + for &id in self.mir.modules.keys() { + let name = &self.names.setup_functions[&id]; + let func = self.module.add_setup_function(name); + + self.builder.call_void(func, &[state.into()]); + } + + let main_class_id = self.db.main_class().unwrap(); + let main_method_id = self.db.main_method().unwrap(); + let main_class_ptr = self + .module + .add_global(&self.names.classes[&main_class_id]) + .as_pointer_value(); + + let main_method = self + .module + .add_function( + &self.names.methods[&main_method_id], + self.context.void_type().fn_type( + &[self.layouts.context.ptr_type(space).into()], + false, + ), + None, + ) + .as_global_value() + .as_pointer_value(); + + let main_class = + self.builder.load_pointer(self.layouts.empty_class, main_class_ptr); + + self.builder.call_void( + rt_start, + &[runtime.into(), main_class.into(), main_method.into()], + ); + + // We'll only reach this code upon successfully finishing the program. + // + // We don't drop the classes and other data as there's no point since + // we're exiting here. We _do_ drop the runtime in case we want to hook + // any additional logic into that step at some point, though technically + // this isn't necessary. + self.builder.call_void(rt_drop, &[runtime.into()]); + self.builder.call_void(exit, &[self.builder.i64_literal(0).into()]); + self.builder.unreachable(); + } + + fn methods(&self, id: ClassId) -> IntValue<'ctx> { + self.context.i16_type().const_int(self.layouts.methods(id) as _, false) + } + + fn set_method_count(&self, counts: PointerValue<'ctx>, class: ClassId) { + let layout = self.layouts.method_counts; + + self.builder.store_field(layout, counts, class.0, self.methods(class)); + } +} diff --git a/compiler/src/llvm/runtime_function.rs b/compiler/src/llvm/runtime_function.rs new file mode 100644 index 000000000..f97e1072d --- /dev/null +++ b/compiler/src/llvm/runtime_function.rs @@ -0,0 +1,1605 @@ +use crate::llvm::module::Module; +use inkwell::values::FunctionValue; +use inkwell::AddressSpace; + +#[derive(Copy, Clone)] +pub(crate) enum RuntimeFunction { + ArrayCapacity, + ArrayClear, + ArrayDrop, + ArrayGet, + ArrayLength, + ArrayNew, + ArrayNewPermanent, + ArrayPop, + ArrayPush, + ArrayRemove, + ArrayReserve, + ArraySet, + ByteArrayAppend, + ByteArrayClear, + ByteArrayClone, + ByteArrayCopyFrom, + ByteArrayDrainToString, + ByteArrayDrop, + ByteArrayEq, + ByteArrayGet, + ByteArrayLength, + ByteArrayNew, + ByteArrayPop, + ByteArrayPush, + ByteArrayRemove, + ByteArrayResize, + ByteArraySet, + ByteArraySlice, + ByteArrayToString, + ChannelDrop, + ChannelNew, + ChannelReceive, + ChannelReceiveUntil, + ChannelSend, + ChannelTryReceive, + ChannelWait, + CheckRefs, + ChildProcessDrop, + ChildProcessSpawn, + ChildProcessStderrClose, + ChildProcessStderrRead, + ChildProcessStdinClose, + ChildProcessStdinFlush, + ChildProcessStdinWriteBytes, + ChildProcessStdinWriteString, + ChildProcessStdoutClose, + ChildProcessStdoutRead, + ChildProcessTryWait, + ChildProcessWait, + ClassObject, + ClassProcess, + CpuCores, + DirectoryCreate, + DirectoryCreateRecursive, + DirectoryList, + DirectoryRemove, + DirectoryRemoveAll, + EnvArguments, + EnvExecutable, + EnvGet, + EnvGetWorkingDirectory, + EnvHomeDirectory, + EnvSetWorkingDirectory, + EnvTempDirectory, + EnvVariables, + Exit, + FileCopy, + FileDrop, + FileFlush, + FileOpen, + FileRead, + FileRemove, + FileSeek, + FileSize, + FileWriteBytes, + FileWriteString, + FloatBoxed, + FloatBoxedPermanent, + FloatClone, + FloatEq, + FloatRound, + FloatToString, + Free, + IntBoxed, + IntBoxedPermanent, + IntClone, + IntOverflow, + IntPow, + IntToString, + MessageNew, + Allocate, + PathAccessedAt, + PathCreatedAt, + PathExists, + PathExpand, + PathIsDirectory, + PathIsFile, + PathModifiedAt, + ProcessFinishMessage, + ProcessNew, + ProcessPanic, + ProcessSendMessage, + ProcessStackFrameLine, + ProcessStackFrameName, + ProcessStackFramePath, + ProcessStacktrace, + ProcessStacktraceDrop, + ProcessStacktraceLength, + ProcessSuspend, + RandomBytes, + RandomDrop, + RandomFloat, + RandomFloatRange, + RandomFromInt, + RandomInt, + RandomIntRange, + RandomNew, + Reduce, + RuntimeDrop, + RuntimeNew, + RuntimeStart, + RuntimeState, + SocketAccept, + SocketAddressPairAddress, + SocketAddressPairDrop, + SocketAddressPairPort, + SocketBind, + SocketConnect, + SocketDrop, + SocketListen, + SocketLocalAddress, + SocketNew, + SocketPeerAddress, + SocketRead, + SocketReceiveFrom, + SocketSendBytesTo, + SocketSendStringTo, + SocketSetBroadcast, + SocketSetKeepalive, + SocketSetLinger, + SocketSetNodelay, + SocketSetOnlyV6, + SocketSetRecvSize, + SocketSetReuseAddress, + SocketSetReusePort, + SocketSetSendSize, + SocketSetTtl, + SocketShutdownRead, + SocketShutdownReadWrite, + SocketShutdownWrite, + SocketTryClone, + SocketWriteBytes, + SocketWriteString, + StderrFlush, + StderrWriteBytes, + StderrWriteString, + StdinRead, + StdoutFlush, + StdoutWriteBytes, + StdoutWriteString, + StringByte, + StringCharacters, + StringCharactersDrop, + StringCharactersNext, + StringConcat, + StringConcatArray, + StringDrop, + StringEquals, + StringNewPermanent, + StringSize, + StringSliceBytes, + StringToByteArray, + StringToFloat, + StringToInt, + StringToLower, + StringToUpper, + TimeMonotonic, + TimeSystem, + TimeSystemOffset, +} + +impl RuntimeFunction { + pub(crate) fn name(self) -> &'static str { + match self { + RuntimeFunction::ArrayCapacity => "inko_array_capacity", + RuntimeFunction::ArrayClear => "inko_array_clear", + RuntimeFunction::ArrayDrop => "inko_array_drop", + RuntimeFunction::ArrayGet => "inko_array_get", + RuntimeFunction::ArrayLength => "inko_array_length", + RuntimeFunction::ArrayNew => "inko_array_new", + RuntimeFunction::ArrayNewPermanent => "inko_array_new_permanent", + RuntimeFunction::ArrayPop => "inko_array_pop", + RuntimeFunction::ArrayPush => "inko_array_push", + RuntimeFunction::ArrayRemove => "inko_array_remove", + RuntimeFunction::ArrayReserve => "inko_array_reserve", + RuntimeFunction::ArraySet => "inko_array_set", + RuntimeFunction::ByteArrayAppend => "inko_byte_array_append", + RuntimeFunction::ByteArrayClear => "inko_byte_array_clear", + RuntimeFunction::ByteArrayClone => "inko_byte_array_clone", + RuntimeFunction::ByteArrayCopyFrom => "inko_byte_array_copy_from", + RuntimeFunction::ByteArrayDrainToString => { + "inko_byte_array_drain_to_string" + } + RuntimeFunction::ByteArrayDrop => "inko_byte_array_drop", + RuntimeFunction::ByteArrayEq => "inko_byte_array_eq", + RuntimeFunction::ByteArrayGet => "inko_byte_array_get", + RuntimeFunction::ByteArrayLength => "inko_byte_array_length", + RuntimeFunction::ByteArrayNew => "inko_byte_array_new", + RuntimeFunction::ByteArrayPop => "inko_byte_array_pop", + RuntimeFunction::ByteArrayPush => "inko_byte_array_push", + RuntimeFunction::ByteArrayRemove => "inko_byte_array_remove", + RuntimeFunction::ByteArrayResize => "inko_byte_array_resize", + RuntimeFunction::ByteArraySet => "inko_byte_array_set", + RuntimeFunction::ByteArraySlice => "inko_byte_array_slice", + RuntimeFunction::ByteArrayToString => "inko_byte_array_to_string", + RuntimeFunction::ChannelDrop => "inko_channel_drop", + RuntimeFunction::ChannelNew => "inko_channel_new", + RuntimeFunction::ChannelReceive => "inko_channel_receive", + RuntimeFunction::ChannelReceiveUntil => { + "inko_channel_receive_until" + } + RuntimeFunction::ChannelSend => "inko_channel_send", + RuntimeFunction::ChannelTryReceive => "inko_channel_try_receive", + RuntimeFunction::ChannelWait => "inko_channel_wait", + RuntimeFunction::CheckRefs => "inko_check_refs", + RuntimeFunction::ChildProcessDrop => "inko_child_process_drop", + RuntimeFunction::ChildProcessSpawn => "inko_child_process_spawn", + RuntimeFunction::ChildProcessStderrClose => { + "inko_child_process_stderr_close" + } + RuntimeFunction::ChildProcessStderrRead => { + "inko_child_process_stderr_read" + } + RuntimeFunction::ChildProcessStdinClose => { + "inko_child_process_stdin_close" + } + RuntimeFunction::ChildProcessStdinFlush => { + "inko_child_process_stdin_flush" + } + RuntimeFunction::ChildProcessStdinWriteBytes => { + "inko_child_process_stdin_write_bytes" + } + RuntimeFunction::ChildProcessStdinWriteString => { + "inko_child_process_stdin_write_string" + } + RuntimeFunction::ChildProcessStdoutClose => { + "inko_child_process_stdout_close" + } + RuntimeFunction::ChildProcessStdoutRead => { + "inko_child_process_stdout_read" + } + RuntimeFunction::ChildProcessTryWait => { + "inko_child_process_try_wait" + } + RuntimeFunction::ChildProcessWait => "inko_child_process_wait", + RuntimeFunction::ClassObject => "inko_class_object", + RuntimeFunction::ClassProcess => "inko_class_process", + RuntimeFunction::CpuCores => "inko_cpu_cores", + RuntimeFunction::DirectoryCreate => "inko_directory_create", + RuntimeFunction::DirectoryCreateRecursive => { + "inko_directory_create_recursive" + } + RuntimeFunction::DirectoryList => "inko_directory_list", + RuntimeFunction::DirectoryRemove => "inko_directory_remove", + RuntimeFunction::DirectoryRemoveAll => "inko_directory_remove_all", + RuntimeFunction::EnvArguments => "inko_env_arguments", + RuntimeFunction::EnvExecutable => "inko_env_executable", + RuntimeFunction::EnvGet => "inko_env_get", + RuntimeFunction::EnvGetWorkingDirectory => { + "inko_env_get_working_directory" + } + RuntimeFunction::EnvHomeDirectory => "inko_env_home_directory", + RuntimeFunction::EnvSetWorkingDirectory => { + "inko_env_set_working_directory" + } + RuntimeFunction::EnvTempDirectory => "inko_env_temp_directory", + RuntimeFunction::EnvVariables => "inko_env_variables", + RuntimeFunction::Exit => "inko_exit", + RuntimeFunction::FileCopy => "inko_file_copy", + RuntimeFunction::FileDrop => "inko_file_drop", + RuntimeFunction::FileFlush => "inko_file_flush", + RuntimeFunction::FileOpen => "inko_file_open", + RuntimeFunction::FileRead => "inko_file_read", + RuntimeFunction::FileRemove => "inko_file_remove", + RuntimeFunction::FileSeek => "inko_file_seek", + RuntimeFunction::FileSize => "inko_file_size", + RuntimeFunction::FileWriteBytes => "inko_file_write_bytes", + RuntimeFunction::FileWriteString => "inko_file_write_string", + RuntimeFunction::FloatBoxed => "inko_float_boxed", + RuntimeFunction::FloatBoxedPermanent => { + "inko_float_boxed_permanent" + } + RuntimeFunction::FloatClone => "inko_float_clone", + RuntimeFunction::FloatEq => "inko_float_eq", + RuntimeFunction::FloatRound => "inko_float_round", + RuntimeFunction::FloatToString => "inko_float_to_string", + RuntimeFunction::Free => "inko_free", + RuntimeFunction::IntBoxed => "inko_int_boxed", + RuntimeFunction::IntBoxedPermanent => "inko_int_boxed_permanent", + RuntimeFunction::IntClone => "inko_int_clone", + RuntimeFunction::IntOverflow => "inko_int_overflow", + RuntimeFunction::IntPow => "inko_int_pow", + RuntimeFunction::IntToString => "inko_int_to_string", + RuntimeFunction::MessageNew => "inko_message_new", + RuntimeFunction::Allocate => "inko_alloc", + RuntimeFunction::PathAccessedAt => "inko_path_accessed_at", + RuntimeFunction::PathCreatedAt => "inko_path_created_at", + RuntimeFunction::PathExists => "inko_path_exists", + RuntimeFunction::PathIsDirectory => "inko_path_is_directory", + RuntimeFunction::PathIsFile => "inko_path_is_file", + RuntimeFunction::PathModifiedAt => "inko_path_modified_at", + RuntimeFunction::ProcessFinishMessage => { + "inko_process_finish_message" + } + RuntimeFunction::ProcessNew => "inko_process_new", + RuntimeFunction::ProcessPanic => "inko_process_panic", + RuntimeFunction::ProcessSendMessage => "inko_process_send_message", + RuntimeFunction::ProcessStacktrace => "inko_process_stacktrace", + RuntimeFunction::ProcessStacktraceDrop => { + "inko_process_stacktrace_drop" + } + RuntimeFunction::ProcessStackFrameLine => { + "inko_process_stack_frame_line" + } + RuntimeFunction::ProcessStackFrameName => { + "inko_process_stack_frame_name" + } + RuntimeFunction::ProcessStackFramePath => { + "inko_process_stack_frame_path" + } + RuntimeFunction::ProcessStacktraceLength => { + "inko_process_stacktrace_length" + } + RuntimeFunction::ProcessSuspend => "inko_process_suspend", + RuntimeFunction::RandomBytes => "inko_random_bytes", + RuntimeFunction::RandomDrop => "inko_random_drop", + RuntimeFunction::RandomFloat => "inko_random_float", + RuntimeFunction::RandomFloatRange => "inko_random_float_range", + RuntimeFunction::RandomFromInt => "inko_random_from_int", + RuntimeFunction::RandomInt => "inko_random_int", + RuntimeFunction::RandomIntRange => "inko_random_int_range", + RuntimeFunction::RandomNew => "inko_random_new", + RuntimeFunction::Reduce => "inko_reduce", + RuntimeFunction::RuntimeDrop => "inko_runtime_drop", + RuntimeFunction::RuntimeNew => "inko_runtime_new", + RuntimeFunction::RuntimeStart => "inko_runtime_start", + RuntimeFunction::RuntimeState => "inko_runtime_state", + RuntimeFunction::SocketAccept => "inko_socket_accept", + RuntimeFunction::SocketAddressPairAddress => { + "inko_socket_address_pair_address" + } + RuntimeFunction::SocketAddressPairDrop => { + "inko_socket_address_pair_drop" + } + RuntimeFunction::SocketAddressPairPort => { + "inko_socket_address_pair_port" + } + RuntimeFunction::SocketBind => "inko_socket_bind", + RuntimeFunction::SocketConnect => "inko_socket_connect", + RuntimeFunction::SocketDrop => "inko_socket_drop", + RuntimeFunction::SocketListen => "inko_socket_listen", + RuntimeFunction::SocketLocalAddress => "inko_socket_local_address", + RuntimeFunction::SocketPeerAddress => "inko_socket_peer_address", + RuntimeFunction::SocketRead => "inko_socket_read", + RuntimeFunction::SocketReceiveFrom => "inko_socket_receive_from", + RuntimeFunction::SocketSendBytesTo => "inko_socket_send_bytes_to", + RuntimeFunction::SocketSendStringTo => "inko_socket_send_string_to", + RuntimeFunction::SocketSetBroadcast => "inko_socket_set_broadcast", + RuntimeFunction::SocketSetKeepalive => "inko_socket_set_keepalive", + RuntimeFunction::SocketSetLinger => "inko_socket_set_linger", + RuntimeFunction::SocketSetNodelay => "inko_socket_set_nodelay", + RuntimeFunction::SocketSetOnlyV6 => "inko_socket_set_only_v6", + RuntimeFunction::SocketSetRecvSize => "inko_socket_set_recv_size", + RuntimeFunction::SocketSetReuseAddress => { + "inko_socket_set_reuse_address" + } + RuntimeFunction::SocketSetReusePort => "inko_socket_set_reuse_port", + RuntimeFunction::SocketSetSendSize => "inko_socket_set_send_size", + RuntimeFunction::SocketSetTtl => "inko_socket_set_ttl", + RuntimeFunction::SocketShutdownRead => "inko_socket_shutdown_read", + RuntimeFunction::SocketShutdownReadWrite => { + "inko_socket_shutdown_read_write" + } + RuntimeFunction::SocketShutdownWrite => { + "inko_socket_shutdown_write" + } + RuntimeFunction::SocketTryClone => "inko_socket_try_clone", + RuntimeFunction::SocketNew => "inko_socket_new", + RuntimeFunction::SocketWriteBytes => "inko_socket_write_bytes", + RuntimeFunction::SocketWriteString => "inko_socket_write_string", + RuntimeFunction::StderrFlush => "inko_stderr_flush", + RuntimeFunction::StderrWriteBytes => "inko_stderr_write_bytes", + RuntimeFunction::StderrWriteString => "inko_stderr_write_string", + RuntimeFunction::StdinRead => "inko_stdin_read", + RuntimeFunction::StdoutFlush => "inko_stdout_flush", + RuntimeFunction::StdoutWriteBytes => "inko_stdout_write_bytes", + RuntimeFunction::StdoutWriteString => "inko_stdout_write_string", + RuntimeFunction::StringByte => "inko_string_byte", + RuntimeFunction::StringCharacters => "inko_string_characters", + RuntimeFunction::StringCharactersDrop => { + "inko_string_characters_drop" + } + RuntimeFunction::StringCharactersNext => { + "inko_string_characters_next" + } + RuntimeFunction::StringConcat => "inko_string_concat", + RuntimeFunction::StringConcatArray => "inko_string_concat_array", + RuntimeFunction::StringDrop => "inko_string_drop", + RuntimeFunction::StringEquals => "inko_string_equals", + RuntimeFunction::StringNewPermanent => "inko_string_new_permanent", + RuntimeFunction::StringSize => "inko_string_size", + RuntimeFunction::StringSliceBytes => "inko_string_slice_bytes", + RuntimeFunction::StringToByteArray => "inko_string_to_byte_array", + RuntimeFunction::StringToFloat => "inko_string_to_float", + RuntimeFunction::StringToInt => "inko_string_to_int", + RuntimeFunction::StringToLower => "inko_string_to_lower", + RuntimeFunction::StringToUpper => "inko_string_to_upper", + RuntimeFunction::TimeMonotonic => "inko_time_monotonic", + RuntimeFunction::TimeSystem => "inko_time_system", + RuntimeFunction::TimeSystemOffset => "inko_time_system_offset", + RuntimeFunction::PathExpand => "inko_path_expand", + } + } + + pub(crate) fn build<'ctx>( + self, + module: &Module<'_, 'ctx>, + ) -> FunctionValue<'ctx> { + let context = module.context; + let space = AddressSpace::default(); + let fn_type = match self { + RuntimeFunction::IntBoxedPermanent => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::IntBoxed => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::IntClone => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.pointer_type(); + + val.fn_type(&[state, val.into()], false) + } + RuntimeFunction::IntOverflow => { + let proc = context.pointer_type().into(); + let lhs = context.i64_type().into(); + let rhs = context.i64_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[proc, lhs, rhs], false) + } + RuntimeFunction::CheckRefs => { + let proc = context.pointer_type().into(); + let val = context.pointer_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[proc, val], false) + } + RuntimeFunction::Free => { + let val = context.pointer_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[val], false) + } + RuntimeFunction::FloatBoxedPermanent => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.f64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::FloatClone => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.pointer_type(); + + val.fn_type(&[state, val.into()], false) + } + RuntimeFunction::ArrayNewPermanent => { + let state = module.layouts.state.ptr_type(space).into(); + let capa = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, capa], false) + } + RuntimeFunction::ArrayNew => { + let state = module.layouts.state.ptr_type(space).into(); + let capa = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, capa], false) + } + RuntimeFunction::ArrayPush => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let val = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array, val], false) + } + RuntimeFunction::Reduce => { + let proc = context.pointer_type().into(); + let amount = context.i16_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[proc, amount], false) + } + RuntimeFunction::Allocate => { + let class = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[class], false) + } + RuntimeFunction::StringEquals => { + let state = module.layouts.state.ptr_type(space).into(); + let lhs = context.pointer_type().into(); + let rhs = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, lhs, rhs], false) + } + RuntimeFunction::ProcessPanic => { + let proc = context.pointer_type().into(); + let val = context.pointer_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[proc, val], false) + } + RuntimeFunction::IntPow => { + let proc = context.pointer_type().into(); + let lhs = context.i64_type().into(); + let rhs = context.i64_type().into(); + let ret = context.i64_type(); + + ret.fn_type(&[proc, lhs, rhs], false) + } + RuntimeFunction::FloatBoxed => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.f64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::FloatEq => { + let state = module.layouts.state.ptr_type(space).into(); + let lhs = context.f64_type().into(); + let rhs = context.f64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, lhs, rhs], false) + } + RuntimeFunction::FloatRound => { + let state = module.layouts.state.ptr_type(space).into(); + let lhs = context.f64_type().into(); + let rhs = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, lhs, rhs], false) + } + RuntimeFunction::FloatToString => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.f64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::ArrayCapacity => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::ArrayClear => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::ArrayDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let val = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, val], false) + } + RuntimeFunction::ArrayGet => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index], false) + } + RuntimeFunction::ArrayLength => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ArrayPop => { + let array = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[array], false) + } + RuntimeFunction::ArrayRemove => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index], false) + } + RuntimeFunction::ArrayReserve => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let amount = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array, amount], false) + } + RuntimeFunction::ArraySet => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let value = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index, value], false) + } + RuntimeFunction::ByteArrayNew => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::ByteArrayPush => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let value = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array, value], false) + } + RuntimeFunction::ByteArrayPop => { + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array], false) + } + RuntimeFunction::ByteArraySet => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let value = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index, value], false) + } + RuntimeFunction::ByteArrayGet => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index], false) + } + RuntimeFunction::ByteArrayRemove => { + let array = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[array, index], false) + } + RuntimeFunction::ByteArrayLength => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArrayEq => { + let state = module.layouts.state.ptr_type(space).into(); + let lhs = context.pointer_type().into(); + let rhs = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, lhs, rhs], false) + } + RuntimeFunction::ByteArrayClear => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArrayClone => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArrayDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArrayToString => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArrayDrainToString => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array], false) + } + RuntimeFunction::ByteArraySlice => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let start = context.i64_type().into(); + let length = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array, start, length], false) + } + RuntimeFunction::ByteArrayAppend => { + let state = module.layouts.state.ptr_type(space).into(); + let target = context.pointer_type().into(); + let source = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, target, source], false) + } + RuntimeFunction::ByteArrayCopyFrom => { + let state = module.layouts.state.ptr_type(space).into(); + let target = context.pointer_type().into(); + let source = context.pointer_type().into(); + let start = context.i64_type().into(); + let length = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, target, source, start, length], false) + } + RuntimeFunction::ByteArrayResize => { + let state = module.layouts.state.ptr_type(space).into(); + let array = context.pointer_type().into(); + let size = context.i64_type().into(); + let filler = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, array, size, filler], false) + } + RuntimeFunction::ChildProcessSpawn => { + let proc = context.pointer_type().into(); + let program = context.pointer_type().into(); + let args = context.pointer_type().into(); + let env = context.pointer_type().into(); + let stdin = context.i64_type().into(); + let stdout = context.i64_type().into(); + let stderr = context.i64_type().into(); + let dir = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type( + &[proc, program, args, env, stdin, stdout, stderr, dir], + false, + ) + } + RuntimeFunction::ChildProcessWait => { + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[proc, child], false) + } + RuntimeFunction::ChildProcessTryWait => { + let child = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[child], false) + } + RuntimeFunction::ChildProcessDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let child = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, child], false) + } + RuntimeFunction::ChildProcessStdoutRead => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let size = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, child, buffer, size], false) + } + RuntimeFunction::ChildProcessStderrRead => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let size = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, child, buffer, size], false) + } + RuntimeFunction::ChildProcessStderrClose => { + let state = module.layouts.state.ptr_type(space).into(); + let child = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, child], false) + } + RuntimeFunction::ChildProcessStdoutClose => { + let state = module.layouts.state.ptr_type(space).into(); + let child = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, child], false) + } + RuntimeFunction::ChildProcessStdinClose => { + let state = module.layouts.state.ptr_type(space).into(); + let child = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, child], false) + } + RuntimeFunction::ChildProcessStdinFlush => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, child], false) + } + RuntimeFunction::ChildProcessStdinWriteBytes => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let input = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, child, input], false) + } + RuntimeFunction::ChildProcessStdinWriteString => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let child = context.pointer_type().into(); + let input = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, child, input], false) + } + RuntimeFunction::CpuCores => { + let ret = context.pointer_type(); + + ret.fn_type(&[], false) + } + RuntimeFunction::ProcessFinishMessage => { + let proc = context.pointer_type().into(); + let terminate = context.bool_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[proc, terminate], false) + } + RuntimeFunction::RuntimeNew => { + let counts = + module.layouts.method_counts.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[counts], false) + } + RuntimeFunction::RuntimeDrop => { + let runtime = context.pointer_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[runtime], false) + } + RuntimeFunction::RuntimeStart => { + let runtime = context.pointer_type().into(); + let class = context.pointer_type().into(); + let method = context.pointer_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[runtime, class, method], false) + } + RuntimeFunction::RuntimeState => { + let runtime = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[runtime], false) + } + RuntimeFunction::ClassObject => { + let name = context.pointer_type().into(); + let fields = context.i8_type().into(); + let methods = context.i16_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[name, fields, methods], false) + } + RuntimeFunction::ClassProcess => { + let name = context.pointer_type().into(); + let fields = context.i8_type().into(); + let methods = context.i16_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[name, fields, methods], false) + } + RuntimeFunction::MessageNew => { + let method = context.pointer_type().into(); + let length = context.i8_type().into(); + let ret = module.layouts.message.ptr_type(space); + + ret.fn_type(&[method, length], false) + } + RuntimeFunction::ProcessSendMessage => { + let state = module.layouts.state.ptr_type(space).into(); + let sender = context.pointer_type().into(); + let receiver = context.pointer_type().into(); + let message = module.layouts.message.ptr_type(space).into(); + let ret = context.void_type(); + + ret.fn_type(&[state, sender, receiver, message], false) + } + RuntimeFunction::ProcessNew => { + let process = context.pointer_type().into(); + let class = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[process, class], false) + } + RuntimeFunction::TimeMonotonic => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::IntToString => { + let state = module.layouts.state.ptr_type(space).into(); + let value = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, value], false) + } + RuntimeFunction::ChannelDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let chan = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, chan], false) + } + RuntimeFunction::ChannelNew => { + let state = module.layouts.state.ptr_type(space).into(); + let capacity = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, capacity], false) + } + RuntimeFunction::ChannelReceive => { + let proc = context.pointer_type().into(); + let chan = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[proc, chan], false) + } + RuntimeFunction::ChannelReceiveUntil => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let channel = context.pointer_type().into(); + let time = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, channel, time], false) + } + RuntimeFunction::ChannelSend => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let channel = context.pointer_type().into(); + let message = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc, channel, message], false) + } + RuntimeFunction::ChannelTryReceive => { + let proc = context.pointer_type().into(); + let channel = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[proc, channel], false) + } + RuntimeFunction::ChannelWait => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let channels = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc, channels], false) + } + RuntimeFunction::DirectoryCreate + | RuntimeFunction::DirectoryCreateRecursive + | RuntimeFunction::DirectoryList + | RuntimeFunction::DirectoryRemove + | RuntimeFunction::DirectoryRemoveAll => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::EnvArguments => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvExecutable => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = module.layouts.result; + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvGet => { + let state = module.layouts.state.ptr_type(space).into(); + let name = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, name], false) + } + RuntimeFunction::EnvGetWorkingDirectory => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = module.layouts.result; + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvHomeDirectory => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = module.layouts.result; + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvSetWorkingDirectory => { + let state = module.layouts.state.ptr_type(space).into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, path], false) + } + RuntimeFunction::EnvTempDirectory => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::EnvVariables => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::Exit => { + let status = context.i64_type().into(); + let ret = context.void_type(); + + ret.fn_type(&[status], false) + } + RuntimeFunction::FileCopy => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let from = context.pointer_type().into(); + let to = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, from, to], false) + } + RuntimeFunction::FileDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let file = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, file], false) + } + RuntimeFunction::FileFlush => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let file = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, file], false) + } + RuntimeFunction::FileOpen => { + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let mode = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[proc, path, mode], false) + } + RuntimeFunction::FileRead => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let file = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let size = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, file, buffer, size], false) + } + RuntimeFunction::FileRemove => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::FileSeek => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let file = context.pointer_type().into(); + let offset = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, file, offset], false) + } + RuntimeFunction::FileSize => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::FileWriteBytes + | RuntimeFunction::FileWriteString => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let file = context.pointer_type().into(); + let input = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, file, input], false) + } + RuntimeFunction::PathAccessedAt + | RuntimeFunction::PathCreatedAt + | RuntimeFunction::PathModifiedAt => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::PathExpand => { + let state = module.layouts.state.ptr_type(space).into(); + let path = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, path], false) + } + RuntimeFunction::PathExists + | RuntimeFunction::PathIsDirectory + | RuntimeFunction::PathIsFile => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let path = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc, path], false) + } + RuntimeFunction::ProcessStacktrace => { + let proc = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[proc], false) + } + RuntimeFunction::ProcessStackFrameLine => { + let state = module.layouts.state.ptr_type(space).into(); + let trace = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, trace, index], false) + } + RuntimeFunction::ProcessStackFrameName => { + let state = module.layouts.state.ptr_type(space).into(); + let trace = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, trace, index], false) + } + RuntimeFunction::ProcessStackFramePath => { + let state = module.layouts.state.ptr_type(space).into(); + let trace = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, trace, index], false) + } + RuntimeFunction::ProcessStacktraceLength => { + let state = module.layouts.state.ptr_type(space).into(); + let trace = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, trace], false) + } + RuntimeFunction::ProcessStacktraceDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let trace = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, trace], false) + } + RuntimeFunction::ProcessSuspend => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let time = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc, time], false) + } + RuntimeFunction::RandomBytes => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let rng = context.pointer_type().into(); + let size = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc, rng, size], false) + } + RuntimeFunction::RandomDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let rng = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, rng], false) + } + RuntimeFunction::RandomFloat => { + let state = module.layouts.state.ptr_type(space).into(); + let rng = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, rng], false) + } + RuntimeFunction::RandomFloatRange => { + let state = module.layouts.state.ptr_type(space).into(); + let rng = context.pointer_type().into(); + let min = context.f64_type().into(); + let max = context.f64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, rng, min, max], false) + } + RuntimeFunction::RandomFromInt => { + let seed = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[seed], false) + } + RuntimeFunction::RandomInt => { + let state = module.layouts.state.ptr_type(space).into(); + let rng = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, rng], false) + } + RuntimeFunction::RandomIntRange => { + let state = module.layouts.state.ptr_type(space).into(); + let rng = context.pointer_type().into(); + let min = context.i64_type().into(); + let max = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, rng, min, max], false) + } + RuntimeFunction::RandomNew => { + let proc = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[proc], false) + } + RuntimeFunction::SocketAccept => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let socket = context.pointer_type().into(); + let deadline = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, socket, deadline], false) + } + RuntimeFunction::SocketAddressPairAddress => { + let pair = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[pair], false) + } + RuntimeFunction::SocketAddressPairDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let pair = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, pair], false) + } + RuntimeFunction::SocketAddressPairPort => { + let pair = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[pair], false) + } + RuntimeFunction::SocketBind => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let address = context.pointer_type().into(); + let port = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket, address, port], false) + } + RuntimeFunction::SocketConnect => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let socket = context.pointer_type().into(); + let address = context.pointer_type().into(); + let port = context.i64_type().into(); + let deadline = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type( + &[state, proc, socket, address, port, deadline], + false, + ) + } + RuntimeFunction::SocketDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, socket], false) + } + RuntimeFunction::SocketListen => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let value = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket, value], false) + } + RuntimeFunction::SocketLocalAddress + | RuntimeFunction::SocketPeerAddress => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket], false) + } + RuntimeFunction::SocketRead + | RuntimeFunction::SocketReceiveFrom => { + let state = module.layouts.state.ptr_type(space).into(); + let process = context.pointer_type().into(); + let socket = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let amount = context.i64_type().into(); + let deadline = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type( + &[state, process, socket, buffer, amount, deadline], + false, + ) + } + RuntimeFunction::SocketSendBytesTo + | RuntimeFunction::SocketSendStringTo => { + let state = module.layouts.state.ptr_type(space).into(); + let process = context.pointer_type().into(); + let socket = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let address = context.pointer_type().into(); + let port = context.i64_type().into(); + let deadline = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type( + &[state, process, socket, buffer, address, port, deadline], + false, + ) + } + RuntimeFunction::SocketSetBroadcast + | RuntimeFunction::SocketSetKeepalive + | RuntimeFunction::SocketSetNodelay + | RuntimeFunction::SocketSetOnlyV6 + | RuntimeFunction::SocketSetReuseAddress + | RuntimeFunction::SocketSetReusePort => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let value = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket, value], false) + } + RuntimeFunction::SocketSetLinger + | RuntimeFunction::SocketSetRecvSize + | RuntimeFunction::SocketSetSendSize + | RuntimeFunction::SocketSetTtl => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let value = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket, value], false) + } + RuntimeFunction::SocketShutdownRead + | RuntimeFunction::SocketShutdownReadWrite + | RuntimeFunction::SocketShutdownWrite => { + let state = module.layouts.state.ptr_type(space).into(); + let socket = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, socket], false) + } + RuntimeFunction::SocketTryClone => { + let socket = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[socket], false) + } + RuntimeFunction::StderrFlush | RuntimeFunction::StdoutFlush => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, proc], false) + } + RuntimeFunction::StdoutWriteString + | RuntimeFunction::StdoutWriteBytes + | RuntimeFunction::StderrWriteString + | RuntimeFunction::StderrWriteBytes => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let input = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, input], false) + } + RuntimeFunction::StdinRead => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let size = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, buffer, size], false) + } + RuntimeFunction::StringByte => { + let string = context.pointer_type().into(); + let index = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[string, index], false) + } + RuntimeFunction::StringCharacters => { + let string = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[string], false) + } + RuntimeFunction::StringCharactersDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let input = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, input], false) + } + RuntimeFunction::StringCharactersNext => { + let state = module.layouts.state.ptr_type(space).into(); + let input = context.pointer_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, input], false) + } + RuntimeFunction::StringConcat => { + let state = module.layouts.state.ptr_type(space).into(); + let strings = context.pointer_type().into(); + let length = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, strings, length], false) + } + RuntimeFunction::StringConcatArray => { + let state = module.layouts.state.ptr_type(space).into(); + let strings = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, strings], false) + } + RuntimeFunction::StringDrop => { + let state = module.layouts.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, string], false) + } + RuntimeFunction::StringNewPermanent => { + let state = module.layouts.state.ptr_type(space).into(); + let bytes = context.pointer_type().into(); + let length = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, bytes, length], false) + } + RuntimeFunction::StringSize => { + let state = module.layouts.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, string], false) + } + RuntimeFunction::StringSliceBytes => { + let state = module.layouts.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let start = context.i64_type().into(); + let length = context.i64_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, string, start, length], false) + } + RuntimeFunction::StringToByteArray + | RuntimeFunction::StringToLower + | RuntimeFunction::StringToUpper => { + let state = module.layouts.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state, string], false) + } + RuntimeFunction::StringToFloat => { + let state = module.layouts.state.ptr_type(space).into(); + let string = context.pointer_type().into(); + let start = context.i64_type().into(); + let end = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, string, start, end], false) + } + RuntimeFunction::StringToInt => { + let state = module.layouts.state.ptr_type(space).into(); + let proc = context.pointer_type().into(); + let string = context.pointer_type().into(); + let radix = context.i64_type().into(); + let start = context.i64_type().into(); + let end = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, proc, string, radix, start, end], false) + } + RuntimeFunction::TimeSystem | RuntimeFunction::TimeSystemOffset => { + let state = module.layouts.state.ptr_type(space).into(); + let ret = context.pointer_type(); + + ret.fn_type(&[state], false) + } + RuntimeFunction::SocketNew => { + let proto = context.i64_type().into(); + let kind = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[proto, kind], false) + } + RuntimeFunction::SocketWriteBytes + | RuntimeFunction::SocketWriteString => { + let state = module.layouts.state.ptr_type(space).into(); + let process = context.pointer_type().into(); + let socket = context.pointer_type().into(); + let buffer = context.pointer_type().into(); + let deadline = context.i64_type().into(); + let ret = module.layouts.result; + + ret.fn_type(&[state, process, socket, buffer, deadline], false) + } + }; + + module.add_function(self.name(), fn_type, None) + } +} diff --git a/compiler/src/mir/mod.rs b/compiler/src/mir/mod.rs index d4a7d71cb..f5e8f8a32 100644 --- a/compiler/src/mir/mod.rs +++ b/compiler/src/mir/mod.rs @@ -3,11 +3,12 @@ //! MIR is used for various optimisations, analysing moves of values, compiling //! pattern matching into decision trees, and more. use ast::source_location::SourceLocation; -use bytecode::{BuiltinFunction, Opcode}; use std::collections::{HashMap, HashSet}; use std::fmt; use std::hash::{Hash, Hasher}; +use std::rc::Rc; use types::collections::IndexMap; +use types::BuiltinFunction; /// The number of reductions to perform after calling a method. const CALL_COST: u16 = 1; @@ -31,14 +32,14 @@ impl Registers { } pub(crate) fn alloc(&mut self, value_type: types::TypeRef) -> RegisterId { - let id = self.values.len(); + let id = self.values.len() as _; self.values.push(Register { value_type }); RegisterId(id) } pub(crate) fn get(&self, register: RegisterId) -> &Register { - &self.values[register.0] + &self.values[register.0 as usize] } pub(crate) fn value_type(&self, register: RegisterId) -> types::TypeRef { @@ -86,7 +87,7 @@ impl Graph { } pub(crate) fn is_connected(&self, block: BlockId) -> bool { - block.0 == 0 || !self.blocks[block.0].predecessors.is_empty() + block == self.start_id || !self.blocks[block.0].predecessors.is_empty() } pub(crate) fn predecessors(&self, block: BlockId) -> Vec { @@ -167,24 +168,26 @@ impl Block { }))); } - pub(crate) fn branch_result( + pub(crate) fn switch( &mut self, - ok: BlockId, - error: BlockId, + register: RegisterId, + blocks: Vec, location: LocationId, ) { - self.instructions.push(Instruction::BranchResult(Box::new( - BranchResult { ok, error, location }, - ))); + self.instructions.push(Instruction::Switch(Box::new(Switch { + register, + blocks, + location, + }))); } - pub(crate) fn jump_table( + pub(crate) fn switch_kind( &mut self, register: RegisterId, blocks: Vec, location: LocationId, ) { - self.instructions.push(Instruction::JumpTable(Box::new(JumpTable { + self.instructions.push(Instruction::SwitchKind(Box::new(SwitchKind { register, blocks, location, @@ -200,39 +203,6 @@ impl Block { .push(Instruction::Return(Box::new(Return { register, location }))); } - pub(crate) fn throw_value( - &mut self, - register: RegisterId, - location: LocationId, - ) { - self.instructions - .push(Instruction::Throw(Box::new(Throw { register, location }))); - } - - pub(crate) fn return_async_value( - &mut self, - register: RegisterId, - value: RegisterId, - location: LocationId, - ) { - self.instructions.push(Instruction::ReturnAsync(Box::new( - ReturnAsync { register, value, location }, - ))); - } - - pub(crate) fn throw_async_value( - &mut self, - register: RegisterId, - value: RegisterId, - location: LocationId, - ) { - self.instructions.push(Instruction::ThrowAsync(Box::new(ThrowAsync { - register, - value, - location, - }))); - } - pub(crate) fn finish(&mut self, terminate: bool, location: LocationId) { self.instructions.push(Instruction::Finish(Box::new(Finish { location, @@ -312,24 +282,13 @@ impl Block { }))); } - pub(crate) fn allocate_array( + pub(crate) fn array( &mut self, register: RegisterId, values: Vec, location: LocationId, ) { - self.instructions.push(Instruction::AllocateArray(Box::new( - AllocateArray { register, values, location }, - ))); - } - - pub(crate) fn strings( - &mut self, - register: RegisterId, - values: Vec, - location: LocationId, - ) { - self.instructions.push(Instruction::Strings(Box::new(Strings { + self.instructions.push(Instruction::Array(Box::new(Array { register, values, location, @@ -347,6 +306,19 @@ impl Block { ))); } + pub(crate) fn reference( + &mut self, + register: RegisterId, + value: RegisterId, + location: LocationId, + ) { + self.instructions.push(Instruction::Reference(Box::new(Reference { + register, + value, + location, + }))); + } + pub(crate) fn increment( &mut self, register: RegisterId, @@ -371,14 +343,26 @@ impl Block { }))); } - pub(crate) fn decrement_atomic( + pub(crate) fn increment_atomic( &mut self, register: RegisterId, value: RegisterId, location: LocationId, + ) { + self.instructions.push(Instruction::IncrementAtomic(Box::new( + IncrementAtomic { register, value, location }, + ))); + } + + pub(crate) fn decrement_atomic( + &mut self, + register: RegisterId, + if_true: BlockId, + if_false: BlockId, + location: LocationId, ) { self.instructions.push(Instruction::DecrementAtomic(Box::new( - DecrementAtomic { register, value, location }, + DecrementAtomic { register, if_true, if_false, location }, ))); } @@ -433,38 +417,16 @@ impl Block { }))); } - pub(crate) fn ref_kind( - &mut self, - register: RegisterId, - value: RegisterId, - location: LocationId, - ) { - self.instructions.push(Instruction::RefKind(Box::new(RefKind { - register, - value, - location, - }))); - } - - pub(crate) fn move_result( - &mut self, - register: RegisterId, - location: LocationId, - ) { - self.instructions.push(Instruction::MoveResult(Box::new(MoveResult { - register, - location, - }))); - } - pub(crate) fn call_static( &mut self, + register: RegisterId, class: types::ClassId, method: types::MethodId, arguments: Vec, location: LocationId, ) { self.instructions.push(Instruction::CallStatic(Box::new(CallStatic { + register, class, method, arguments, @@ -472,90 +434,68 @@ impl Block { }))); } - pub(crate) fn call_virtual( + pub(crate) fn call_instance( &mut self, + register: RegisterId, receiver: RegisterId, method: types::MethodId, arguments: Vec, location: LocationId, ) { - self.instructions.push(Instruction::CallVirtual(Box::new( - CallVirtual { receiver, method, arguments, location }, + self.instructions.push(Instruction::CallInstance(Box::new( + CallInstance { register, receiver, method, arguments, location }, ))); } pub(crate) fn call_dynamic( &mut self, + register: RegisterId, receiver: RegisterId, method: types::MethodId, arguments: Vec, location: LocationId, ) { self.instructions.push(Instruction::CallDynamic(Box::new( - CallDynamic { receiver, method, arguments, location }, + CallDynamic { register, receiver, method, arguments, location }, ))); } pub(crate) fn call_closure( &mut self, + register: RegisterId, receiver: RegisterId, arguments: Vec, location: LocationId, ) { self.instructions.push(Instruction::CallClosure(Box::new( - CallClosure { receiver, arguments, location }, + CallClosure { register, receiver, arguments, location }, ))); } pub(crate) fn call_dropper( &mut self, + register: RegisterId, receiver: RegisterId, location: LocationId, ) { self.instructions.push(Instruction::CallDropper(Box::new( - CallDropper { receiver, location }, + CallDropper { register, receiver, location }, ))); } pub(crate) fn call_builtin( &mut self, - id: BuiltinFunction, + register: RegisterId, + name: BuiltinFunction, arguments: Vec, location: LocationId, ) { self.instructions.push(Instruction::CallBuiltin(Box::new( - CallBuiltin { id, arguments, location }, + CallBuiltin { register, name, arguments, location }, ))); } - pub(crate) fn raw_instruction( - &mut self, - opcode: Opcode, - arguments: Vec, - location: LocationId, - ) { - self.instructions.push(Instruction::RawInstruction(Box::new( - RawInstruction { opcode, arguments, location }, - ))); - } - - pub(crate) fn send_and_wait( - &mut self, - receiver: RegisterId, - method: types::MethodId, - arguments: Vec, - location: LocationId, - ) { - self.instructions.push(Instruction::Send(Box::new(Send { - receiver, - method, - arguments, - location, - wait: true, - }))); - } - - pub(crate) fn send_and_forget( + pub(crate) fn send( &mut self, receiver: RegisterId, method: types::MethodId, @@ -567,24 +507,6 @@ impl Block { method, arguments, location, - wait: false, - }))); - } - - pub(crate) fn send_async( - &mut self, - register: RegisterId, - receiver: RegisterId, - method: types::MethodId, - arguments: Vec, - location: LocationId, - ) { - self.instructions.push(Instruction::SendAsync(Box::new(SendAsync { - register, - receiver, - method, - arguments, - location, }))); } @@ -592,10 +514,12 @@ impl Block { &mut self, register: RegisterId, receiver: RegisterId, + class: types::ClassId, field: types::FieldId, location: LocationId, ) { self.instructions.push(Instruction::GetField(Box::new(GetField { + class, register, receiver, field, @@ -606,6 +530,7 @@ impl Block { pub(crate) fn set_field( &mut self, receiver: RegisterId, + class: types::ClassId, field: types::FieldId, value: RegisterId, location: LocationId, @@ -613,6 +538,7 @@ impl Block { self.instructions.push(Instruction::SetField(Box::new(SetField { receiver, value, + class, field, location, }))); @@ -631,45 +557,28 @@ impl Block { }))); } - pub(crate) fn get_constant( - &mut self, - register: RegisterId, - id: types::ConstantId, - location: LocationId, - ) { - self.instructions.push(Instruction::GetConstant(Box::new( - GetConstant { register, id, location }, - ))); - } - - pub(crate) fn string_eq( + pub(crate) fn spawn( &mut self, register: RegisterId, - left: RegisterId, - right: RegisterId, + class: types::ClassId, location: LocationId, ) { - self.instructions.push(Instruction::StringEq(Box::new(StringEq { + self.instructions.push(Instruction::Spawn(Box::new(Spawn { register, - left, - right, + class, location, - }))) + }))); } - pub(crate) fn int_eq( + pub(crate) fn get_constant( &mut self, register: RegisterId, - left: RegisterId, - right: RegisterId, + id: types::ConstantId, location: LocationId, ) { - self.instructions.push(Instruction::IntEq(Box::new(IntEq { - register, - left, - right, - location, - }))) + self.instructions.push(Instruction::GetConstant(Box::new( + GetConstant { register, id, location }, + ))); } pub(crate) fn reduce(&mut self, amount: u16, location: LocationId) { @@ -686,8 +595,8 @@ impl Block { pub(crate) enum Constant { Int(i64), Float(f64), - String(String), - Array(Vec), + String(Rc), + Array(Rc>), } impl PartialEq for Constant { @@ -746,7 +655,7 @@ pub(crate) struct Register { } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub(crate) struct RegisterId(pub(crate) usize); +pub(crate) struct RegisterId(pub(crate) u32); #[derive(Clone)] pub(crate) struct Branch { @@ -756,22 +665,17 @@ pub(crate) struct Branch { pub(crate) location: LocationId, } -/// A jump table/switch instruction. -/// -/// This instruction expects a list of blocks to jump to for their corresponding -/// indexes/values. As such, it currently only supports monotonically increasing -/// values that start at zero. #[derive(Clone)] -pub(crate) struct JumpTable { +pub(crate) struct Switch { pub(crate) register: RegisterId, pub(crate) blocks: Vec, pub(crate) location: LocationId, } #[derive(Clone)] -pub(crate) struct BranchResult { - pub(crate) ok: BlockId, - pub(crate) error: BlockId, +pub(crate) struct SwitchKind { + pub(crate) register: RegisterId, + pub(crate) blocks: Vec, pub(crate) location: LocationId, } @@ -781,7 +685,6 @@ pub(crate) struct Goto { pub(crate) location: LocationId, } -/// Moves a value from one register to another. #[derive(Clone)] pub(crate) struct MoveRegister { pub(crate) source: RegisterId, @@ -789,59 +692,14 @@ pub(crate) struct MoveRegister { pub(crate) location: LocationId, } -/// Checks if the reference count of a register is zero. -/// -/// If the value in the register has any references left, a runtime panic is -/// produced. #[derive(Clone)] pub(crate) struct CheckRefs { pub(crate) register: RegisterId, pub(crate) location: LocationId, } -/// Returns the kind of reference we're dealing with. -/// -/// The following values may be produced: -/// -/// - `0`: owned -/// - `1`: a regular reference -/// - `2`: an atomic type (either a reference or owned) -#[derive(Clone)] -pub(crate) struct RefKind { - pub(crate) register: RegisterId, - pub(crate) value: RegisterId, - pub(crate) location: LocationId, -} - /// Drops a value according to its type. /// -/// This instruction is essentially a macro that expands into one of the -/// following (given the instruction `drop(value)`): -/// -/// For an owned value, it checks the reference count and calls the value's -/// dropper method: -/// -/// check_refs(value) -/// value.$dropper() -/// -/// For a regular reference, it decrements the reference count: -/// -/// decrement(value) -/// -/// For a value that uses atomic reference counting, it decrements the count. -/// If the new count is zero, the dropper method is called: -/// -/// if decrement_atomic(value) { -/// value.$dropper() -/// } -/// -/// For a process it does the same as an atomically reference counted value, -/// except the dropper method is scheduled asynchronously: -/// -/// if decrement_atomic(value) { -/// async value.$dropper() -/// } -/// /// If `dropper` is set to `false`, the dropper method isn't called for a value /// no longer in use. #[derive(Clone)] @@ -853,6 +711,7 @@ pub(crate) struct Drop { #[derive(Clone)] pub(crate) struct CallDropper { + pub(crate) register: RegisterId, pub(crate) receiver: RegisterId, pub(crate) location: LocationId, } @@ -867,9 +726,6 @@ pub(crate) struct Free { pub(crate) enum CloneKind { Float, Int, - Process, - String, - Other, } /// Clones a value type. @@ -884,20 +740,13 @@ pub(crate) struct Clone { pub(crate) location: LocationId, } -/// Increments the reference count of a value. -/// -/// For regular values this uses regular reference counting. For values that use -/// atomic reference counting (e.g. processes), this instruction uses atomic -/// reference counting. -/// -/// If used on a type parameter, this instruction compiles down to the -/// equivalent of: -/// -/// if atomic(value) { -/// increment_atomic(value) -/// } else { -/// increment(value) -/// } +#[derive(Clone)] +pub(crate) struct Reference { + pub(crate) register: RegisterId, + pub(crate) value: RegisterId, + pub(crate) location: LocationId, +} + #[derive(Clone)] pub(crate) struct Increment { pub(crate) register: RegisterId, @@ -912,16 +761,17 @@ pub(crate) struct Decrement { } #[derive(Clone)] -pub(crate) struct DecrementAtomic { +pub(crate) struct IncrementAtomic { pub(crate) register: RegisterId, pub(crate) value: RegisterId, pub(crate) location: LocationId, } -/// Moves the result of the last method call into a register. #[derive(Clone)] -pub(crate) struct MoveResult { +pub(crate) struct DecrementAtomic { pub(crate) register: RegisterId, + pub(crate) if_true: BlockId, + pub(crate) if_false: BlockId, pub(crate) location: LocationId, } @@ -944,7 +794,7 @@ pub(crate) struct NilLiteral { } #[derive(Clone)] -pub(crate) struct AllocateArray { +pub(crate) struct Array { pub(crate) register: RegisterId, pub(crate) values: Vec, pub(crate) location: LocationId, @@ -956,26 +806,6 @@ pub(crate) struct Return { pub(crate) location: LocationId, } -#[derive(Clone)] -pub(crate) struct Throw { - pub(crate) register: RegisterId, - pub(crate) location: LocationId, -} - -#[derive(Clone)] -pub(crate) struct ReturnAsync { - pub(crate) register: RegisterId, - pub(crate) value: RegisterId, - pub(crate) location: LocationId, -} - -#[derive(Clone)] -pub(crate) struct ThrowAsync { - pub(crate) register: RegisterId, - pub(crate) value: RegisterId, - pub(crate) location: LocationId, -} - #[derive(Clone)] pub(crate) struct IntLiteral { pub(crate) register: RegisterId, @@ -997,15 +827,9 @@ pub(crate) struct StringLiteral { pub(crate) location: LocationId, } -#[derive(Clone)] -pub(crate) struct Strings { - pub(crate) register: RegisterId, - pub(crate) values: Vec, - pub(crate) location: LocationId, -} - #[derive(Clone)] pub(crate) struct CallStatic { + pub(crate) register: RegisterId, pub(crate) class: types::ClassId, pub(crate) method: types::MethodId, pub(crate) arguments: Vec, @@ -1013,7 +837,8 @@ pub(crate) struct CallStatic { } #[derive(Clone)] -pub(crate) struct CallVirtual { +pub(crate) struct CallInstance { + pub(crate) register: RegisterId, pub(crate) receiver: RegisterId, pub(crate) method: types::MethodId, pub(crate) arguments: Vec, @@ -1022,6 +847,7 @@ pub(crate) struct CallVirtual { #[derive(Clone)] pub(crate) struct CallDynamic { + pub(crate) register: RegisterId, pub(crate) receiver: RegisterId, pub(crate) method: types::MethodId, pub(crate) arguments: Vec, @@ -1030,6 +856,7 @@ pub(crate) struct CallDynamic { #[derive(Clone)] pub(crate) struct CallClosure { + pub(crate) register: RegisterId, pub(crate) receiver: RegisterId, pub(crate) arguments: Vec, pub(crate) location: LocationId, @@ -1037,14 +864,8 @@ pub(crate) struct CallClosure { #[derive(Clone)] pub(crate) struct CallBuiltin { - pub(crate) id: BuiltinFunction, - pub(crate) arguments: Vec, - pub(crate) location: LocationId, -} - -#[derive(Clone)] -pub(crate) struct RawInstruction { - pub(crate) opcode: Opcode, + pub(crate) register: RegisterId, + pub(crate) name: BuiltinFunction, pub(crate) arguments: Vec, pub(crate) location: LocationId, } @@ -1055,20 +876,11 @@ pub(crate) struct Send { pub(crate) method: types::MethodId, pub(crate) arguments: Vec, pub(crate) location: LocationId, - pub(crate) wait: bool, -} - -#[derive(Clone)] -pub(crate) struct SendAsync { - pub(crate) register: RegisterId, - pub(crate) receiver: RegisterId, - pub(crate) method: types::MethodId, - pub(crate) arguments: Vec, - pub(crate) location: LocationId, } #[derive(Clone)] pub(crate) struct GetField { + pub(crate) class: types::ClassId, pub(crate) register: RegisterId, pub(crate) receiver: RegisterId, pub(crate) field: types::FieldId, @@ -1077,6 +889,7 @@ pub(crate) struct GetField { #[derive(Clone)] pub(crate) struct SetField { + pub(crate) class: types::ClassId, pub(crate) receiver: RegisterId, pub(crate) value: RegisterId, pub(crate) field: types::FieldId, @@ -1098,18 +911,9 @@ pub(crate) struct Allocate { } #[derive(Clone)] -pub(crate) struct IntEq { +pub(crate) struct Spawn { pub(crate) register: RegisterId, - pub(crate) left: RegisterId, - pub(crate) right: RegisterId, - pub(crate) location: LocationId, -} - -#[derive(Clone)] -pub(crate) struct StringEq { - pub(crate) register: RegisterId, - pub(crate) left: RegisterId, - pub(crate) right: RegisterId, + pub(crate) class: types::ClassId, pub(crate) location: LocationId, } @@ -1131,95 +935,81 @@ pub(crate) struct Finish { /// sure to also update the compiler pass that removes empty basic blocks. #[derive(Clone)] pub(crate) enum Instruction { - AllocateArray(Box), + Array(Box), Branch(Box), - BranchResult(Box), - JumpTable(Box), + Switch(Box), + SwitchKind(Box), False(Box), Float(Box), Goto(Box), Int(Box), MoveRegister(Box), - MoveResult(Box), Nil(Box), Return(Box), - Throw(Box), - ReturnAsync(Box), - ThrowAsync(Box), String(Box), True(Box), - Strings(Box), CallStatic(Box), - CallVirtual(Box), + CallInstance(Box), CallDynamic(Box), CallClosure(Box), CallDropper(Box), CallBuiltin(Box), Send(Box), - SendAsync(Box), GetField(Box), SetField(Box), CheckRefs(Box), - RefKind(Box), Drop(Box), Free(Box), Clone(Box), + Reference(Box), Increment(Box), Decrement(Box), + IncrementAtomic(Box), DecrementAtomic(Box), - RawInstruction(Box), Allocate(Box), + Spawn(Box), GetConstant(Box), - IntEq(Box), - StringEq(Box), Reduce(Box), Finish(Box), } impl Instruction { - fn location(&self) -> LocationId { + pub(crate) fn location(&self) -> LocationId { match self { Instruction::Branch(ref v) => v.location, - Instruction::BranchResult(ref v) => v.location, - Instruction::JumpTable(ref v) => v.location, + Instruction::Switch(ref v) => v.location, + Instruction::SwitchKind(ref v) => v.location, Instruction::False(ref v) => v.location, Instruction::True(ref v) => v.location, Instruction::Goto(ref v) => v.location, Instruction::MoveRegister(ref v) => v.location, - Instruction::MoveResult(ref v) => v.location, Instruction::Return(ref v) => v.location, - Instruction::Throw(ref v) => v.location, - Instruction::ReturnAsync(ref v) => v.location, - Instruction::ThrowAsync(ref v) => v.location, Instruction::Nil(ref v) => v.location, - Instruction::AllocateArray(ref v) => v.location, + Instruction::Array(ref v) => v.location, Instruction::Int(ref v) => v.location, Instruction::Float(ref v) => v.location, Instruction::String(ref v) => v.location, - Instruction::Strings(ref v) => v.location, Instruction::CallStatic(ref v) => v.location, - Instruction::CallVirtual(ref v) => v.location, + Instruction::CallInstance(ref v) => v.location, Instruction::CallDynamic(ref v) => v.location, Instruction::CallClosure(ref v) => v.location, Instruction::CallDropper(ref v) => v.location, Instruction::CallBuiltin(ref v) => v.location, Instruction::Send(ref v) => v.location, - Instruction::SendAsync(ref v) => v.location, Instruction::GetField(ref v) => v.location, Instruction::SetField(ref v) => v.location, Instruction::CheckRefs(ref v) => v.location, - Instruction::RefKind(ref v) => v.location, Instruction::Drop(ref v) => v.location, Instruction::Free(ref v) => v.location, Instruction::Clone(ref v) => v.location, + Instruction::Reference(ref v) => v.location, Instruction::Increment(ref v) => v.location, Instruction::Decrement(ref v) => v.location, + Instruction::IncrementAtomic(ref v) => v.location, Instruction::DecrementAtomic(ref v) => v.location, - Instruction::RawInstruction(ref v) => v.location, Instruction::Allocate(ref v) => v.location, + Instruction::Spawn(ref v) => v.location, Instruction::GetConstant(ref v) => v.location, - Instruction::IntEq(ref v) => v.location, - Instruction::StringEq(ref v) => v.location, Instruction::Reduce(ref v) => v.location, Instruction::Finish(ref v) => v.location, } @@ -1233,15 +1023,21 @@ impl Instruction { v.condition.0, v.if_true.0, v.if_false.0 ) } - Instruction::BranchResult(ref v) => { + Instruction::Switch(ref v) => { format!( - "branch_result ok = b{}, error = b{}", - v.ok.0, v.error.0 + "switch r{}, {}", + v.register.0, + v.blocks + .iter() + .enumerate() + .map(|(idx, block)| format!("{} = b{}", idx, block.0)) + .collect::>() + .join(", ") ) } - Instruction::JumpTable(ref v) => { + Instruction::SwitchKind(ref v) => { format!( - "jump_table r{}, {}", + "switch_kind r{}, {}", v.register.0, v.blocks .iter() @@ -1290,44 +1086,31 @@ impl Instruction { Instruction::CheckRefs(ref v) => { format!("check_refs r{}", v.register.0) } - Instruction::RefKind(ref v) => { - format!("r{} = ref_kind r{}", v.register.0, v.value.0) - } - Instruction::MoveResult(ref v) => { - format!("r{} = move_result", v.register.0) - } Instruction::Return(ref v) => { format!("return r{}", v.register.0) } - Instruction::Throw(ref v) => { - format!("throw r{}", v.register.0) - } - Instruction::ReturnAsync(ref v) => { - format!("r{} = async return r{}", v.register.0, v.value.0) - } - Instruction::ThrowAsync(ref v) => { - format!("r{} = async throw r{}", v.register.0, v.value.0) - } Instruction::Allocate(ref v) => { format!("r{} = allocate {}", v.register.0, v.class.name(db)) } - Instruction::AllocateArray(ref v) => { - format!("r{} = array [{}]", v.register.0, join(&v.values)) + Instruction::Spawn(ref v) => { + format!("r{} = spawn {}", v.register.0, v.class.name(db)) } - Instruction::Strings(ref v) => { - format!("r{} = strings [{}]", v.register.0, join(&v.values)) + Instruction::Array(ref v) => { + format!("r{} = array [{}]", v.register.0, join(&v.values)) } Instruction::CallStatic(ref v) => { format!( - "call_static {}.{}({})", + "r{} = call_static {}.{}({})", + v.register.0, v.class.name(db), v.method.name(db), join(&v.arguments) ) } - Instruction::CallVirtual(ref v) => { + Instruction::CallInstance(ref v) => { format!( - "call_virtual r{}.{}({})", + "r{} = call_instance r{}.{}({})", + v.register.0, v.receiver.0, v.method.name(db), join(&v.arguments) @@ -1335,7 +1118,8 @@ impl Instruction { } Instruction::CallDynamic(ref v) => { format!( - "call_dynamic r{}.{}({})", + "r{} = call_dynamic r{}.{}({})", + v.register.0, v.receiver.0, v.method.name(db), join(&v.arguments) @@ -1343,33 +1127,26 @@ impl Instruction { } Instruction::CallClosure(ref v) => { format!( - "call_closure r{}({})", + "r{} = call_closure r{}({})", + v.register.0, v.receiver.0, join(&v.arguments) ) } Instruction::CallDropper(ref v) => { - format!("call_dropper r{}", v.receiver.0,) + format!("r{} = call_dropper r{}", v.register.0, v.receiver.0,) } Instruction::CallBuiltin(ref v) => { - format!("call_builtin {}({})", v.id.name(), join(&v.arguments)) - } - Instruction::RawInstruction(ref v) => { - format!("raw {}({})", v.opcode.name(), join(&v.arguments)) - } - Instruction::Send(ref v) => { format!( - "{} r{}.{}({})", - if v.wait { "send" } else { "send_forget" }, - v.receiver.0, - v.method.name(db), + "r{} = call_builtin {}({})", + v.register.0, + v.name.name(), join(&v.arguments) ) } - Instruction::SendAsync(ref v) => { + Instruction::Send(ref v) => { format!( - "r{} = send_async r{}.{}({})", - v.register.0, + "send r{}.{}({})", v.receiver.0, v.method.name(db), join(&v.arguments) @@ -1391,14 +1168,23 @@ impl Instruction { v.value.0 ) } + Instruction::Reference(ref v) => { + format!("r{} = ref r{}", v.register.0, v.value.0) + } Instruction::Increment(ref v) => { format!("r{} = increment r{}", v.register.0, v.value.0) } Instruction::Decrement(ref v) => { format!("decrement r{}", v.register.0) } + Instruction::IncrementAtomic(ref v) => { + format!("r{} = increment_atomic r{}", v.register.0, v.value.0) + } Instruction::DecrementAtomic(ref v) => { - format!("r{} = decrement_atomic r{}", v.register.0, v.value.0) + format!( + "decrement_atomic r{}, true = b{}, false = b{}", + v.register.0, v.if_true.0, v.if_false.0 + ) } Instruction::GetConstant(ref v) => { format!( @@ -1408,18 +1194,6 @@ impl Instruction { v.id.name(db) ) } - Instruction::IntEq(ref v) => { - format!( - "r{} = int_eq r{}, r{}", - v.register.0, v.left.0, v.right.0 - ) - } - Instruction::StringEq(ref v) => { - format!( - "r{} = string_eq r{}, r{}", - v.register.0, v.left.0, v.right.0 - ) - } Instruction::Reduce(ref v) => format!("reduce {}", v.amount), Instruction::Finish(v) => { if v.terminate { "terminate" } else { "finish" }.to_string() @@ -1480,28 +1254,22 @@ pub(crate) struct Method { pub(crate) id: types::MethodId, pub(crate) registers: Registers, pub(crate) body: Graph, + pub(crate) arguments: Vec, + pub(crate) location: LocationId, } impl Method { - pub(crate) fn new(id: types::MethodId) -> Self { - Self { id, body: Graph::new(), registers: Registers::new() } + pub(crate) fn new(id: types::MethodId, location: LocationId) -> Self { + Self { + id, + body: Graph::new(), + registers: Registers::new(), + arguments: Vec::new(), + location, + } } } -pub(crate) struct Location { - /// The module to use for obtaining the file of the location. - /// - /// We store the module here instead of the file so we don't need to - /// duplicate file paths. - pub(crate) module: types::ModuleId, - - /// The method the location is defined in. - pub(crate) method: types::MethodId, - - /// The line and column range. - pub(crate) range: SourceLocation, -} - #[derive(Copy, Clone, Debug)] pub(crate) struct LocationId(usize); @@ -1512,8 +1280,7 @@ pub(crate) struct Mir { pub(crate) classes: HashMap, pub(crate) traits: HashMap, pub(crate) methods: HashMap, - pub(crate) closure_classes: HashMap, - locations: Vec, + locations: Vec, } impl Mir { @@ -1525,7 +1292,6 @@ impl Mir { traits: HashMap::new(), methods: HashMap::new(), locations: Vec::new(), - closure_classes: HashMap::new(), } } @@ -1537,13 +1303,11 @@ impl Mir { pub(crate) fn add_location( &mut self, - module: types::ModuleId, - method: types::MethodId, - range: SourceLocation, + location: SourceLocation, ) -> LocationId { let id = LocationId(self.locations.len()); - self.locations.push(Location { module, method, range }); + self.locations.push(location); id } @@ -1555,7 +1319,7 @@ impl Mir { } } - pub(crate) fn location(&self, index: LocationId) -> &Location { + pub(crate) fn location(&self, index: LocationId) -> &SourceLocation { &self.locations[index.0] } } diff --git a/compiler/src/mir/passes.rs b/compiler/src/mir/passes.rs index 5c64b6629..a8bd6a3a3 100644 --- a/compiler/src/mir/passes.rs +++ b/compiler/src/mir/passes.rs @@ -8,15 +8,19 @@ use crate::mir::{ }; use crate::state::State; use ast::source_location::SourceLocation; -use bytecode::Opcode; use std::collections::{HashMap, HashSet}; use std::iter::repeat_with; use std::mem::swap; use std::path::PathBuf; -use types::{self, Block as _, FIELDS_LIMIT}; +use std::rc::Rc; +use types::format::format_type; +use types::{ + self, Block as _, TypeBounds, FIELDS_LIMIT, OPTION_NONE, OPTION_SOME, + RESULT_CLASS, RESULT_ERROR, RESULT_MODULE, RESULT_OK, +}; const SELF_NAME: &str = "self"; -const SELF_ID: usize = 0; +const SELF_ID: u32 = 0; const MODULES_LIMIT: usize = u32::MAX as usize; const CLASSES_LIMIT: usize = u32::MAX as usize; @@ -387,36 +391,40 @@ impl<'a> GenerateDropper<'a> { /// the async dropper is the last message. When run, it cleans up the object /// like a regular class, and the process shuts down. fn async_class(&mut self) { + let loc = self.mir.add_location(self.location.clone()); let async_dropper = self.generate_dropper( types::ASYNC_DROPPER_METHOD, types::MethodKind::AsyncMutable, false, true, ); - let (dropper_type, rec) = + let dropper_type = self.method_type(types::DROPPER_METHOD, types::MethodKind::Mutable); - let mut dropper_method = Method::new(dropper_type); + let mut dropper_method = Method::new(dropper_type, loc); let mut lower = LowerMethod::new( self.state, self.mir, self.module, &mut dropper_method, ); - let self_reg = lower.new_self(rec); - let loc = lower.add_location(self.location.clone()); + + lower.prepare(loc); + + let self_reg = lower.self_register; let nil_reg = lower.get_nil(loc); - // When scheduling the real dropper we don't wait to wait, as the - // receiving process may have many messages left to process. - lower.current_block_mut().send_and_forget( + lower.current_block_mut().send( self_reg, async_dropper, Vec::new(), loc, ); - lower.current_block_mut().reduce_call(loc); + lower.reduce_call(types::TypeRef::nil(), loc); lower.current_block_mut().return_value(nil_reg, loc); + assert_eq!(lower.method.arguments.len(), 1); + assert!(lower.method.id.is_instance_method(&self.state.db)); + self.add_method(types::DROPPER_METHOD, dropper_type, dropper_method); } @@ -426,40 +434,40 @@ impl<'a> GenerateDropper<'a> { /// tag, certain fields may be set to NULL. As such we branch based on the /// tag value, and only drop the fields relevant for that tag. fn enum_class(&mut self) { + let loc = self.mir.add_location(self.location.clone()); let name = types::DROPPER_METHOD; - let drop_method_opt = - self.class.id.method(&self.state.db, types::DROP_METHOD); - let (method_type, rec) = - self.method_type(name, types::MethodKind::Mutable); - let mut method = Method::new(method_type); + let class = self.class.id; + let drop_method_opt = class.method(&self.state.db, types::DROP_METHOD); + let method_type = self.method_type(name, types::MethodKind::Mutable); + let mut method = Method::new(method_type, loc); let mut lower = LowerMethod::new(self.state, self.mir, self.module, &mut method); - let loc = lower.add_location(self.location.clone()); - let self_reg = lower.new_self(rec); + + lower.prepare(loc); + + let self_reg = lower.self_register; if let Some(id) = drop_method_opt { - let res = lower.new_register(types::TypeRef::nil()); + let typ = types::TypeRef::nil(); + let res = lower.new_register(typ); - lower.current_block_mut().call_virtual( + lower.current_block_mut().call_instance( + res, self_reg, id, Vec::new(), loc, ); - lower.current_block_mut().reduce_call(loc); - lower.current_block_mut().move_result(res, loc); + lower.reduce_call(typ, loc); } - let variants = self.class.id.variants(lower.db()); + let variants = class.variants(lower.db()); let mut blocks = Vec::new(); let before_block = lower.current_block; let after_block = lower.add_block(); - let enum_fields = self.class.id.enum_fields(lower.db()); - let tag_field = self - .class - .id - .field_by_index(lower.db(), types::ENUM_TAG_INDEX) - .unwrap(); + let enum_fields = class.enum_fields(lower.db()); + let tag_field = + class.field_by_index(lower.db(), types::ENUM_TAG_INDEX).unwrap(); let tag_reg = lower.new_register(types::TypeRef::int()); for var in variants { @@ -473,7 +481,9 @@ impl<'a> GenerateDropper<'a> { for (&field, typ) in fields.iter().zip(members.into_iter()) { let reg = lower.new_register(typ); - lower.current_block_mut().get_field(reg, self_reg, field, loc); + lower + .current_block_mut() + .get_field(reg, self_reg, class, field, loc); lower.drop_register(reg, loc); } @@ -484,8 +494,8 @@ impl<'a> GenerateDropper<'a> { lower .block_mut(before_block) - .get_field(tag_reg, self_reg, tag_field, loc); - lower.block_mut(before_block).jump_table(tag_reg, blocks, loc); + .get_field(tag_reg, self_reg, class, tag_field, loc); + lower.block_mut(before_block).switch(tag_reg, blocks, loc); lower.current_block = after_block; @@ -505,26 +515,30 @@ impl<'a> GenerateDropper<'a> { free_self: bool, terminate: bool, ) -> types::MethodId { - let drop_method_opt = - self.class.id.method(&self.state.db, types::DROP_METHOD); - let (method_type, rec) = self.method_type(name, kind); - let mut method = Method::new(method_type); + let class = self.class.id; + let drop_method_opt = class.method(&self.state.db, types::DROP_METHOD); + let method_type = self.method_type(name, kind); + let loc = self.mir.add_location(self.location.clone()); + let mut method = Method::new(method_type, loc); let mut lower = LowerMethod::new(self.state, self.mir, self.module, &mut method); - let loc = lower.add_location(self.location.clone()); - let self_reg = lower.new_self(rec); + + lower.prepare(loc); + + let self_reg = lower.self_register; if let Some(id) = drop_method_opt { - let res = lower.new_register(types::TypeRef::nil()); + let typ = types::TypeRef::nil(); + let res = lower.new_register(typ); - lower.current_block_mut().call_virtual( + lower.current_block_mut().call_instance( + res, self_reg, id, Vec::new(), loc, ); - lower.current_block_mut().reduce_call(loc); - lower.current_block_mut().move_result(res, loc); + lower.reduce_call(typ, loc); } // We check the ref count _after_ running the destructor, as otherwise a @@ -532,7 +546,7 @@ impl<'a> GenerateDropper<'a> { // references (e.g. a field containing a mutable Array reference). lower.current_block_mut().check_refs(self_reg, loc); - for field in self.class.id.fields(lower.db()) { + for field in class.fields(lower.db()) { let typ = field.value_type(lower.db()); if typ.is_permanent(lower.db()) { @@ -541,7 +555,9 @@ impl<'a> GenerateDropper<'a> { let reg = lower.new_register(typ); - lower.current_block_mut().get_field(reg, self_reg, field, loc); + lower + .current_block_mut() + .get_field(reg, self_reg, class, field, loc); lower.drop_register(reg, loc); } @@ -565,7 +581,7 @@ impl<'a> GenerateDropper<'a> { &mut self, name: &str, kind: types::MethodKind, - ) -> (types::MethodId, types::TypeRef) { + ) -> types::MethodId { let id = types::Method::alloc( &mut self.state.db, self.module.id, @@ -574,18 +590,17 @@ impl<'a> GenerateDropper<'a> { kind, ); - let self_type = types::TypeId::ClassInstance( - types::ClassInstance::for_instance_self_type( + let self_type = + types::TypeId::ClassInstance(types::ClassInstance::rigid( &mut self.state.db, self.class.id, &types::TypeBounds::new(), - ), - ); + )); let receiver = types::TypeRef::Mut(self_type); id.set_receiver(&mut self.state.db, receiver); - id.set_self_type(&mut self.state.db, self_type); - (id, receiver) + id.set_return_type(&mut self.state.db, types::TypeRef::nil()); + id } fn add_method(&mut self, name: &str, id: types::MethodId, method: Method) { @@ -632,7 +647,7 @@ impl<'a> DefineConstants<'a> { let val = match n.value { hir::ConstExpression::Int(ref n) => Constant::Int(n.value), hir::ConstExpression::String(ref n) => { - Constant::String(n.value.clone()) + Constant::String(Rc::new(n.value.clone())) } hir::ConstExpression::Float(ref n) => { Constant::Float(n.value) @@ -661,22 +676,30 @@ impl<'a> DefineConstants<'a> { match node { hir::ConstExpression::Int(ref n) => Constant::Int(n.value), hir::ConstExpression::String(ref n) => { - Constant::String(n.value.clone()) + Constant::String(Rc::new(n.value.clone())) } hir::ConstExpression::Float(ref n) => Constant::Float(n.value), hir::ConstExpression::Binary(ref n) => self.binary(n), - hir::ConstExpression::ConstantRef(ref n) => { - let id = if let types::ConstantKind::Constant(id) = n.kind { - id - } else { - unreachable!() - }; - - self.mir.constants.get(&id).cloned().unwrap() - } - hir::ConstExpression::Array(ref n) => Constant::Array( + hir::ConstExpression::ConstantRef(ref n) => match n.kind { + types::ConstantKind::Constant(id) => { + self.mir.constants.get(&id).cloned().unwrap() + } + types::ConstantKind::Builtin(id) => match id { + types::BuiltinConstant::Arch => Constant::String(Rc::new( + self.state.config.target.arch_name().to_string(), + )), + types::BuiltinConstant::Os => Constant::String(Rc::new( + self.state.config.target.os_name().to_string(), + )), + types::BuiltinConstant::Abi => Constant::String(Rc::new( + self.state.config.target.abi_name().to_string(), + )), + }, + _ => unreachable!(), + }, + hir::ConstExpression::Array(ref n) => Constant::Array(Rc::new( n.values.iter().map(|n| self.expression(n)).collect(), - ), + )), hir::ConstExpression::Invalid(_) => unreachable!(), } } @@ -750,10 +773,10 @@ impl<'a> DefineConstants<'a> { } if let Some(val) = res { - Constant::String(val) + Constant::String(Rc::new(val)) } else { self.const_expr_error(&left, op, &right, loc); - Constant::String(String::new()) + Constant::String(Rc::new(String::new())) } } Constant::Array(_) => { @@ -958,14 +981,16 @@ impl<'a> LowerToMir<'a> { let mut class = Class::new(id); - GenerateDropper { - state: self.state, - mir: self.mir, - module: self.module, - class: &mut class, - location: node.location, + if !id.kind(self.db()).is_extern() { + GenerateDropper { + state: self.state, + mir: self.mir, + module: self.module, + class: &mut class, + location: node.location, + } + .run(); } - .run(); class.add_methods(&methods); self.mir.add_methods(methods); @@ -999,9 +1024,8 @@ impl<'a> LowerToMir<'a> { node: hir::DefineModuleMethod, ) -> Method { let id = node.method_id.unwrap(); - let mut method = Method::new(id); - let loc = - self.mir.add_location(self.module.id, id, node.location.clone()); + let loc = self.mir.add_location(node.location.clone()); + let mut method = Method::new(id, loc); LowerMethod::new(self.state, self.mir, self.module, &mut method) .run(node.body, loc); @@ -1014,9 +1038,8 @@ impl<'a> LowerToMir<'a> { node: hir::DefineInstanceMethod, ) -> Method { let id = node.method_id.unwrap(); - let mut method = Method::new(id); - let loc = - self.mir.add_location(self.module.id, id, node.location.clone()); + let loc = self.mir.add_location(node.location.clone()); + let mut method = Method::new(id, loc); LowerMethod::new(self.state, self.mir, self.module, &mut method) .run(node.body, loc); @@ -1026,9 +1049,8 @@ impl<'a> LowerToMir<'a> { fn define_async_method(&mut self, node: hir::DefineAsyncMethod) -> Method { let id = node.method_id.unwrap(); - let mut method = Method::new(id); - let loc = - self.mir.add_location(self.module.id, id, node.location.clone()); + let loc = self.mir.add_location(node.location.clone()); + let mut method = Method::new(id, loc); LowerMethod::new(self.state, self.mir, self.module, &mut method) .run(node.body, loc); @@ -1041,9 +1063,8 @@ impl<'a> LowerToMir<'a> { node: hir::DefineStaticMethod, ) -> Method { let id = node.method_id.unwrap(); - let mut method = Method::new(id); - let loc = - self.mir.add_location(self.module.id, id, node.location.clone()); + let loc = self.mir.add_location(node.location.clone()); + let mut method = Method::new(id, loc); LowerMethod::new(self.state, self.mir, self.module, &mut method) .run(node.body, loc); @@ -1058,11 +1079,12 @@ impl<'a> LowerToMir<'a> { ) -> Method { let id = node.method_id.unwrap(); let variant_id = node.variant_id.unwrap(); - let mut method = Method::new(id); - let loc = self.mir.add_location(self.module.id, id, node.location); + let loc = self.mir.add_location(node.location); + let mut method = Method::new(id, loc); let fields = class.enum_fields(self.db()); + let bounds = TypeBounds::new(); let ins = types::TypeRef::Owned(types::TypeId::ClassInstance( - types::ClassInstance::for_static_self_type(self.db_mut(), class), + types::ClassInstance::rigid(self.db_mut(), class, &bounds), )); let mut lower = LowerMethod::new(self.state, self.mir, self.module, &mut method); @@ -1077,14 +1099,18 @@ impl<'a> LowerToMir<'a> { lower.current_block_mut().allocate(ins_reg, class, loc); lower.current_block_mut().int_literal(tag_reg, tag_val, loc); - lower.current_block_mut().set_field(ins_reg, tag_field, tag_reg, loc); + lower + .current_block_mut() + .set_field(ins_reg, class, tag_field, tag_reg, loc); for (arg, field) in id.arguments(lower.db()).into_iter().zip(fields.into_iter()) { let reg = *lower.variable_mapping.get(&arg.variable).unwrap(); - lower.current_block_mut().set_field(ins_reg, field, reg, loc); + lower + .current_block_mut() + .set_field(ins_reg, class, field, reg, loc); lower.mark_register_as_moved(reg); } @@ -1214,7 +1240,7 @@ impl<'a> LowerMethod<'a> { self.mark_register_as_moved(reg); self.partially_move_self_if_field(reg); self.drop_all_registers(); - self.return_or_throw(reg, false, location); + self.return_register(reg, location); return; } @@ -1245,7 +1271,7 @@ impl<'a> LowerMethod<'a> { self.mark_register_as_moved(reg); self.partially_move_self_if_field(reg); self.drop_all_registers(); - self.return_or_throw(reg, false, loc); + self.return_register(reg, loc); } } @@ -1258,7 +1284,10 @@ impl<'a> LowerMethod<'a> { // define the register. This is OK because the type-checker disallows // the use of `self` in these cases. let self_reg = if self.method.id.is_instance_method(self.db()) { - Some(self.new_self(self.method.id.receiver(self.db()))) + let reg = self.new_self(self.method.id.receiver(self.db())); + + self.method.arguments.push(reg); + Some(reg) } else { None }; @@ -1268,6 +1297,7 @@ impl<'a> LowerMethod<'a> { for arg in self.method.id.arguments(self.db()) { let reg = self.new_variable(arg.variable); + self.method.arguments.push(reg); self.variable_mapping.insert(arg.variable, reg); args.push(reg); } @@ -1302,13 +1332,13 @@ impl<'a> LowerMethod<'a> { // Within a closure, explicit and implicit references to `self` should // use the _captured_ `self` (i.e. point to the outer `self` value), not // the closure itself. - let outer_self = self.field_register(field, field_type, location); - let inner_self = self.surrounding_type_register; + let captured = self.field_register(field, field_type, location); + let closure = self.surrounding_type_register; + let class = self.register_type(closure).class_id(self.db()).unwrap(); self.current_block_mut() - .get_field(outer_self, inner_self, field, location); - - self.self_register = outer_self; + .get_field(captured, closure, class, field, location); + self.self_register = captured; } fn warn_unreachable(&mut self) { @@ -1320,7 +1350,7 @@ impl<'a> LowerMethod<'a> { } if let Some(first) = block.instructions.first() { - let loc = self.mir.location(first.location()).range.clone(); + let loc = self.mir.location(first.location()).clone(); let file = self.module.id.file(&self.state.db); self.state.diagnostics.unreachable(file, loc); @@ -1362,7 +1392,6 @@ impl<'a> LowerMethod<'a> { hir::Expression::AssignSetter(n) => self.assign_setter(*n), hir::Expression::AssignVariable(n) => self.assign_variable(*n), hir::Expression::ReplaceVariable(n) => self.replace_variable(*n), - hir::Expression::AsyncCall(n) => self.async_call(*n), hir::Expression::Break(n) => self.break_expression(*n), hir::Expression::BuiltinCall(n) => self.builtin_call(*n), hir::Expression::Call(n) => self.call(*n), @@ -1373,7 +1402,6 @@ impl<'a> LowerMethod<'a> { hir::Expression::FieldRef(n) => self.field(*n), hir::Expression::Float(n) => self.float_literal(*n), hir::Expression::IdentifierRef(n) => self.identifier(*n), - hir::Expression::Index(n) => self.index_expression(*n), hir::Expression::ClassLiteral(n) => self.class_literal(*n), hir::Expression::Int(n) => self.int_literal(*n), hir::Expression::Loop(n) => self.loop_expression(*n), @@ -1392,7 +1420,7 @@ impl<'a> LowerMethod<'a> { hir::Expression::Tuple(n) => self.tuple_literal(*n), hir::Expression::TypeCast(n) => self.type_cast(*n), hir::Expression::Recover(n) => self.recover_expression(*n), - hir::Expression::Invalid(_) => unreachable!(), + hir::Expression::Try(n) => self.try_expression(*n), } } @@ -1490,7 +1518,7 @@ impl<'a> LowerMethod<'a> { self.state.diagnostics.moved_variable_in_loop( &name, self.file(), - self.mir.location(loc).range.clone(), + self.mir.location(loc).clone(), ); } } @@ -1555,11 +1583,17 @@ impl<'a> LowerMethod<'a> { } fn class_literal(&mut self, node: hir::ClassLiteral) -> RegisterId { + self.check_inferred(node.resolved_type, &node.location); + let ins = self.new_register(node.resolved_type); - let id = node.class_id.unwrap(); + let class = node.class_id.unwrap(); let loc = self.add_location(node.location); - self.current_block_mut().allocate(ins, id, loc); + if class.kind(self.db()).is_async() { + self.current_block_mut().spawn(ins, class, loc); + } else { + self.current_block_mut().allocate(ins, class, loc); + } for field in node.fields { let id = field.field_id.unwrap(); @@ -1567,13 +1601,15 @@ impl<'a> LowerMethod<'a> { let val = self.input_expression(field.value, Some(exp)); let loc = self.add_location(field.location); - self.current_block_mut().set_field(ins, id, val, loc); + self.current_block_mut().set_field(ins, class, id, val, loc); } ins } fn tuple_literal(&mut self, node: hir::TupleLiteral) -> RegisterId { + self.check_inferred(node.resolved_type, &node.location); + let tup = self.new_register(node.resolved_type); let id = node.class_id.unwrap(); let loc = self.add_location(node.location); @@ -1587,20 +1623,14 @@ impl<'a> LowerMethod<'a> { let loc = self.add_location(val.location().clone()); let reg = self.input_expression(val, Some(exp)); - self.current_block_mut().set_field(tup, field, reg, loc); + self.current_block_mut().set_field(tup, id, field, reg, loc); } tup } fn array_literal(&mut self, node: hir::ArrayLiteral) -> RegisterId { - if !node.resolved_type.is_inferred(self.db()) { - self.state.diagnostics.cant_infer_type( - types::format_type(self.db(), node.resolved_type), - self.file(), - node.location.clone(), - ); - } + self.check_inferred(node.resolved_type, &node.location); let vals: Vec = node .values @@ -1623,7 +1653,7 @@ impl<'a> LowerMethod<'a> { let loc = self.add_location(node.location); let reg = self.new_register(node.resolved_type); - self.current_block_mut().allocate_array(reg, vals, loc); + self.current_block_mut().array(reg, vals, loc); reg } @@ -1701,7 +1731,12 @@ impl<'a> LowerMethod<'a> { let loc = self.add_location(node.location); let reg = self.new_register(node.resolved_type); - self.current_block_mut().strings(reg, vals, loc); + self.current_block_mut().call_builtin( + reg, + types::BuiltinFunction::StringConcat, + vals, + loc, + ); reg } } @@ -1724,10 +1759,9 @@ impl<'a> LowerMethod<'a> { let loc = self.add_location(node.name.location); let reg = match node.kind { types::CallKind::Call(info) => { - self.verify_call_info(&info, &node.location); + self.check_inferred(info.returns, &node.location); let returns = info.returns; - let throws = info.throws; let rec = if info.receiver.is_explicit() { node.receiver.map(|expr| self.expression(expr)) } else { @@ -1735,30 +1769,30 @@ impl<'a> LowerMethod<'a> { }; let args = self.call_arguments(&info, node.arguments); + let result = self.handle_call(info, rec, args, loc); - self.handle_call(info, rec, args, loc); - self.current_block_mut().reduce_call(loc); - self.handle_result(node.else_block, returns, throws, loc) + self.reduce_call(returns, loc); + + if returns.is_never(self.db()) { + self.add_current_block(); + } + + result } types::CallKind::GetField(info) => { + self.check_inferred(info.variable_type, &node.location); + let rec = self.expression(node.receiver.unwrap()); let reg = self.new_field(info.id, info.variable_type); - self.current_block_mut().get_field(reg, rec, info.id, loc); + self.current_block_mut() + .get_field(reg, rec, info.class, info.id, loc); reg } - types::CallKind::ClosureCall(info) => { - self.verify_inferred_type(info.returns, &node.location); - - if !info.throws.is_inferred(self.db()) { - self.state.diagnostics.cant_infer_throw_type( - self.file(), - node.location.clone(), - ); - } + types::CallKind::CallClosure(info) => { + self.check_inferred(info.returns, &node.location); let returns = info.returns; - let throws = info.throws; let rec = self.expression(node.receiver.unwrap()); let mut args = Vec::new(); @@ -1772,9 +1806,16 @@ impl<'a> LowerMethod<'a> { } } - self.current_block_mut().call_closure(rec, args, loc); - self.current_block_mut().reduce_call(loc); - self.handle_result(node.else_block, returns, throws, loc) + let res = self.new_register(returns); + + self.current_block_mut().call_closure(res, rec, args, loc); + self.reduce_call(returns, loc); + + if returns.is_never(self.db()) { + self.add_current_block(); + } + + res } _ => { unreachable!() @@ -1791,7 +1832,8 @@ impl<'a> LowerMethod<'a> { receiver: Option, arguments: Vec, location: LocationId, - ) { + ) -> RegisterId { + let result = self.new_register(info.returns); let mut rec = match info.receiver { types::Receiver::Explicit => receiver.unwrap(), types::Receiver::Implicit => { @@ -1803,7 +1845,7 @@ impl<'a> LowerMethod<'a> { self.state.diagnostics.implicit_receiver_moved( &name, self.file(), - self.mir.location(location).range.clone(), + self.mir.location(location).clone(), ); } @@ -1813,9 +1855,9 @@ impl<'a> LowerMethod<'a> { // Static methods can't move, be dynamic or async, so we can // skip the code that comes after this. self.current_block_mut() - .call_static(id, info.id, arguments, location); + .call_static(result, id, info.id, arguments, location); - return; + return result; } }; @@ -1824,15 +1866,17 @@ impl<'a> LowerMethod<'a> { } if info.id.is_async(self.db()) { - self.current_block_mut() - .send_and_wait(rec, info.id, arguments, location); + self.current_block_mut().send(rec, info.id, arguments, location); + self.current_block_mut().nil_literal(result, location); } else if info.dynamic { self.current_block_mut() - .call_dynamic(rec, info.id, arguments, location); + .call_dynamic(result, rec, info.id, arguments, location); } else { self.current_block_mut() - .call_virtual(rec, info.id, arguments, location); + .call_instance(result, rec, info.id, arguments, location); } + + result } fn call_arguments( @@ -1861,21 +1905,7 @@ impl<'a> LowerMethod<'a> { args } - fn verify_call_info( - &mut self, - info: &types::CallInfo, - location: &SourceLocation, - ) { - self.verify_inferred_type(info.returns, location); - - if !info.throws.is_inferred(self.db()) { - self.state - .diagnostics - .cant_infer_throw_type(self.file(), location.clone()); - } - } - - fn verify_inferred_type( + fn check_inferred( &mut self, typ: types::TypeRef, location: &SourceLocation, @@ -1885,7 +1915,7 @@ impl<'a> LowerMethod<'a> { } self.state.diagnostics.cant_infer_type( - types::format_type(self.db(), typ), + format_type(self.db(), typ), self.file(), location.clone(), ); @@ -1911,91 +1941,11 @@ impl<'a> LowerMethod<'a> { self.input_register(reg, typ, expected, loc) } - fn async_call(&mut self, node: hir::AsyncCall) -> RegisterId { - let loc = self.add_location(node.name.location); - let info = node.info.unwrap(); - - self.verify_call_info(&info, &node.location); - - let rec = self.expression(node.receiver); - let args = self.call_arguments(&info, node.arguments); - let reg = self.new_register(info.returns); - - self.current_block_mut().send_async(reg, rec, info.id, args, loc); - self.current_block_mut().reduce_call(loc); - reg - } - - fn handle_result( - &mut self, - else_node: Option, - returns: types::TypeRef, - throws: types::TypeRef, - location: LocationId, - ) -> RegisterId { - // We create the register first so its state is mapped to the block the - // call is performed in, not a block we may branch to based on the - // result. - let ret_reg = self.new_untracked_register(returns); - - if let Some(node) = else_node { - let before_else = self.current_block; - let ok_block = self.add_block(); - let else_block = self.add_block(); - let after_else = self.add_block(); - let else_loc = self.add_location(node.location); - - self.add_edge(before_else, else_block); - self.add_edge(before_else, ok_block); - self.add_edge(ok_block, after_else); - - self.current_block = else_block; - - self.enter_scope(); - - let else_var = self.new_register(throws); - - self.current_block_mut().move_result(else_var, else_loc); - self.else_argument(node.argument, else_var, else_loc); - - let else_res = self.body(node.body, else_loc); - - self.mark_register_as_moved(else_res); - self.exit_scope(); - - if self.in_connected_block() { - self.mark_register_as_moved(else_res); - self.current_block_mut() - .move_register(ret_reg, else_res, else_loc); - self.add_edge(self.current_block, after_else); - } - - self.current_block = after_else; - - self.block_mut(before_else) - .branch_result(ok_block, else_block, location); - - self.block_mut(ok_block).move_result(ret_reg, location); - self.scope.created.push(ret_reg); - - return ret_reg; - } - - self.current_block_mut().move_result(ret_reg, location); - self.scope.created.push(ret_reg); - - if returns.is_never(self.db()) { - self.add_current_block(); - } - - ret_reg - } - fn assign_setter(&mut self, node: hir::AssignSetter) -> RegisterId { let entered = self.enter_call_scope(); let reg = match node.kind { types::CallKind::Call(info) => { - self.verify_call_info(&info, &node.location); + self.check_inferred(info.returns, &node.location); let loc = self.add_location(node.location); let exp = self.expected_argument_type(info.id, 0); @@ -2007,11 +1957,15 @@ impl<'a> LowerMethod<'a> { let args = vec![self.input_expression(node.value, Some(exp))]; let returns = info.returns; - let throws = info.throws; + let result = self.handle_call(info, rec, args, loc); + + self.reduce_call(returns, loc); + + if returns.is_never(self.db()) { + self.add_current_block(); + } - self.handle_call(info, rec, args, loc); - self.current_block_mut().reduce_call(loc); - self.handle_result(node.else_block, returns, throws, loc) + result } types::CallKind::SetField(info) => { let rec = self.expression(node.receiver); @@ -2019,7 +1973,8 @@ impl<'a> LowerMethod<'a> { let loc = self.add_location(node.location); let arg = self.input_expression(node.value, Some(exp)); - self.current_block_mut().set_field(rec, info.id, arg, loc); + self.current_block_mut() + .set_field(rec, info.class, info.id, arg, loc); self.get_nil(loc) } _ => unreachable!(), @@ -2046,13 +2001,14 @@ impl<'a> LowerMethod<'a> { let &field = self.variable_fields.get(&id).unwrap(); let ® = self.field_mapping.get(&field).unwrap(); let rec = self.surrounding_type_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); if self.should_drop_register(reg) { - self.current_block_mut().get_field(reg, rec, field, loc); + self.current_block_mut().get_field(reg, rec, class, field, loc); self.drop_register(reg, loc); } - self.current_block_mut().set_field(rec, field, val, loc); + self.current_block_mut().set_field(rec, class, field, val, loc); } self.get_nil(loc) @@ -2077,9 +2033,10 @@ impl<'a> LowerMethod<'a> { } else { let &field = self.variable_fields.get(&id).unwrap(); let rec = self.surrounding_type_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); - self.current_block_mut().get_field(old_val, rec, field, loc); - self.current_block_mut().set_field(rec, field, new_val, loc); + self.current_block_mut().get_field(old_val, rec, class, field, loc); + self.current_block_mut().set_field(rec, class, field, new_val, loc); } old_val @@ -2093,25 +2050,26 @@ impl<'a> LowerMethod<'a> { if let Some(®) = self.field_mapping.get(&id) { let rec = self.surrounding_type_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); if self.should_drop_register(reg) { - self.current_block_mut().get_field(reg, rec, id, loc); + self.current_block_mut().get_field(reg, rec, class, id, loc); self.drop_register(reg, loc); } self.update_register_state(reg, RegisterState::Available); - self.current_block_mut().set_field(rec, id, new_val, loc); + self.current_block_mut().set_field(rec, class, id, new_val, loc); } else { let reg = self.new_register(node.resolved_type); let rec = self.self_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); // Closures capture `self` as a whole, so we can't end up with a // case where we try to drop an already dropped value here. - self.current_block_mut().get_field(reg, rec, id, loc); + self.current_block_mut().get_field(reg, rec, class, id, loc); self.drop_register(reg, loc); - self.mark_register_as_moved(reg); - self.current_block_mut().set_field(rec, id, new_val, loc); + self.current_block_mut().set_field(rec, class, id, new_val, loc); }; self.get_nil(loc) @@ -2130,143 +2088,40 @@ impl<'a> LowerMethod<'a> { (self.self_register, self.self_register) }; + let class = self.register_type(rec).class_id(self.db()).unwrap(); + self.check_if_moved(check_reg, &node.field.name, &node.field.location); - self.current_block_mut().get_field(old_val, rec, id, loc); - self.current_block_mut().set_field(rec, id, new_val, loc); + self.current_block_mut().get_field(old_val, rec, class, id, loc); + self.current_block_mut().set_field(rec, class, id, new_val, loc); old_val } - fn index_expression(&mut self, node: hir::Index) -> RegisterId { - let info = node.info.unwrap(); - - self.verify_inferred_type(info.returns, &node.location); - - let entered = self.enter_call_scope(); - let exp = self.expected_argument_type(info.id, 0); - let loc = self.add_location(node.location); - let rec = self.expression(node.receiver); - let args = vec![self.input_expression(node.index, Some(exp))]; - let reg = self.new_register(info.returns); - let block = self.current_block_mut(); - - if info.dynamic { - block.call_dynamic(rec, info.id, args, loc); - } else { - block.call_virtual(rec, info.id, args, loc); - } - - block.reduce_call(loc); - block.move_result(reg, loc); - - self.exit_call_scope(entered, reg); - reg - } - fn builtin_call(&mut self, node: hir::BuiltinCall) -> RegisterId { let loc = self.add_location(node.location); let info = node.info.unwrap(); let returns = info.returns; - let throws = info.throws; // Builtin calls don't take ownership of arguments, nor do we need/want // to modify reference counts. As such we use a simplified approach to // passing arguments (compared to regular method calls). - let mut args = + let args: Vec<_> = node.arguments.into_iter().map(|n| self.expression(n)).collect(); - match info.id.kind(self.db()) { - types::BuiltinFunctionKind::Function(bif) => { - self.current_block_mut().call_builtin(bif, args, loc); - self.current_block_mut().reduce_call(loc); - self.handle_result(node.else_block, returns, throws, loc) - } - types::BuiltinFunctionKind::Instruction(op) if op.writes() => { - let reg = self.new_register(returns); - let block = self.current_block_mut(); - let mut regs = vec![reg]; - - regs.append(&mut args); - block.raw_instruction(op, regs, loc); - reg - } - types::BuiltinFunctionKind::Instruction(Opcode::Panic) => { - let op = Opcode::Panic; - - self.current_block_mut().raw_instruction(op, args, loc); - self.add_current_block(); - self.new_register(types::TypeRef::Never) - } - types::BuiltinFunctionKind::Instruction(op) => { - self.current_block_mut().raw_instruction(op, args, loc); + match info.id { + types::BuiltinFunction::Moved => { + self.mark_register_as_moved(args[0]); self.get_nil(loc) } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::FutureGet, - ) => { - let op = Opcode::FutureGet; - - self.current_block_mut().raw_instruction(op, args, loc); - self.handle_result(node.else_block, returns, throws, loc) - } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::FutureGetFor, - ) => { - let op = Opcode::FutureGetFor; + name => { + let reg = self.new_register(returns); - self.current_block_mut().raw_instruction(op, args, loc); - self.handle_result(node.else_block, returns, throws, loc) - } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::StringClone, - ) => { - let kind = CloneKind::String; - let reg = self.new_register(types::TypeRef::string()); - let src = args[0]; - - self.current_block_mut().clone(kind, reg, src, loc); - reg - } - types::BuiltinFunctionKind::Macro(types::CompilerMacro::Moved) => { - let reg = args[0]; + self.current_block_mut().call_builtin(reg, name, args, loc); + self.reduce_call(returns, loc); - self.mark_register_as_moved(reg); - reg - } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::PanicThrown, - ) => { - let reg = self.new_register(types::TypeRef::string()); - let arg = args[0]; - let mid = self - .register_type(arg) - .type_id(self.db(), self.method.id.self_type(self.db())) - .ok() - .and_then(|id| { - id.method(self.db(), types::TO_STRING_METHOD) - }) - .unwrap(); - - self.current_block_mut().call_virtual( - arg, - mid, - Vec::new(), - loc, - ); - self.current_block_mut().reduce_call(loc); - self.current_block_mut().move_result(reg, loc); - self.current_block_mut().raw_instruction( - Opcode::Panic, - vec![reg], - loc, - ); - self.get_nil(loc) - } - types::BuiltinFunctionKind::Macro( - types::CompilerMacro::Strings, - ) => { - let reg = self.new_register(types::TypeRef::string()); + if returns.is_never(self.db()) { + self.add_current_block(); + } - self.current_block_mut().strings(reg, args, loc); reg } } @@ -2282,64 +2137,153 @@ impl<'a> LowerMethod<'a> { self.mark_register_as_moved(reg); self.drop_all_registers(); - self.return_or_throw(reg, false, loc); + self.return_register(reg, loc); self.add_current_block(); self.new_register(types::TypeRef::Never) } - fn throw_expression(&mut self, node: hir::Throw) -> RegisterId { + fn try_expression(&mut self, node: hir::Try) -> RegisterId { let loc = self.add_location(node.location); - let reg = self.output_expression(node.value); + let reg = self.expression(node.expression); + let class = self.register_type(reg).class_id(self.db()).unwrap(); + let tag_reg = self.new_untracked_register(types::TypeRef::int()); + let tag_field = + class.field_by_index(self.db(), types::ENUM_TAG_INDEX).unwrap(); + let val_field = class.enum_fields(self.db())[0]; + let ok_block = self.add_block(); + let err_block = self.add_block(); + let after_block = self.add_block(); + let mut blocks = vec![BlockId(0), BlockId(0)]; + let ret_reg = self.new_untracked_register(node.return_type); + let err_tag = self.new_untracked_register(types::TypeRef::int()); - self.mark_register_as_moved(reg); - self.drop_all_registers(); - self.return_or_throw(reg, true, loc); - self.add_current_block(); - self.new_register(types::TypeRef::Never) - } + self.add_edge(self.current_block, ok_block); + self.add_edge(self.current_block, err_block); + self.add_edge(ok_block, after_block); - fn return_or_throw( - &mut self, - register: RegisterId, - throw: bool, - location: LocationId, - ) { - if self.method.id.is_async(self.db()) { - let write_reg = self.new_register(types::TypeRef::boolean()); - let before_id = self.current_block; + self.mark_register_as_moved(reg); + self.current_block_mut().get_field(tag_reg, reg, class, tag_field, loc); + + let out_reg = match node.kind { + types::ThrowKind::Unknown => unreachable!(), + types::ThrowKind::Option(typ) => { + let some_id = class + .variant(self.db(), OPTION_SOME) + .unwrap() + .id(self.db()); + let none_id = class + .variant(self.db(), OPTION_NONE) + .unwrap() + .id(self.db()); + let ok_reg = self.new_untracked_register(typ); + + blocks[some_id as usize] = ok_block; + blocks[none_id as usize] = err_block; + + self.current_block_mut().switch(tag_reg, blocks, loc); + + // The block to jump to for a Some. + self.block_mut(ok_block) + .get_field(ok_reg, reg, class, val_field, loc); + self.block_mut(ok_block).free(reg, loc); + self.block_mut(ok_block).goto(after_block, loc); + + // The block to jump to for a None + self.current_block = err_block; + + self.current_block_mut().allocate(ret_reg, class, loc); + self.current_block_mut().int_literal( + err_tag, + none_id as _, + loc, + ); + self.current_block_mut() + .set_field(ret_reg, class, tag_field, err_tag, loc); + self.current_block_mut().free(reg, loc); - if throw { + self.drop_all_registers(); + self.current_block_mut().return_value(ret_reg, loc); + ok_reg + } + types::ThrowKind::Result(ok_typ, err_typ) => { + let ok_id = + class.variant(self.db(), RESULT_OK).unwrap().id(self.db()); + let err_id = class + .variant(self.db(), RESULT_ERROR) + .unwrap() + .id(self.db()); + let ok_reg = self.new_untracked_register(ok_typ); + let err_val = self.new_untracked_register(err_typ); + + blocks[ok_id as usize] = ok_block; + blocks[err_id as usize] = err_block; + + self.current_block_mut().switch(tag_reg, blocks, loc); + + // The block to jump to for an Ok. + self.block_mut(ok_block) + .get_field(ok_reg, reg, class, val_field, loc); + self.block_mut(ok_block).free(reg, loc); + self.block_mut(ok_block).goto(after_block, loc); + + // The block to jump to for an Error. + self.current_block = err_block; + + self.current_block_mut().allocate(ret_reg, class, loc); + self.current_block_mut().int_literal(err_tag, err_id as _, loc); self.current_block_mut() - .throw_async_value(write_reg, register, location); - } else { + .get_field(err_val, reg, class, val_field, loc); + self.current_block_mut() + .set_field(ret_reg, class, tag_field, err_tag, loc); self.current_block_mut() - .return_async_value(write_reg, register, location); + .set_field(ret_reg, class, val_field, err_val, loc); + self.current_block_mut().free(reg, loc); + + self.drop_all_registers(); + self.current_block_mut().return_value(ret_reg, loc); + ok_reg } + }; - if !self.register_type(register).is_permanent(self.db()) { - let drop_id = self.add_block(); - let after_id = self.add_block(); + self.current_block = after_block; + self.scope.created.push(out_reg); + out_reg + } - self.add_edge(before_id, drop_id); - self.add_edge(before_id, after_id); - self.add_edge(drop_id, after_id); + fn throw_expression(&mut self, node: hir::Throw) -> RegisterId { + let loc = self.add_location(node.location); + let reg = self.expression(node.value); + let class = self.db().class_in_module(RESULT_MODULE, RESULT_CLASS); + let err_id = + class.variant(self.db(), RESULT_ERROR).unwrap().id(self.db()); + let tag_field = + class.field_by_index(self.db(), types::ENUM_TAG_INDEX).unwrap(); + let val_field = class.enum_fields(self.db())[0]; + let result_reg = self.new_register(node.return_type); + let tag_reg = self.new_register(types::TypeRef::int()); - self.current_block_mut() - .branch(write_reg, after_id, drop_id, location); + self.current_block_mut().allocate(result_reg, class, loc); + self.current_block_mut().int_literal(tag_reg, err_id as _, loc); + self.current_block_mut() + .set_field(result_reg, class, tag_field, tag_reg, loc); + self.current_block_mut() + .set_field(result_reg, class, val_field, reg, loc); - self.current_block = drop_id; + self.mark_register_as_moved(reg); + self.mark_register_as_moved(result_reg); + self.drop_all_registers(); - self.drop_register(register, location); - self.current_block_mut().goto(after_id, location); + self.current_block_mut().return_value(result_reg, loc); - self.current_block = after_id; - } + self.add_current_block(); + self.new_register(types::TypeRef::Never) + } + fn return_register(&mut self, register: RegisterId, location: LocationId) { + if self.method.id.is_async(self.db()) { let terminate = self.method.id.is_main(self.db()); self.current_block_mut().finish(terminate, location); - } else if throw { - self.current_block_mut().throw_value(register, location); } else { self.current_block_mut().return_value(register, location); } @@ -2366,21 +2310,22 @@ impl<'a> LowerMethod<'a> { fn increment( &mut self, value: hir::Expression, - value_type: types::TypeRef, + return_type: types::TypeRef, location: SourceLocation, ) -> RegisterId { let loc = self.add_location(location); let val = self.expression(value); + let val_type = self.register_type(val); - if value_type.is_value_type(self.db()) { - let reg = self.clone_value_type(val, value_type, false, loc); + if val_type.is_value_type(self.db()) { + let reg = self.clone_value_type(val, return_type, false, loc); self.mark_register_as_available(reg); reg } else { - let reg = self.new_register(value_type); + let reg = self.new_register(return_type); - self.current_block_mut().increment(reg, val, loc); + self.current_block_mut().reference(reg, val, loc); reg } } @@ -2442,8 +2387,8 @@ impl<'a> LowerMethod<'a> { let input_reg = self.input_expression(node.expression, None); let input_type = self.register_type(input_reg); - // The result is untracked as otherwise an explicit return/throw/etc may - // drop it before we write to it. + // The result is untracked as otherwise an explicit return may drop it + // before we write to it. let output_reg = self.new_untracked_register(node.resolved_type); let mut rows = Vec::new(); @@ -2478,8 +2423,8 @@ impl<'a> LowerMethod<'a> { rows.push(pmatch::Row::new(vec![col], guard, body)); } - let stype = self.self_type(); - let compiler = pmatch::Compiler::new(self.state, stype, vars); + let bounds = self.method.id.bounds(self.db()).clone(); + let compiler = pmatch::Compiler::new(self.state, vars, bounds); let result = compiler.compile(rows); if result.missing { @@ -2701,7 +2646,19 @@ impl<'a> LowerMethod<'a> { self.add_drop_flag(target, loc); if state.increment.contains(&source) { - self.current_block_mut().increment(target, source, loc); + let typ = self.register_type(source); + + if typ.is_value_type(self.db()) { + let copy = + self.clone_value_type(source, typ, false, loc); + + self.mark_register_as_moved(copy); + self.current_block_mut() + .move_register(target, copy, loc); + } else { + self.current_block_mut() + .reference(target, source, loc); + } } else { self.current_block_mut() .move_register(target, source, loc); @@ -2879,8 +2836,12 @@ impl<'a> LowerMethod<'a> { let val_reg = self.new_untracked_register(types::TypeRef::string()); self.block_mut(test_block).string_literal(val_reg, val, loc); - self.block_mut(test_block) - .string_eq(res_reg, test_reg, val_reg, loc); + self.block_mut(test_block).call_builtin( + res_reg, + types::BuiltinFunction::StringEq, + vec![test_reg, val_reg], + loc, + ); let ok_block = self.decision(state, case.node, test_block, registers.clone()); @@ -2932,8 +2893,12 @@ impl<'a> LowerMethod<'a> { let val_reg = self.new_untracked_register(val_type); self.block_mut(test_block).int_literal(val_reg, val, loc); - self.block_mut(test_block) - .int_eq(res_reg, test_reg, val_reg, loc); + self.block_mut(test_block).call_builtin( + res_reg, + types::BuiltinFunction::IntEq, + vec![test_reg, val_reg], + loc, + ); test_block } @@ -2974,9 +2939,12 @@ impl<'a> LowerMethod<'a> { for (arg, field) in case.arguments.into_iter().zip(fields.into_iter()) { let reg = state.registers[arg.0]; + let class = + self.register_type(test_reg).class_id(self.db()).unwrap(); state.mark_as_increment(reg); - self.block_mut(parent_block).get_field(reg, test_reg, field, loc); + self.block_mut(parent_block) + .get_field(reg, test_reg, class, field, loc); registers.push(reg); } @@ -2999,19 +2967,21 @@ impl<'a> LowerMethod<'a> { registers.push(test_reg); let test_type = self.register_type(test_reg); - let class = test_type.class_id(self.db(), self.self_type()).unwrap(); + let class = test_type.class_id(self.db()).unwrap(); let tag_reg = self.new_untracked_register(types::TypeRef::int()); let tag_field = class.field_by_index(self.db(), types::ENUM_TAG_INDEX).unwrap(); let member_fields = class.enum_fields(self.db()); let mut member_regs = Vec::new(); - self.block_mut(test_block).get_field(tag_reg, test_reg, tag_field, loc); + self.block_mut(test_block) + .get_field(tag_reg, test_reg, class, tag_field, loc); for &field in &member_fields { let reg = self.new_untracked_register(types::TypeRef::Any); - self.block_mut(test_block).get_field(reg, test_reg, field, loc); + self.block_mut(test_block) + .get_field(reg, test_reg, class, field, loc); member_regs.push(reg); } @@ -3040,38 +3010,10 @@ impl<'a> LowerMethod<'a> { self.decision(state, case.node, block, case_registers); } - self.block_mut(test_block).jump_table(tag_reg, blocks, loc); + self.block_mut(test_block).switch(tag_reg, blocks, loc); test_block } - fn else_argument( - &mut self, - node: Option, - value: RegisterId, - else_location: LocationId, - ) { - let (id, loc) = match node { - Some(hir::BlockArgument { - variable_id: Some(id), - location: loc, - .. - }) => (id, self.add_location(loc)), - _ => { - // If no argument is given or it's a wildcard, we just drop the - // thrown value right away. - self.drop_register(value, else_location); - return; - } - }; - - let reg = self.new_variable(id); - - self.add_drop_flag(reg, loc); - self.current_block_mut().move_register(reg, value, loc); - self.variable_mapping.insert(id, reg); - self.mark_register_as_moved(value); - } - fn identifier(&mut self, node: hir::IdentifierRef) -> RegisterId { let loc = self.add_location(node.location.clone()); @@ -3084,11 +3026,10 @@ impl<'a> LowerMethod<'a> { } types::IdentifierKind::Method(info) => { let entered = self.enter_call_scope(); - let reg = self.new_register(info.returns); + let ret = info.returns; + let reg = self.handle_call(info, None, Vec::new(), loc); - self.handle_call(info, None, Vec::new(), loc); - self.current_block_mut().reduce_call(loc); - self.current_block_mut().move_result(reg, loc); + self.reduce_call(ret, loc); self.exit_call_scope(entered, reg); reg } @@ -3097,14 +3038,15 @@ impl<'a> LowerMethod<'a> { self.state.diagnostics.implicit_receiver_moved( &node.name, self.file(), - self.mir.location(loc).range.clone(), + self.mir.location(loc).clone(), ); } let rec = self.self_register; let reg = self.new_field(info.id, info.variable_type); - self.current_block_mut().get_field(reg, rec, info.id, loc); + self.current_block_mut() + .get_field(reg, rec, info.class, info.id, loc); reg } types::IdentifierKind::Unknown => unreachable!(), @@ -3121,6 +3063,7 @@ impl<'a> LowerMethod<'a> { }; let rec = self.self_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); let name = &node.name; let check_loc = &node.location; @@ -3137,7 +3080,7 @@ impl<'a> LowerMethod<'a> { } } - self.current_block_mut().get_field(reg, rec, id, loc); + self.current_block_mut().get_field(reg, rec, class, id, loc); reg } @@ -3153,15 +3096,14 @@ impl<'a> LowerMethod<'a> { types::ConstantKind::Method(info) => { let entered = self.enter_call_scope(); let loc = self.add_location(node.location); - let reg = self.new_register(info.returns); + let ret = info.returns; + let reg = self.handle_call(info, None, Vec::new(), loc); - self.handle_call(info, None, Vec::new(), loc); - self.current_block_mut().reduce_call(loc); - self.current_block_mut().move_result(reg, loc); + self.reduce_call(ret, loc); self.exit_call_scope(entered, reg); reg } - types::ConstantKind::Unknown => unreachable!(), + _ => unreachable!(), } } @@ -3173,13 +3115,15 @@ impl<'a> LowerMethod<'a> { } fn closure(&mut self, node: hir::Closure) -> RegisterId { + self.check_inferred(node.resolved_type, &node.location); + let module = self.module.id; let closure_id = node.closure_id.unwrap(); let moving = closure_id.is_moving(self.db()); let class_id = types::Class::alloc( self.db_mut(), format!("Closure{}", closure_id.0), - types::ClassKind::Regular, + types::ClassKind::Closure, types::Visibility::Private, module, ); @@ -3189,7 +3133,7 @@ impl<'a> LowerMethod<'a> { module, types::CALL_METHOD.to_string(), types::Visibility::Public, - types::MethodKind::Instance, + types::MethodKind::Mutable, ); let gen_class_ins = @@ -3197,11 +3141,8 @@ impl<'a> LowerMethod<'a> { let call_rec_type = types::TypeRef::Mut(gen_class_ins); let returns = closure_id.return_type(self.db()); - let throws = closure_id.throw_type(self.db()); - method_id.set_self_type(self.db_mut(), gen_class_ins); method_id.set_receiver(self.db_mut(), call_rec_type); - method_id.set_throw_type(self.db_mut(), throws); method_id.set_return_type(self.db_mut(), returns); for arg in closure_id.arguments(self.db()) { @@ -3266,7 +3207,13 @@ impl<'a> LowerMethod<'a> { let val = self.input_register(self_reg, captured_as, None, loc); - self.current_block_mut().set_field(gen_class_reg, field, val, loc); + self.current_block_mut().set_field( + gen_class_reg, + class_id, + field, + val, + loc, + ); method_id.set_field_type(self.db_mut(), name, field, captured_as); captured_self_field = Some((field, exposed_as)); @@ -3304,7 +3251,13 @@ impl<'a> LowerMethod<'a> { let val = self.input_register(raw, captured_as, None, loc); - self.current_block_mut().set_field(gen_class_reg, field, val, loc); + self.current_block_mut().set_field( + gen_class_reg, + class_id, + field, + val, + loc, + ); field_index += 1; @@ -3314,7 +3267,7 @@ impl<'a> LowerMethod<'a> { if field_index >= FIELDS_LIMIT { self.state.diagnostics.error( - DiagnosticId::InvalidClass, + DiagnosticId::InvalidType, format!( "Closures can't capture more than {} variables", FIELDS_LIMIT @@ -3325,7 +3278,7 @@ impl<'a> LowerMethod<'a> { } let mut mir_class = Class::new(class_id); - let mut mir_method = Method::new(method_id); + let mut mir_method = Method::new(method_id, loc); let mut lower = LowerMethod::new( self.state, self.mir, @@ -3354,7 +3307,6 @@ impl<'a> LowerMethod<'a> { self.mir.methods.insert(method_id, mir_method); self.mir.classes.insert(class_id, mir_class); self.module.classes.push(class_id); - self.mir.closure_classes.insert(closure_id, class_id); gen_class_reg } @@ -3369,8 +3321,10 @@ impl<'a> LowerMethod<'a> { let &field = self.variable_fields.get(&id).unwrap(); let ® = self.field_mapping.get(&field).unwrap(); let rec = self.surrounding_type_register; + let class = self.register_type(rec).class_id(self.db()).unwrap(); - self.current_block_mut().get_field(reg, rec, field, location); + self.current_block_mut() + .get_field(reg, rec, class, field, location); reg } } @@ -3393,7 +3347,7 @@ impl<'a> LowerMethod<'a> { } fn add_current_block(&mut self) -> BlockId { - self.current_block = self.method.body.add_block(); + self.current_block = self.add_block(); self.current_block } @@ -3416,11 +3370,7 @@ impl<'a> LowerMethod<'a> { } fn in_connected_block(&self) -> bool { - self.block_is_connected(self.current_block) - } - - fn block_is_connected(&self, block: BlockId) -> bool { - self.method.body.is_connected(block) + self.method.body.is_connected(self.current_block) } /// Returns the register to use for an output expression (`return` or @@ -3451,11 +3401,11 @@ impl<'a> LowerMethod<'a> { } // When returning/throwing a field or `self` as a reference, we must - // increment the reference count. + // return a new reference. if self.register_kind(reg).is_field_or_self() { let res = self.new_register(typ); - self.current_block_mut().increment(res, reg, loc); + self.current_block_mut().reference(res, reg, loc); return res; } @@ -3521,18 +3471,14 @@ impl<'a> LowerMethod<'a> { return; } - let loc = self.mir.location(location).range.clone(); + let loc = self.mir.location(location).clone(); self.state.diagnostics.error( - DiagnosticId::InvalidMove, + DiagnosticId::Moved, format!( "This value can't be moved out of '{}', \ as it defines a custom destructor", - types::format_type_with_self( - self.db(), - stype, - self.surrounding_type() - ), + format_type(self.db(), self.surrounding_type()), ), self.file(), loc, @@ -3589,20 +3535,36 @@ impl<'a> LowerMethod<'a> { } } + // Value types are always passed as a new value, whether the receiving + // argument is owned or a reference. + // + // This ensures that if we pass the value to generic code, it can freely + // add references to it (if the value is boxed), without this affecting + // our current code (i.e. by said reference outliving the input value). + if register_type.is_value_type(self.db()) + && !register_type.use_atomic_reference_counting(self.db()) + { + return self.clone_value_type( + register, + register_type, + true, + location, + ); + } + if register_type.is_owned_or_uni(self.db()) { - match expected { - // Owned values passed to references are implicitly passed as - // references. - Some(exp) if !exp.is_owned_or_uni(self.db()) => { + if let Some(exp) = expected { + // Regular owned values passed to references are implicitly + // passed as references. + if !exp.is_owned_or_uni(self.db()) { let typ = register_type.cast_according_to(exp, self.db()); let reg = self.new_register(typ); self.mark_register_as_moved(reg); - self.current_block_mut().increment(reg, register, location); + self.current_block_mut().reference(reg, register, location); return reg; } - _ => {} } self.check_field_move(register, location); @@ -3627,25 +3589,6 @@ impl<'a> LowerMethod<'a> { return register; } - // References of value types passed to owned values should be cloned. - // This allows passing e.g. `ref Int` to something that expects `Int`, - // without the need for an explicit clone. - if register_type.is_value_type(self.db()) - && expected.map_or(false, |v| v.is_owned_or_uni(self.db())) - { - // In this case we force cloning, so expressions such as - // `foo(vals[0])` pass a clone instead of passing the returned ref - // directly. If we were to pass the ref directly, `foo` might drop - // it thinking its an owned value, then fail because a ref still - // exists. - return self.clone_value_type( - register, - register_type, - true, - location, - ); - } - // For reference types we only need to increment if they originate from // a variable or field, as regular registers can't be referred to more // than once. @@ -3654,7 +3597,7 @@ impl<'a> LowerMethod<'a> { { let reg = self.new_register(register_type); - self.current_block_mut().increment(reg, register, location); + self.current_block_mut().reference(reg, register, location); self.mark_register_as_moved(reg); return reg; @@ -3691,20 +3634,35 @@ impl<'a> LowerMethod<'a> { } let reg = self.new_register(typ); - let class = typ.class_id(self.db(), self.self_type()).unwrap(); - let kind = if class.kind(self.db()).is_async() { - CloneKind::Process - } else { - match class.0 { - types::INT_ID => CloneKind::Int, - types::FLOAT_ID => CloneKind::Float, - types::STRING_ID => CloneKind::String, - _ => CloneKind::Other, + let class = typ.class_id(self.db()).unwrap(); + + match class.0 { + types::INT_ID => { + self.current_block_mut().clone( + CloneKind::Int, + reg, + source, + location, + ); } - }; + types::FLOAT_ID => { + self.current_block_mut().clone( + CloneKind::Float, + reg, + source, + location, + ); + } + _ if class.kind(self.db()).is_extern() => { + self.current_block_mut().move_register(reg, source, location); + } + _ => { + self.current_block_mut() + .increment_atomic(reg, source, location); + } + } self.mark_register_as_moved(reg); - self.current_block_mut().clone(kind, reg, source, location); reg } @@ -3951,7 +3909,10 @@ impl<'a> LowerMethod<'a> { register: RegisterId, location: LocationId, ) { - self.current_block_mut().get_field(register, receiver, field, location); + let class = self.register_type(receiver).class_id(self.db()).unwrap(); + + self.current_block_mut() + .get_field(register, receiver, class, field, location); self.unconditional_drop_register(register, location); } @@ -4072,7 +4033,7 @@ impl<'a> LowerMethod<'a> { } fn register_kind(&self, register: RegisterId) -> RegisterKind { - self.register_kinds[register.0] + self.register_kinds[register.0 as usize] } fn register_is_available(&mut self, register: RegisterId) -> bool { @@ -4206,9 +4167,7 @@ impl<'a> LowerMethod<'a> { } fn add_location(&mut self, range: SourceLocation) -> LocationId { - let module = self.module.id; - - self.mir.add_location(module, self.method.id, range) + self.mir.add_location(range) } fn last_location(&self) -> LocationId { @@ -4228,7 +4187,7 @@ impl<'a> LowerMethod<'a> { } fn self_type(&self) -> types::TypeId { - self.method.id.self_type(self.db()) + self.method.id.receiver_id(self.db()) } fn surrounding_type(&self) -> types::TypeRef { @@ -4238,6 +4197,17 @@ impl<'a> LowerMethod<'a> { fn in_closure(&self) -> bool { self.self_register != self.surrounding_type_register } + + fn reduce_call(&mut self, returns: types::TypeRef, location: LocationId) { + // If the method never returns there's no point in generating a + // reduction. Doing this can also break the LLVM code generation + // process. + if returns.is_never(self.db()) { + return; + } + + self.current_block_mut().reduce_call(location); + } } /// A compiler pass that cleans up basic blocks. @@ -4256,22 +4226,23 @@ pub(crate) fn clean_up_basic_blocks(mir: &mut Mir) { let blocks = &method.body.blocks; let mut new_blocks = Vec::new(); let mut id_map = vec![BlockId(0); blocks.len()]; + let mut valid = Vec::with_capacity(blocks.len()); for (index, block) in blocks.iter().enumerate() { - if block.instructions.is_empty() { + if block.instructions.is_empty() + || !method.body.is_connected(BlockId(index)) + { + // Empty and unreachable blocks are useless, so we get rid of + // them here. continue; } id_map[index] = BlockId(new_blocks.len()); - + valid.push((index, block)); new_blocks.push(Block::new()); } - for (index, block) in blocks.iter().enumerate() { - if block.instructions.is_empty() { - continue; - } - + for (index, block) in valid { let block_id = id_map[index]; new_blocks[block_id.0].instructions = block.instructions.clone(); @@ -4288,16 +4259,27 @@ pub(crate) fn clean_up_basic_blocks(mir: &mut Mir) { vec![ok, err] } - Instruction::BranchResult(ins) => { - let ok = id_map[find_successor(blocks, ins.ok).0]; - let err = id_map[find_successor(blocks, ins.error).0]; + Instruction::DecrementAtomic(ins) => { + let ok = id_map[find_successor(blocks, ins.if_true).0]; + let err = + id_map[find_successor(blocks, ins.if_false).0]; - ins.ok = ok; - ins.error = err; + ins.if_true = ok; + ins.if_false = err; vec![ok, err] } - Instruction::JumpTable(ins) => { + Instruction::Switch(ins) => { + for index in 0..ins.blocks.len() { + let old_id = ins.blocks[index]; + + ins.blocks[index] = + id_map[find_successor(blocks, old_id).0]; + } + + ins.blocks.clone() + } + Instruction::SwitchKind(ins) => { for index in 0..ins.blocks.len() { let old_id = ins.blocks[index]; @@ -4365,6 +4347,15 @@ pub(crate) fn clean_up_basic_blocks(mir: &mut Mir) { /// /// For example, a `drop()` instruction used on a type parameter is expanded /// into a conditional drop or decrement. +/// +/// This pass exists because when initially generating MIR, we might not have +/// enough information to generate the correct drop logic (e.g. we're depending +/// on a class that has yet to be lowered to MIR). +/// +/// This pass should be run after applying any optimisation on MIR, that way we +/// can generate the most ideal drop code. For example, inlining a method may +/// influence how certain values are dropped. Running this pass at the end means +/// we don't have to undo some of the work this pass does. pub(crate) struct ExpandDrop<'a> { db: &'a types::Database, method: &'a mut Method, @@ -4409,17 +4400,14 @@ impl<'a> ExpandDrop<'a> { let typ = self.method.registers.value_type(val); let mut succ = Vec::new(); let after_id = self.add_block(); - let stype = self.method.id.self_type(self.db); swap(&mut succ, &mut self.block_mut(block_id).successors); if typ.use_reference_counting(self.db) { self.drop_reference(block_id, after_id, val, typ, loc); - } else if typ.use_atomic_reference_counting(self.db, stype) { + } else if typ.use_atomic_reference_counting(self.db) { self.drop_atomic(block_id, after_id, val, loc); - } else if typ.is_trait_instance(self.db) - || typ.is_self_type(self.db) - { + } else if typ.is_trait_instance(self.db) { self.drop_owned_trait_or_self( block_id, after_id, @@ -4460,19 +4448,19 @@ impl<'a> ExpandDrop<'a> { value: RegisterId, location: LocationId, ) { - let stype = self.method.id.self_type(self.db); let drop_id = self.add_block(); - let check_reg = self.method.registers.alloc(types::TypeRef::boolean()); let check = self.block_mut(before_id); - check.decrement_atomic(check_reg, value, location); - check.branch(check_reg, drop_id, after_id, location); + check.decrement_atomic(value, drop_id, after_id, location); - if self.method.registers.value_type(value).is_string(self.db, stype) { + if self.method.registers.value_type(value).is_string(self.db) { // Strings can be dropped directly instead of going through the // dropper. - self.block_mut(drop_id).raw_instruction( - Opcode::StringDrop, + let reg = self.method.registers.alloc(types::TypeRef::nil()); + + self.block_mut(drop_id).call_builtin( + reg, + types::BuiltinFunction::StringDrop, vec![value], location, ); @@ -4498,49 +4486,28 @@ impl<'a> ExpandDrop<'a> { value_type: types::TypeRef, location: LocationId, ) { - let stype = self.method.id.self_type(self.db); - - if value_type.use_atomic_reference_counting(self.db, stype) { - let reg = self.method.registers.alloc(types::TypeRef::boolean()); - - self.block_mut(before_id).decrement_atomic(reg, value, location); + if value_type.use_atomic_reference_counting(self.db) { + self.drop_atomic(before_id, after_id, value, location); + } else if value_type.is_value_type(self.db) { + self.block_mut(before_id).free(value, location); self.block_mut(before_id).goto(after_id, location); - - self.method.body.add_edge(before_id, after_id); - } else if value_type.is_type_parameter(self.db) - || value_type.is_self_type(self.db) - { - let atomic_id = self.add_block(); - let ref_id = self.add_block(); - let kind_reg = self.method.registers.alloc(types::TypeRef::int()); - - self.block_mut(before_id).ref_kind(kind_reg, value, location); - self.block_mut(before_id).jump_table( - kind_reg, - vec![after_id, ref_id, atomic_id, after_id], - location, - ); - - self.block_mut(ref_id).decrement(value, location); - self.block_mut(ref_id).goto(after_id, location); - - let decr_reg = - self.method.registers.alloc(types::TypeRef::boolean()); - - self.block_mut(atomic_id) - .decrement_atomic(decr_reg, value, location); - self.block_mut(atomic_id).goto(after_id, location); - - self.method.body.add_edge(ref_id, after_id); - self.method.body.add_edge(atomic_id, after_id); - self.method.body.add_edge(before_id, atomic_id); - self.method.body.add_edge(before_id, ref_id); self.method.body.add_edge(before_id, after_id); - } else { + } else if value_type.class_id(self.db).is_some() { self.block_mut(before_id).decrement(value, location); self.block_mut(before_id).goto(after_id, location); - self.method.body.add_edge(before_id, after_id); + } else { + // If the value is typed as a type parameter or trait, it may be + // passed a value type or a type that uses atomic reference + // counting. As such we fall back to a runtime check for such cases. + // + // Disabling the use of a dropper here ensures that _if_ the value + // is a value type (in which case it's treated as an owned value), + // we simply free it, as running its dropper is redundant in that + // case. + self.drop_with_runtime_check( + before_id, after_id, value, false, location, + ); } } @@ -4554,20 +4521,21 @@ impl<'a> ExpandDrop<'a> { ) { let atomic_id = self.add_block(); let owned_id = self.add_block(); - let kind_reg = self.method.registers.alloc(types::TypeRef::int()); + let value_id = self.add_block(); - self.block_mut(before_id).ref_kind(kind_reg, value, location); - self.block_mut(before_id).jump_table( - kind_reg, - vec![owned_id, after_id, atomic_id, after_id], + self.block_mut(before_id).switch_kind( + value, + vec![owned_id, after_id, atomic_id, after_id, value_id, value_id], location, ); self.drop_owned(owned_id, after_id, value, dropper, location); self.drop_atomic(atomic_id, after_id, value, location); + self.drop_value_type(value_id, after_id, value, location); self.method.body.add_edge(before_id, atomic_id); self.method.body.add_edge(before_id, owned_id); + self.method.body.add_edge(before_id, value_id); self.method.body.add_edge(before_id, after_id); } @@ -4586,12 +4554,22 @@ impl<'a> ExpandDrop<'a> { { self.call_dropper(before_id, value, location); } else { - self.block_mut(before_id).check_refs(value, location); self.block_mut(before_id).free(value, location); } self.block_mut(before_id).goto(after_id, location); + self.method.body.add_edge(before_id, after_id); + } + fn drop_value_type( + &mut self, + before_id: BlockId, + after_id: BlockId, + value: RegisterId, + location: LocationId, + ) { + self.block_mut(before_id).free(value, location); + self.block_mut(before_id).goto(after_id, location); self.method.body.add_edge(before_id, after_id); } @@ -4606,12 +4584,11 @@ impl<'a> ExpandDrop<'a> { let ref_id = self.add_block(); let atomic_id = self.add_block(); let owned_id = self.add_block(); - let kind_reg = self.method.registers.alloc(types::TypeRef::int()); + let value_id = self.add_block(); - self.block_mut(before_id).ref_kind(kind_reg, value, location); - self.block_mut(before_id).jump_table( - kind_reg, - vec![owned_id, ref_id, atomic_id, after_id], + self.block_mut(before_id).switch_kind( + value, + vec![owned_id, ref_id, atomic_id, after_id, value_id, value_id], location, ); @@ -4620,11 +4597,13 @@ impl<'a> ExpandDrop<'a> { self.drop_owned(owned_id, after_id, value, dropper, location); self.drop_atomic(atomic_id, after_id, value, location); + self.drop_value_type(value_id, after_id, value, location); self.method.body.add_edge(before_id, ref_id); self.method.body.add_edge(ref_id, after_id); self.method.body.add_edge(before_id, atomic_id); self.method.body.add_edge(before_id, owned_id); + self.method.body.add_edge(before_id, value_id); self.method.body.add_edge(before_id, after_id); } @@ -4634,24 +4613,25 @@ impl<'a> ExpandDrop<'a> { value: RegisterId, location: LocationId, ) { - if let Some(class) = self - .method - .registers - .value_type(value) - .class_id(self.db, self.method.id.self_type(self.db)) - { + let typ = self.method.registers.value_type(value); + let reg = self.method.registers.alloc(types::TypeRef::nil()); + + if let Some(class) = typ.class_id(self.db) { // If the type of the receiver is statically known to be a class, we // can just call the dropper directly. let method = class.method(self.db, types::DROPPER_METHOD).unwrap(); - self.block_mut(block).call_virtual( + self.block_mut(block).call_instance( + reg, value, method, Vec::new(), location, ); - } else { - self.block_mut(block).call_dropper(value, location); + } else if !typ.is_any(self.db) { + // Values of type `Any` could be, well, anything, and as such it's + // not safe to drop them. + self.block_mut(block).call_dropper(reg, value, location); } self.block_mut(block).reduce_call(location); @@ -4666,6 +4646,158 @@ impl<'a> ExpandDrop<'a> { } } +/// A compiler pass that expands the reference() instruction into the correct +/// instruction to create an alias. +pub(crate) struct ExpandReference<'a> { + db: &'a types::Database, + method: &'a mut Method, +} + +impl<'a> ExpandReference<'a> { + pub(crate) fn run_all(db: &'a types::Database, mir: &'a mut Mir) { + for method in mir.methods.values_mut() { + ExpandReference { db, method }.run(); + } + } + + pub(crate) fn run(mut self) { + let mut block_idx = 0; + + while block_idx < self.method.body.blocks.len() { + let block_id = BlockId(block_idx); + + if let Some(ins_idx) = self + .block_mut(block_id) + .instructions + .iter() + .position(|ins| matches!(ins, Instruction::Reference(_))) + { + let (ins, remaining_ins) = { + let block = self.block_mut(block_id); + + if let Instruction::Reference(ins) = + block.instructions.remove(ins_idx) + { + (ins, block.instructions.split_off(ins_idx)) + } else { + unreachable!() + } + }; + + let loc = ins.location; + let reg = ins.register; + let val = ins.value; + let typ = self.method.registers.value_type(val); + let mut succ = Vec::new(); + let after_id = self.add_block(); + + swap(&mut succ, &mut self.block_mut(block_id).successors); + + if let Some(class) = typ.class_id(self.db) { + self.increment_class( + block_id, after_id, reg, val, class, loc, + ); + } else { + self.increment_with_runtime_check( + block_id, after_id, reg, val, loc, + ); + } + + for succ_id in succ { + self.method.body.remove_predecessor(succ_id, block_id); + self.method.body.add_edge(after_id, succ_id); + } + + self.block_mut(after_id).instructions = remaining_ins; + } + + block_idx += 1; + } + } + + fn increment_class( + &mut self, + block_id: BlockId, + after_id: BlockId, + register: RegisterId, + value: RegisterId, + class: types::ClassId, + location: LocationId, + ) { + if class.is_atomic(self.db) { + self.block_mut(block_id) + .increment_atomic(register, value, location); + } else { + self.block_mut(block_id).increment(register, value, location); + } + + self.block_mut(block_id).goto(after_id, location); + self.method.body.add_edge(block_id, after_id); + } + + fn increment_with_runtime_check( + &mut self, + block_id: BlockId, + after_id: BlockId, + register: RegisterId, + value: RegisterId, + location: LocationId, + ) { + let normal_id = self.add_block(); + let atomic_id = self.add_block(); + let int_id = self.add_block(); + let float_id = self.add_block(); + let perm_id = self.add_block(); + + self.block_mut(block_id).switch_kind( + value, + vec![normal_id, normal_id, atomic_id, perm_id, int_id, float_id], + location, + ); + + self.block_mut(normal_id).increment(register, value, location); + self.block_mut(normal_id).goto(after_id, location); + + self.block_mut(atomic_id).increment_atomic(register, value, location); + self.block_mut(atomic_id).goto(after_id, location); + + self.block_mut(int_id).clone(CloneKind::Int, register, value, location); + self.block_mut(int_id).goto(after_id, location); + + self.block_mut(float_id).clone( + CloneKind::Float, + register, + value, + location, + ); + self.block_mut(float_id).goto(after_id, location); + + self.block_mut(perm_id).move_register(register, value, location); + self.block_mut(perm_id).goto(after_id, location); + + self.method.body.add_edge(block_id, normal_id); + self.method.body.add_edge(block_id, atomic_id); + self.method.body.add_edge(block_id, after_id); + self.method.body.add_edge(block_id, int_id); + self.method.body.add_edge(block_id, float_id); + self.method.body.add_edge(block_id, perm_id); + + self.method.body.add_edge(normal_id, after_id); + self.method.body.add_edge(atomic_id, after_id); + self.method.body.add_edge(int_id, after_id); + self.method.body.add_edge(float_id, after_id); + self.method.body.add_edge(perm_id, after_id); + } + + fn block_mut(&mut self, id: BlockId) -> &mut Block { + &mut self.method.body.blocks[id.0] + } + + fn add_block(&mut self) -> BlockId { + self.method.body.add_block() + } +} + fn find_successor(blocks: &[Block], old_id: BlockId) -> BlockId { let mut id = old_id; diff --git a/compiler/src/mir/pattern_matching.rs b/compiler/src/mir/pattern_matching.rs index 26d4b02b9..010685e31 100644 --- a/compiler/src/mir/pattern_matching.rs +++ b/compiler/src/mir/pattern_matching.rs @@ -22,11 +22,94 @@ use crate::hir; use crate::mir::{BlockId, Constant, Mir}; use crate::state::State; use std::collections::{HashMap, HashSet}; +use types::resolve::TypeResolver; use types::{ - ClassInstance, ClassKind, Database, FieldId, TypeContext, TypeId, TypeRef, - VariableId, VariantId, BOOLEAN_ID, INT_ID, STRING_ID, + ClassInstance, ClassKind, Database, FieldId, TypeArguments, TypeBounds, + TypeId, TypeRef, VariableId, VariantId, BOOLEAN_ID, INT_ID, STRING_ID, }; +fn add_missing_patterns( + db: &Database, + node: &Decision, + terms: &mut Vec, + missing: &mut HashSet, +) { + match node { + Decision::Success(_) => {} + Decision::Fail => { + let mut mapping = HashMap::new(); + + // At this point the terms stack looks something like this: + // `[term, term + arguments, term, ...]`. To construct a pattern + // name from this stack, we first map all variables to their + // term indexes. This is needed because when a term defines + // arguments, the terms for those arguments don't necessarily + // appear in order in the term stack. + // + // This mapping is then used when (recursively) generating a + // pattern name. + for (index, step) in terms.iter().enumerate() { + mapping.insert(&step.variable, index); + } + + let name = terms + .first() + .map(|term| term.pattern_name(terms, &mapping)) + .unwrap_or_else(|| "_".to_string()); + + missing.insert(name); + } + Decision::Guard(_, _, fallback) => { + add_missing_patterns(db, fallback, terms, missing); + } + Decision::Switch(var, cases, fallback) => { + for case in cases { + match &case.constructor { + Constructor::True => { + let name = "true".to_string(); + + terms.push(Term::new(*var, name, Vec::new())); + } + Constructor::False => { + let name = "false".to_string(); + + terms.push(Term::new(*var, name, Vec::new())); + } + Constructor::Int(_) | Constructor::String(_) => { + let name = "_".to_string(); + + terms.push(Term::new(*var, name, Vec::new())); + } + Constructor::Class(_) => { + let name = "_".to_string(); + + terms.push(Term::new(*var, name, Vec::new())); + } + Constructor::Tuple(_) => { + let name = String::new(); + let args = case.arguments.clone(); + + terms.push(Term::new(*var, name, args)); + } + Constructor::Variant(variant) => { + let args = case.arguments.clone(); + let name = variant.name(db).clone(); + + terms.push(Term::new(*var, name, args)); + } + } + + add_missing_patterns(db, &case.node, terms, missing); + terms.pop(); + } + + if let Some(node) = fallback { + add_missing_patterns(db, node, terms, missing); + } + } + } +} + /// A binding to define as part of a pattern. #[derive(Clone, Eq, PartialEq, Debug)] pub(crate) enum Binding { @@ -131,7 +214,7 @@ impl Pattern { for pat in n.values { let field = pat.field_id.unwrap(); - let index = field.index(db) as usize; + let index = field.index(db); args[index] = Pattern::from_hir(db, mir, pat.pattern); fields[index] = field; @@ -142,7 +225,9 @@ impl Pattern { hir::Pattern::Constant(n) => match n.kind { types::ConstantPatternKind::String(id) => { match mir.constants.get(&id) { - Some(Constant::String(v)) => Pattern::String(v.clone()), + Some(Constant::String(v)) => { + Pattern::String(v.as_ref().clone()) + } _ => unreachable!(), } } @@ -359,7 +444,7 @@ impl Match { let mut names = HashSet::new(); let mut steps = Vec::new(); - self.add_missing_patterns(db, &self.tree, &mut steps, &mut names); + add_missing_patterns(db, &self.tree, &mut steps, &mut names); let mut missing: Vec = names.into_iter().collect(); @@ -367,89 +452,6 @@ impl Match { missing.sort(); missing } - - fn add_missing_patterns( - &self, - db: &Database, - node: &Decision, - terms: &mut Vec, - missing: &mut HashSet, - ) { - match node { - Decision::Success(_) => {} - Decision::Fail => { - let mut mapping = HashMap::new(); - - // At this point the terms stack looks something like this: - // `[term, term + arguments, term, ...]`. To construct a pattern - // name from this stack, we first map all variables to their - // term indexes. This is needed because when a term defines - // arguments, the terms for those arguments don't necessarily - // appear in order in the term stack. - // - // This mapping is then used when (recursively) generating a - // pattern name. - for (index, step) in terms.iter().enumerate() { - mapping.insert(&step.variable, index); - } - - let name = terms - .first() - .map(|term| term.pattern_name(terms, &mapping)) - .unwrap_or_else(|| "_".to_string()); - - missing.insert(name); - } - Decision::Guard(_, _, fallback) => { - self.add_missing_patterns(db, fallback, terms, missing); - } - Decision::Switch(var, cases, fallback) => { - for case in cases { - match &case.constructor { - Constructor::True => { - let name = "true".to_string(); - - terms.push(Term::new(*var, name, Vec::new())); - } - Constructor::False => { - let name = "false".to_string(); - - terms.push(Term::new(*var, name, Vec::new())); - } - Constructor::Int(_) | Constructor::String(_) => { - let name = "_".to_string(); - - terms.push(Term::new(*var, name, Vec::new())); - } - Constructor::Class(_) => { - let name = "_".to_string(); - - terms.push(Term::new(*var, name, Vec::new())); - } - Constructor::Tuple(_) => { - let name = String::new(); - let args = case.arguments.clone(); - - terms.push(Term::new(*var, name, args)); - } - Constructor::Variant(variant) => { - let args = case.arguments.clone(); - let name = variant.name(db).clone(); - - terms.push(Term::new(*var, name, args)); - } - } - - self.add_missing_patterns(db, &case.node, terms, missing); - terms.pop(); - } - - if let Some(node) = fallback { - self.add_missing_patterns(db, node, terms, missing); - } - } - } - } } /// State to use for creating pattern matching variables. @@ -485,9 +487,6 @@ impl Variables { pub(crate) struct Compiler<'a> { state: &'a mut State, - /// The type of `Self` in the scope the `match` expression resides in. - self_type: TypeId, - /// The basic blocks that are reachable in the match expression. /// /// If a block isn't in this list it means its pattern is redundant. @@ -498,20 +497,23 @@ pub(crate) struct Compiler<'a> { /// The pattern matching variables/temporaries. variables: Variables, + + /// Type bounds to apply to types produced by patterns. + bounds: TypeBounds, } impl<'a> Compiler<'a> { pub(crate) fn new( state: &'a mut State, - self_type: TypeId, variables: Variables, + bounds: TypeBounds, ) -> Self { Self { state, reachable: HashSet::new(), missing: false, variables, - self_type, + bounds, } } @@ -799,17 +801,15 @@ impl<'a> Compiler<'a> { return types.into_iter().map(|t| self.new_variable(t)).collect(); } - let mut ctx = TypeContext::with_arguments( - self.self_type, - instance.type_arguments(self.db()).clone(), - ); + let args = TypeArguments::for_class(self.db_mut(), instance); types .into_iter() .map(|raw_type| { - let inferred = raw_type - .inferred(self.db_mut(), &mut ctx, false) - .cast_according_to(source_variable_type, self.db()); + let inferred = + TypeResolver::new(&mut self.state.db, &args, &self.bounds) + .resolve(raw_type) + .cast_according_to(source_variable_type, self.db()); self.new_variable(inferred) }) @@ -818,7 +818,7 @@ impl<'a> Compiler<'a> { fn variable_type(&mut self, variable: &Variable) -> Type { let typ = variable.value_type(&self.variables); - let type_id = typ.type_id(self.db(), self.self_type).unwrap(); + let type_id = typ.type_id(self.db()).unwrap(); let class_ins = if let TypeId::ClassInstance(ins) = type_id { ins } else { @@ -851,7 +851,7 @@ impl<'a> Compiler<'a> { Type::Finite(cons) } - ClassKind::Regular => { + ClassKind::Regular | ClassKind::Extern => { let fields = class_id.fields(self.db()); let args = fields .iter() @@ -877,9 +877,7 @@ impl<'a> Compiler<'a> { Vec::new(), )]) } - // Async classes can't be used in class patterns, so we should - // never encounter an async class here. - ClassKind::Async => unreachable!(), + _ => unreachable!(), }, } } @@ -900,7 +898,7 @@ mod tests { use similar_asserts::assert_eq; use types::module_name::ModuleName; use types::{ - Class, ClassId, ClassInstance, ClassKind, Module, TypeId, + Class, ClassInstance, ClassKind, Module, TypeId, Variable as VariableType, Visibility, }; @@ -930,10 +928,7 @@ mod tests { } fn compiler(state: &mut State) -> Compiler { - let self_type = - TypeId::ClassInstance(ClassInstance::new(ClassId::int())); - - Compiler::new(state, self_type, Variables::new()) + Compiler::new(state, Variables::new(), TypeBounds::new()) } fn success(block: BlockId) -> Decision { diff --git a/compiler/src/mir/printer.rs b/compiler/src/mir/printer.rs index 040cf5968..4f0f4f196 100644 --- a/compiler/src/mir/printer.rs +++ b/compiler/src/mir/printer.rs @@ -23,7 +23,7 @@ pub(crate) fn to_dot(db: &Database, mir: &Mir, methods: &[&Method]) -> String { buffer.push_str("node[fontname=\"monospace\", fontsize=10];\n"); buffer.push_str("edge[fontname=\"monospace\", fontsize=10];\n"); - let rec_name = match method.id.self_type(db) { + let rec_name = match method.id.receiver_id(db) { TypeId::Class(id) => id.name(db), TypeId::Trait(id) => id.name(db), TypeId::ClassInstance(ins) => ins.instance_of().name(db), @@ -74,7 +74,7 @@ pub(crate) fn to_dot(db: &Database, mir: &Mir, methods: &[&Method]) -> String { buffer, "{}{}", ins.format(db).replace('>', ">").replace('<', "<"), - mir.location(ins.location()).range.line_range.start(), + mir.location(ins.location()).line_range.start(), ); } diff --git a/compiler/src/modules_parser.rs b/compiler/src/modules_parser.rs index 37cf3ffb3..a3881c2d1 100644 --- a/compiler/src/modules_parser.rs +++ b/compiler/src/modules_parser.rs @@ -67,9 +67,9 @@ impl<'a> ModulesParser<'a> { } for name in &self.state.config.implicit_imports { - // Implicitly imported modules are always part of libstd, so we + // Implicitly imported modules are always part of std, so we // don't need to search through all the source paths. - let path = self.state.config.libstd.join(name.to_path()); + let path = self.state.config.std.join(name.to_path()); scheduled.insert(name.clone()); pending.push((name.clone(), path)); @@ -105,8 +105,7 @@ impl<'a> ModulesParser<'a> { } } - let mut result: Vec = - modules.into_iter().map(|(_, v)| v).collect(); + let mut result: Vec = modules.into_values().collect(); // We sort the modules so we process them in a deterministic order, // resulting in diagnostics being produced in a deterministic order. @@ -253,7 +252,7 @@ mod tests { let mut state = State::new(Config::new()); - state.config.libstd = temp_dir(); + state.config.std = temp_dir(); state.config.implicit_imports = vec![ModuleName::new("parsing2d")]; let mut pass = ModulesParser::new(&mut state); diff --git a/compiler/src/presenters.rs b/compiler/src/presenters.rs index a3ab53fbc..4fe07fee7 100644 --- a/compiler/src/presenters.rs +++ b/compiler/src/presenters.rs @@ -8,7 +8,7 @@ pub(crate) trait Presenter { fn present(&self, diagnostics: &Diagnostics); } -/// Prints diagnostics in a compact text form, optionally enabling the use of +/// Print diagnostics in a compact text form, optionally enabling the use of /// colors. /// /// The resulting output looks like this: @@ -92,7 +92,7 @@ impl Presenter for TextPresenter { } } -/// Prints diagnostics in JSON. +/// A type that presents diagnostics as JSON. pub(crate) struct JSONPresenter {} impl JSONPresenter { diff --git a/compiler/src/symbol_names.rs b/compiler/src/symbol_names.rs new file mode 100644 index 000000000..5d28c7948 --- /dev/null +++ b/compiler/src/symbol_names.rs @@ -0,0 +1,84 @@ +//! Mangled symbol names for native code. +use crate::mir::Mir; +use std::collections::HashMap; +use types::{ClassId, ConstantId, Database, MethodId, ModuleId}; + +pub(crate) const SYMBOL_PREFIX: &str = "_I"; + +/// A cache of mangled symbol names. +pub(crate) struct SymbolNames { + pub(crate) classes: HashMap, + pub(crate) methods: HashMap, + pub(crate) constants: HashMap, + pub(crate) setup_functions: HashMap, +} + +impl SymbolNames { + pub(crate) fn new(db: &Database, mir: &Mir) -> Self { + let mut classes = HashMap::new(); + let mut methods = HashMap::new(); + let mut constants = HashMap::new(); + let mut setup_functions = HashMap::new(); + + for module_index in 0..mir.modules.len() { + let module = &mir.modules[module_index]; + let mod_name = module.id.name(db).as_str(); + + for &class in &module.classes { + let is_mod = class.kind(db).is_module(); + let class_name = format!( + "{}T_{}::{}", + SYMBOL_PREFIX, + mod_name, + class.name(db) + ); + + classes.insert(class, class_name); + + for &method in &mir.classes[&class].methods { + let name = if is_mod { + // This ensures that methods such as + // `std::process.sleep` aren't formatted as + // `std::process::std::process.sleep`. This in turn + // makes stack traces easier to read. + format!( + "{}M_{}.{}", + SYMBOL_PREFIX, + mod_name, + method.name(db) + ) + } else { + format!( + "{}M_{}::{}.{}", + SYMBOL_PREFIX, + mod_name, + class.name(db), + method.name(db) + ) + }; + + methods.insert(method, name); + } + } + } + + for id in mir.constants.keys() { + let mod_name = id.module(db).name(db).as_str(); + let name = id.name(db); + + constants.insert( + *id, + format!("{}C_{}::{}", SYMBOL_PREFIX, mod_name, name), + ); + } + + for &id in mir.modules.keys() { + let name = + format!("{}M_{}::$setup", SYMBOL_PREFIX, id.name(db).as_str()); + + setup_functions.insert(id, name); + } + + Self { classes, methods, constants, setup_functions } + } +} diff --git a/compiler/src/target.rs b/compiler/src/target.rs new file mode 100644 index 000000000..8e3b4357e --- /dev/null +++ b/compiler/src/target.rs @@ -0,0 +1,294 @@ +use std::fmt; + +/// The supported CPU architectures. +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum Architecture { + Amd64, + Arm64, +} + +impl Architecture { + pub(crate) fn from_str(input: &str) -> Option { + match input { + "amd64" => Some(Architecture::Amd64), + "arm64" => Some(Architecture::Arm64), + _ => None, + } + } + + pub(crate) fn native() -> Architecture { + if cfg!(target_arch = "x86_64") { + Architecture::Amd64 + } else if cfg!(target_arch = "aarch64") { + Architecture::Arm64 + } else { + panic!("The host architecture isn't supported"); + } + } +} + +/// The supported operating systems. +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum OperatingSystem { + Freebsd, + Linux, + Mac, + Openbsd, +} + +impl OperatingSystem { + pub(crate) fn from_str(input: &str) -> Option { + match input { + "freebsd" => Some(OperatingSystem::Freebsd), + "linux" => Some(OperatingSystem::Linux), + "mac" => Some(OperatingSystem::Mac), + "openbsd" => Some(OperatingSystem::Openbsd), + _ => None, + } + } + + pub(crate) fn native() -> OperatingSystem { + if cfg!(target_os = "freebsd") { + OperatingSystem::Freebsd + } else if cfg!(target_os = "linux") { + OperatingSystem::Linux + } else if cfg!(target_os = "macos") { + OperatingSystem::Mac + } else if cfg!(target_os = "openbsd") { + OperatingSystem::Openbsd + } else { + panic!("The host operating system isn't supported"); + } + } +} + +/// The ABI to target. +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum Abi { + Native, + Gnu, + Msvc, +} + +impl Abi { + pub(crate) fn from_str(input: &str) -> Option { + match input { + "native" => Some(Abi::Native), + "gnu" => Some(Abi::Gnu), + "msvc" => Some(Abi::Msvc), + _ => None, + } + } + + pub(crate) fn native() -> Abi { + if cfg!(target_env = "gnu") { + Abi::Gnu + } else if cfg!(target_env = "msvc") { + Abi::Msvc + } else { + Abi::Native + } + } +} + +/// A type describing the compile target, such as the operating system and +/// architecture. +#[derive(Eq, PartialEq, Debug)] +pub struct Target { + pub(crate) arch: Architecture, + pub(crate) os: OperatingSystem, + pub(crate) abi: Abi, +} + +impl Target { + /// Parses a target from a string. + /// + /// If the target is invalid, a None is returned. + pub(crate) fn from_str(input: &str) -> Option { + let mut iter = input.split('-'); + let arch = iter.next().and_then(Architecture::from_str)?; + let os = iter.next().and_then(OperatingSystem::from_str)?; + let abi = iter.next().and_then(Abi::from_str)?; + + Some(Target { arch, os, abi }) + } + + /// Returns the target for the current platform. + pub fn native() -> Target { + Target { + arch: Architecture::native(), + os: OperatingSystem::native(), + abi: Abi::native(), + } + } + + /// Returns a String describing the target using the LLVM triple format. + pub(crate) fn llvm_triple(&self) -> String { + let arch = match self.arch { + Architecture::Amd64 => "x86_64", + Architecture::Arm64 => "aarch64", + }; + + let os = match self.os { + OperatingSystem::Freebsd => "unknown-freebsd", + OperatingSystem::Mac => "apple-darwin", + OperatingSystem::Openbsd => "unknown-openbsd", + OperatingSystem::Linux => "unknown-linux-gnu", + }; + + format!("{}-{}", arch, os) + } + + pub(crate) fn arch_name(&self) -> &'static str { + match self.arch { + Architecture::Amd64 => "amd64", + Architecture::Arm64 => "arm64", + } + } + + pub(crate) fn os_name(&self) -> &'static str { + match self.os { + OperatingSystem::Freebsd => "freebsd", + OperatingSystem::Mac => "mac", + OperatingSystem::Openbsd => "openbsd", + OperatingSystem::Linux => "linux", + } + } + + pub(crate) fn abi_name(&self) -> &'static str { + match self.abi { + Abi::Native => match self.os { + OperatingSystem::Linux => "gnu", + _ => "native", + }, + Abi::Gnu => "gnu", + Abi::Msvc => "msvc", + } + } + + pub(crate) fn is_native(&self) -> bool { + self == &Target::native() + } +} + +impl fmt::Display for Target { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!( + fmt, + "{}-{}-{}", + self.arch_name(), + self.os_name(), + self.abi_name() + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn target(arch: Architecture, os: OperatingSystem, abi: Abi) -> Target { + Target { arch, os, abi } + } + + #[test] + fn test_operating_system_from_str() { + assert_eq!( + OperatingSystem::from_str("freebsd"), + Some(OperatingSystem::Freebsd) + ); + assert_eq!( + OperatingSystem::from_str("openbsd"), + Some(OperatingSystem::Openbsd) + ); + assert_eq!( + OperatingSystem::from_str("linux"), + Some(OperatingSystem::Linux) + ); + assert_eq!( + OperatingSystem::from_str("mac"), + Some(OperatingSystem::Mac) + ); + assert_eq!(OperatingSystem::from_str("bla"), None); + } + + #[test] + fn test_architecture_from_str() { + assert_eq!(Architecture::from_str("amd64"), Some(Architecture::Amd64)); + assert_eq!(Architecture::from_str("arm64"), Some(Architecture::Arm64)); + assert_eq!(Architecture::from_str("bla"), None); + } + + #[test] + fn test_target_from_str() { + assert_eq!( + Target::from_str("amd64-freebsd-native"), + Some(target( + Architecture::Amd64, + OperatingSystem::Freebsd, + Abi::Native + )) + ); + assert_eq!( + Target::from_str("arm64-linux-gnu"), + Some( + target(Architecture::Arm64, OperatingSystem::Linux, Abi::Gnu,) + ) + ); + + assert_eq!(Target::from_str("bla-linux-native"), None); + assert_eq!(Target::from_str("amd64-bla-native"), None); + assert_eq!(Target::from_str("amd64-linux"), None); + } + + #[test] + fn test_target_host() { + let target = Target::native(); + + assert_eq!(target.arch, Architecture::native()); + assert_eq!(target.os, OperatingSystem::native()); + } + + #[test] + fn test_target_llvm_triple() { + assert_eq!( + target(Architecture::Amd64, OperatingSystem::Linux, Abi::Native) + .llvm_triple(), + "x86_64-unknown-linux-gnu" + ); + assert_eq!( + target(Architecture::Amd64, OperatingSystem::Freebsd, Abi::Native) + .llvm_triple(), + "x86_64-unknown-freebsd" + ); + assert_eq!( + target(Architecture::Arm64, OperatingSystem::Mac, Abi::Native) + .llvm_triple(), + "aarch64-apple-darwin" + ); + } + + #[test] + fn test_target_to_string() { + assert_eq!( + target(Architecture::Amd64, OperatingSystem::Linux, Abi::Native) + .to_string(), + "amd64-linux-gnu" + ); + assert_eq!( + target(Architecture::Amd64, OperatingSystem::Freebsd, Abi::Native) + .to_string(), + "amd64-freebsd-native" + ); + assert_eq!( + target(Architecture::Arm64, OperatingSystem::Mac, Abi::Native) + .to_string(), + "arm64-mac-native" + ); + } + + #[test] + fn test_target_is_native() { + assert!(Target::native().is_native()); + } +} diff --git a/compiler/src/type_check/define_types.rs b/compiler/src/type_check/define_types.rs index 3c7723acf..c226a6364 100644 --- a/compiler/src/type_check/define_types.rs +++ b/compiler/src/type_check/define_types.rs @@ -3,15 +3,17 @@ use crate::diagnostics::DiagnosticId; use crate::hir; use crate::state::State; use crate::type_check::{ - CheckTypeSignature, DefineAndCheckTypeSignature, DefineTypeSignature, - Rules, TypeScope, + define_type_bounds, CheckTypeSignature, DefineAndCheckTypeSignature, + DefineTypeSignature, Rules, TypeScope, }; use std::path::PathBuf; +use types::check::TypeChecker; +use types::format::format_type; use types::{ - format_type, Class, ClassId, ClassInstance, ClassKind, Constant, Database, - ModuleId, Symbol, Trait, TraitId, TraitImplementation, TypeBounds, - TypeContext, TypeId, TypeParameter, TypeRef, Visibility, ENUM_TAG_FIELD, - ENUM_TAG_INDEX, FIELDS_LIMIT, MAIN_CLASS, VARIANTS_LIMIT, + Class, ClassId, ClassInstance, ClassKind, Constant, Database, ModuleId, + Symbol, Trait, TraitId, TraitImplementation, TypeId, TypeRef, Visibility, + ENUM_TAG_FIELD, ENUM_TAG_INDEX, FIELDS_LIMIT, MAIN_CLASS, OPTION_CLASS, + OPTION_MODULE, RESULT_CLASS, RESULT_MODULE, VARIANTS_LIMIT, }; /// The maximum number of members a single variant can store. We subtract one as @@ -64,7 +66,7 @@ impl<'a> DefineTypes<'a> { hir::ClassKind::Builtin => { if !self.module.is_std(self.db()) { self.state.diagnostics.error( - DiagnosticId::InvalidClass, + DiagnosticId::InvalidType, "Builtin classes can only be defined in 'std' modules", self.file(), node.location.clone(), @@ -76,7 +78,7 @@ impl<'a> DefineTypes<'a> { id } else { self.state.diagnostics.error( - DiagnosticId::InvalidClass, + DiagnosticId::InvalidType, format!("'{}' isn't a valid builtin class", name), self.file(), node.location.clone(), @@ -243,68 +245,22 @@ impl<'a> ImplementTraits<'a> { return; } - let mut bounds = TypeBounds::new(); - let rules = Rules::default(); - - for bound in &mut node.bounds { - let name = &bound.name.name; - - let param = - if let Some(id) = class_id.type_parameter(self.db(), name) { - id - } else { - self.state.diagnostics.undefined_symbol( - name, - self.file(), - bound.name.location.clone(), - ); - - continue; - }; - - if bounds.get(param).is_some() { - self.state.diagnostics.error( - DiagnosticId::InvalidBound, - format!( - "Bounds are already defined for type parameter '{}'", - name - ), - self.file(), - bound.location.clone(), - ); - - continue; - } - - let mut reqs = param.requirements(self.db()); - let scope = - TypeScope::new(self.module, TypeId::Class(class_id), None); - - let mut definer = DefineTypeSignature::new( - self.state, - self.module, - &scope, - rules, + if class_id.kind(self.db()).is_extern() { + self.state.diagnostics.error( + DiagnosticId::InvalidImplementation, + "Traits can't be implemented for extern classes", + self.file(), + node.location.clone(), ); - - for req in &mut bound.requirements { - if let Some(ins) = definer.as_trait_instance(req) { - reqs.push(ins); - } - } - - let name = param.name(self.db()).clone(); - let new_param = TypeParameter::alloc(self.db_mut(), name); - - new_param.add_requirements(self.db_mut(), reqs); - bounds.set(param, new_param); } - let class_ins = ClassInstance::for_instance_self_type( - self.db_mut(), + let bounds = define_type_bounds( + self.state, + self.module, class_id, - &bounds, + &mut node.bounds, ); + let class_ins = ClassInstance::rigid(self.db_mut(), class_id, &bounds); let scope = TypeScope::with_bounds( self.module, TypeId::ClassInstance(class_ins), @@ -312,6 +268,7 @@ impl<'a> ImplementTraits<'a> { &bounds, ); + let rules = Rules::default(); let mut definer = DefineTypeSignature::new(self.state, self.module, &scope, rules); @@ -451,13 +408,7 @@ impl<'a> CheckTraitImplementations<'a> { fn implement_trait(&mut self, node: &hir::ImplementTrait) { let class_ins = node.class_instance.unwrap(); let trait_ins = node.trait_instance.unwrap(); - let self_type = TypeId::ClassInstance(class_ins); - let mut checker = CheckTypeSignature::new( - self.state, - self.module, - self_type, - Rules::default(), - ); + let mut checker = CheckTypeSignature::new(self.state, self.module); checker.check_type_name(&node.trait_name); @@ -467,15 +418,10 @@ impl<'a> CheckTraitImplementations<'a> { } } - let mut context = TypeContext::new(self_type); - for req in trait_ins.instance_of().required_traits(self.db()) { - if !class_ins.type_check_with_trait_instance( - self.db_mut(), - req, - &mut context, - true, - ) { + let mut checker = TypeChecker::new(self.db()); + + if !checker.class_implements_trait(class_ins, req) { self.state.diagnostics.error( DiagnosticId::MissingTrait, format!( @@ -497,10 +443,31 @@ impl<'a> CheckTraitImplementations<'a> { fn db(&self) -> &Database { &self.state.db } +} - fn db_mut(&mut self) -> &mut Database { - &mut self.state.db - } +/// A compiler pass that defines the fields for the runtime's result type. +pub(crate) fn define_runtime_result_fields(state: &mut State) -> bool { + let class = ClassId::result(); + let module = class.module(&state.db); + + class.new_field( + &mut state.db, + "tag".to_string(), + 0, + TypeRef::int(), + Visibility::Public, + module, + ); + class.new_field( + &mut state.db, + "value".to_string(), + 1, + TypeRef::Any, + Visibility::Public, + module, + ); + + true } /// A compiler pass that defines the fields in a class. @@ -577,7 +544,7 @@ impl<'a> DefineFields<'a> { if id >= FIELDS_LIMIT { self.state.diagnostics.error( - DiagnosticId::InvalidClass, + DiagnosticId::InvalidType, format!( "Classes can't define more than {} fields", FIELDS_LIMIT @@ -621,25 +588,9 @@ impl<'a> DefineFields<'a> { ) .define_type(&mut node.value_type); - match typ { - TypeRef::OwnedSelf | TypeRef::RefSelf => { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "'Self' can't be used here as it prevents \ - creating instances of '{}'", - format_type(self.db(), scope.self_type), - ), - self.file(), - node.value_type.location().clone(), - ); - } - _ => {} - } - if !class_id.is_public(self.db()) && vis == Visibility::Public { self.state.diagnostics.error( - DiagnosticId::InvalidField, + DiagnosticId::InvalidSymbol, "Public fields can't be defined for private types", self.file(), node.location.clone(), @@ -717,6 +668,10 @@ impl<'a> DefineTypeParameters<'a> { } else { let pid = id.new_type_parameter(self.db_mut(), name.clone()); + if param.mutable { + pid.set_mutable(self.db_mut()); + } + param.type_parameter_id = Some(pid); } } @@ -737,6 +692,10 @@ impl<'a> DefineTypeParameters<'a> { } else { let pid = id.new_type_parameter(self.db_mut(), name.clone()); + if param.mutable { + pid.set_mutable(self.db_mut()); + } + param.type_parameter_id = Some(pid); } } @@ -854,41 +813,18 @@ impl<'a> CheckTypeParameters<'a> { for expr in module.expressions.iter_mut() { match expr { hir::TopLevelExpression::Class(ref node) => { - self.check_class(node); + self.check_type_parameters(&node.type_parameters); } hir::TopLevelExpression::Trait(ref node) => { - self.check_trait(node); + self.check_type_parameters(&node.type_parameters); } _ => {} } } } - fn check_class(&mut self, node: &hir::DefineClass) { - let id = node.class_id.unwrap(); - let self_type = TypeId::Class(id); - - self.check_type_parameters(&node.type_parameters, self_type); - } - - fn check_trait(&mut self, node: &hir::DefineTrait) { - let id = node.trait_id.unwrap(); - let self_type = TypeId::Trait(id); - - self.check_type_parameters(&node.type_parameters, self_type); - } - - fn check_type_parameters( - &mut self, - nodes: &Vec, - self_type: TypeId, - ) { - let mut checker = CheckTypeSignature::new( - self.state, - self.module, - self_type, - Rules { allow_self_type: false, ..Default::default() }, - ); + fn check_type_parameters(&mut self, nodes: &Vec) { + let mut checker = CheckTypeSignature::new(self.state, self.module); for node in nodes { for req in &node.requirements { @@ -923,8 +859,15 @@ impl<'a> InsertPrelude<'a> { self.add_class(ClassId::boolean()); self.add_class(ClassId::nil()); self.add_class(ClassId::byte_array()); + self.add_class(ClassId::channel()); + + self.import_class(OPTION_MODULE, OPTION_CLASS); + + // $Result is used by try/throw to prevent name conflicts, while Result + // is meant to be used by developers. + self.import_class_as(RESULT_MODULE, RESULT_CLASS, "$Result"); + self.import_class(RESULT_MODULE, RESULT_CLASS); - self.import_class("std::option", "Option"); self.import_class("std::map", "Map"); self.import_method("std::process", "panic"); } @@ -945,6 +888,20 @@ impl<'a> InsertPrelude<'a> { self.add_class(id); } + fn import_class_as(&mut self, module: &str, class: &str, alias: &str) { + let id = self.state.db.class_in_module(module, class); + + if self.module.symbol_exists(self.db(), alias) { + return; + } + + self.module.new_symbol( + self.db_mut(), + alias.to_string(), + Symbol::Class(id), + ); + } + fn import_method(&mut self, module: &str, method: &str) { let mod_id = self.state.db.module(module); let method_id = if let Some(id) = mod_id.method(self.db(), method) { @@ -1140,7 +1097,7 @@ mod tests { use ast::parser::Parser; use std::fmt::Write as _; use types::module_name::ModuleName; - use types::{ClassId, ConstantId, TraitId, TraitInstance}; + use types::{ClassId, ConstantId, TraitId, TraitInstance, TypeBounds}; fn get_trait(db: &Database, module: ModuleId, name: &str) -> TraitId { if let Some(Symbol::Trait(id)) = module.symbol(db, name) { @@ -1204,7 +1161,7 @@ mod tests { assert!(DefineTypes::run_all(&mut state, &mut modules)); - let id = ClassId(17); + let id = ClassId(18); assert_eq!(state.diagnostics.iter().count(), 0); assert_eq!(class_expr(&modules[0]).class_id, Some(id)); @@ -1224,7 +1181,7 @@ mod tests { assert!(DefineTypes::run_all(&mut state, &mut modules)); - let id = ClassId(17); + let id = ClassId(18); assert_eq!(state.diagnostics.iter().count(), 0); assert_eq!(class_expr(&modules[0]).class_id, Some(id)); @@ -1739,7 +1696,7 @@ mod tests { let diag = state.diagnostics.iter().next().unwrap(); - assert_eq!(diag.id(), DiagnosticId::InvalidClass); + assert_eq!(diag.id(), DiagnosticId::InvalidType); } #[test] @@ -1753,7 +1710,7 @@ mod tests { let error = state.diagnostics.iter().next().unwrap(); - assert_eq!(error.id(), DiagnosticId::InvalidType); + assert_eq!(error.id(), DiagnosticId::InvalidSymbol); assert_eq!(error.location(), &cols(27, 30)); } diff --git a/compiler/src/type_check/expressions.rs b/compiler/src/type_check/expressions.rs index d32656e54..4cadffb45 100644 --- a/compiler/src/type_check/expressions.rs +++ b/compiler/src/type_check/expressions.rs @@ -7,25 +7,19 @@ use ast::source_location::SourceLocation; use std::cell::Cell; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; +use types::check::{Environment, TypeChecker}; +use types::format::{format_type, format_type_with_arguments}; +use types::resolve::TypeResolver; use types::{ - format_type, format_type_with_context, format_type_with_self, Block, - BuiltinCallInfo, BuiltinFunctionKind, CallInfo, CallKind, ClassId, - ClassInstance, Closure, ClosureCallInfo, ClosureId, CompilerMacro, - ConstantKind, ConstantPatternKind, Database, FieldId, FieldInfo, - IdentifierKind, MethodId, MethodLookup, MethodSource, ModuleId, Receiver, - Symbol, TraitId, TraitInstance, TypeArguments, TypeBounds, TypeContext, - TypeId, TypeRef, Variable, VariableId, CALL_METHOD, STRING_MODULE, - TO_STRING_TRAIT, + Block, BuiltinCallInfo, CallInfo, CallKind, ClassId, ClassInstance, + Closure, ClosureCallInfo, ClosureId, ConstantKind, ConstantPatternKind, + Database, FieldId, FieldInfo, IdentifierKind, MethodId, MethodKind, + MethodLookup, MethodSource, ModuleId, Receiver, Symbol, ThrowKind, TraitId, + TraitInstance, TypeArguments, TypeBounds, TypeId, TypeRef, Variable, + VariableId, CALL_METHOD, }; const IGNORE_VARIABLE: &str = "_"; - -const INDEX_METHOD: &str = "index"; -const INDEX_MUT_METHOD: &str = "index_mut"; -const INDEX_MODULE: &str = "std::index"; -const INDEX_TRAIT: &str = "Index"; -const INDEX_MUT_TRAIT: &str = "IndexMut"; - const STRING_LITERAL_LIMIT: usize = u32::MAX as usize; const CONST_ARRAY_LIMIT: usize = u16::MAX as usize; @@ -50,7 +44,7 @@ impl<'a> Pattern<'a> { } /// A collection of variables defined in a lexical scope. -pub struct VariableScope { +struct VariableScope { /// The variables defined in this scope. variables: HashMap, } @@ -98,9 +92,6 @@ enum ScopeKind { struct LexicalScope<'a> { kind: ScopeKind, - /// The throw type of the surrounding block. - throw_type: TypeRef, - /// The return type of the surrounding block. return_type: TypeRef, @@ -134,17 +125,12 @@ struct LexicalScope<'a> { } impl<'a> LexicalScope<'a> { - fn method( - self_type: TypeRef, - return_type: TypeRef, - throw_type: TypeRef, - ) -> Self { + fn method(self_type: TypeRef, return_type: TypeRef) -> Self { Self { kind: ScopeKind::Method, variables: VariableScope::new(), surrounding_type: self_type, return_type, - throw_type, parent: None, in_closure: false, break_in_loop: Cell::new(false), @@ -155,7 +141,6 @@ impl<'a> LexicalScope<'a> { Self { kind, surrounding_type: self.surrounding_type, - throw_type: self.throw_type, return_type: self.return_type, variables: VariableScope::new(), parent: Some(self), @@ -233,8 +218,14 @@ struct MethodCall { /// The method that's called. method: MethodId, - /// A context for type-checking and substituting types. - context: TypeContext, + /// The base type arguments to use for type-checking. + type_arguments: TypeArguments, + + /// A union of the type bounds of the surrounding and the called method. + /// + /// These bounds are to be used when inferring types, such as the return + /// type. + bounds: TypeBounds, /// The type of the method's receiver. receiver: TypeRef, @@ -247,32 +238,51 @@ struct MethodCall { /// If input/output types should be limited to sendable types. require_sendable: bool, + + /// Arguments of which we need to check if they are sendable. + check_sendable: Vec<(TypeRef, SourceLocation)>, + + /// The resolved return type of the call. + return_type: TypeRef, } impl MethodCall { fn new( - state: &State, + state: &mut State, module: ModuleId, + surrounding_scope: Option<(TypeId, MethodId)>, receiver: TypeRef, receiver_id: TypeId, method: MethodId, ) -> Self { - let mut args = TypeArguments::new(); - - // The method call needs access to the type arguments of the receiver. - // So given a `pop -> T` method for `Array[Int]`, we want to be able to - // map `T` to `Int`. Since a TypeContext only has a single - // `TypeArguments` structure (to simplify lookups), we copy the - // arguments of the receiver into this temporary collection of - // arguments. - match receiver_id { - TypeId::ClassInstance(ins) => { - ins.copy_type_arguments_into(&state.db, &mut args); - } - TypeId::TraitInstance(ins) => { - ins.copy_type_arguments_into(&state.db, &mut args); + // When checking arguments we need access to the type arguments of the + // receiver, along with any type arguments introduced by the method + // itself. + let mut type_arguments = receiver.type_arguments(&state.db); + + // Type parameters may be reused between arguments and throw/return + // types, so we need to ensure all references resolve into the same + // types, hence we create type placeholders here. + for param in method.type_parameters(&state.db).into_iter() { + type_arguments.assign( + param, + TypeRef::placeholder(&mut state.db, Some(param)), + ); + } + + // Static methods may use/return type parameters of the surrounding + // type, so we also need to create placeholders for those. + if method.kind(&state.db) == MethodKind::Static { + if let TypeId::Class(class) = receiver_id { + if class.is_generic(&state.db) { + for param in class.type_parameters(&state.db) { + type_arguments.assign( + param, + TypeRef::placeholder(&mut state.db, Some(param)), + ); + } + } } - _ => {} } // When a method is implemented through a trait, it may depend on type @@ -301,59 +311,56 @@ impl MethodCall { // We only need to do this when the receiver is a class (and thus the // method has a source). If the receiver is a trait, we'll end up using // its inherited type arguments when inferring a type parameter. - match method.source(&state.db) { - MethodSource::BoundedImplementation(ins) - | MethodSource::Implementation(ins) => { - ins.copy_type_arguments_into(&state.db, &mut args); - } - _ => {} + if let MethodSource::Implementation(ins, _) = method.source(&state.db) { + ins.copy_type_arguments_into(&state.db, &mut type_arguments); } let require_sendable = receiver.require_sendable_arguments(&state.db) && !method.is_moving(&state.db); + let bounds = if let Some((self_id, self_method)) = surrounding_scope { + // When calling a method on `self`, we need to take any surrounding + // bounds into account when resolving types. + if self_id == receiver_id { + self_method.bounds(&state.db).union(method.bounds(&state.db)) + } else { + self_method.bounds(&state.db).clone() + } + } else { + TypeBounds::new() + }; + Self { module, method, + bounds, receiver, - context: TypeContext::with_arguments(receiver_id, args), + type_arguments, arguments: 0, named_arguments: HashSet::new(), require_sendable, + check_sendable: Vec::new(), + return_type: TypeRef::Unknown, } } - fn check_bounded_implementation( + fn check_type_bounds( &mut self, state: &mut State, location: &SourceLocation, ) { - if let MethodSource::BoundedImplementation(trait_ins) = - self.method.source(&state.db) - { - if self.receiver_id().implements_trait_instance( - &mut state.db, - trait_ins, - &mut self.context, - ) { - return; - } - - let method_name = self.method.name(&state.db).clone(); - let rec_name = format_type_with_context( - &state.db, - &self.context, - self.receiver_id(), - ); - let trait_name = - format_type_with_context(&state.db, &self.context, trait_ins); + let bounds = self.method.bounds(&state.db); + let args = self.type_arguments.clone(); + let mut scope = Environment::new(args.clone(), args); + let mut checker = TypeChecker::new(&state.db); + if !checker.check_bounds(bounds, &mut scope) { state.diagnostics.error( DiagnosticId::InvalidSymbol, format!( - "The method '{}' exists for type '{}', \ - but requires this type to implement trait '{}'", - method_name, rec_name, trait_name + "The method '{}' exists but isn't available because \ + one or more type parameter bounds aren't met", + self.method.name(&state.db), ), self.module.file(&state.db), location.clone(), @@ -361,23 +368,21 @@ impl MethodCall { } } - fn check_argument_count( + fn check_arguments( &mut self, state: &mut State, location: &SourceLocation, ) { let expected = self.method.number_of_arguments(&state.db); - if self.arguments == expected { - return; + if self.arguments != expected { + state.diagnostics.incorrect_call_arguments( + self.arguments, + expected, + self.module.file(&state.db), + location.clone(), + ); } - - state.diagnostics.incorrect_call_arguments( - self.arguments, - expected, - self.module.file(&state.db), - location.clone(), - ); } fn check_mutability( @@ -395,7 +400,11 @@ impl MethodCall { "The method '{}' takes ownership of its receiver, \ but '{}' isn't an owned value", name, - format_type_with_context(&state.db, &self.context, rec) + format_type_with_arguments( + &state.db, + &self.type_arguments, + rec + ) ), self.module.file(&state.db), location.clone(), @@ -411,7 +420,11 @@ impl MethodCall { "The method '{}' requires a mutable receiver, \ but '{}' isn't mutable", name, - format_type_with_context(&state.db, &self.context, rec) + format_type_with_arguments( + &state.db, + &self.type_arguments, + rec + ) ), self.module.file(&state.db), location.clone(), @@ -428,143 +441,90 @@ impl MethodCall { ) { let given = argument.cast_according_to(expected, &state.db); - if self.require_sendable && !given.is_sendable(&state.db) { - state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "The receiver ('{}') of this call requires sendable \ - arguments, but '{}' isn't sendable", - format_type_with_context( - &state.db, - &self.context, - self.receiver - ), - format_type_with_context(&state.db, &self.context, given), - ), + if self.require_sendable { + self.check_sendable.push((given, location.clone())); + } + + let mut scope = Environment::new( + given.type_arguments(&state.db), + self.type_arguments.clone(), + ); + + if !TypeChecker::new(&state.db).run(given, expected, &mut scope) { + state.diagnostics.type_error( + format_type_with_arguments(&state.db, &scope.left, given), + format_type_with_arguments(&state.db, &scope.right, expected), self.module.file(&state.db), location.clone(), ); } + } - if given.type_check(&mut state.db, expected, &mut self.context, true) { + fn check_sendable(&mut self, state: &mut State, location: &SourceLocation) { + if !self.require_sendable { return; } - state.diagnostics.type_error( - format_type_with_context(&state.db, &self.context, given), - format_type_with_context(&state.db, &self.context, expected), - self.module.file(&state.db), - location.clone(), - ); - } + // It's safe to pass `ref T` as an argument if all arguments and `self` + // are immutable, as this prevents storing of the `ref T` in `self`, + // thus violating the uniqueness constraints. + let ref_safe = self.method.is_immutable(&state.db) + && self.check_sendable.iter().all(|(typ, _)| { + typ.is_sendable(&state.db) || typ.is_ref(&state.db) + }); - fn update_receiver_type_arguments( - &mut self, - state: &mut State, - surrounding_type: TypeRef, - ) { - // We don't update the type arguments of `self`, as that messes up - // future method calls acting on the same type. - match surrounding_type { - TypeRef::Owned(id) | TypeRef::Ref(id) - if id == self.receiver_id() => + for (given, loc) in &self.check_sendable { + if given.is_sendable(&state.db) + || (given.is_ref(&state.db) && ref_safe) { - return; + continue; } - TypeRef::OwnedSelf | TypeRef::RefSelf => return, - _ => {} - } - - let db = &mut state.db; - let args = &self.context.type_arguments; - // As part of the method call we use a temporary collection of type - // arguments. Once the call is done, newly assigned type parameters that - // belong to the receiver need to be copied into the receiver's type - // arguments. This ensures that future method calls observe the newly - // assigned type parameters. - match self.receiver_id() { - TypeId::ClassInstance(ins) => ins.copy_new_arguments_from(db, args), - TypeId::TraitInstance(ins) => ins.copy_new_arguments_from(db, args), - _ => {} - } - } - - fn throw_type( - &mut self, - state: &mut State, - location: &SourceLocation, - ) -> TypeRef { - let typ = self.method.throw_type(&state.db).inferred( - &mut state.db, - &mut self.context, - false, - ); - - if !self.output_type_is_sendable(state, typ) { - let name = self.method.name(&state.db); + let targs = &self.type_arguments; - state.diagnostics.unsendable_throw_type( - name, - format_type_with_context(&state.db, &self.context, typ), + state.diagnostics.unsendable_argument( + format_type_with_arguments(&state.db, targs, *given), self.module.file(&state.db), - location.clone(), + loc.clone(), ); } - typ - } - - fn return_type( - &mut self, - state: &mut State, - location: &SourceLocation, - ) -> TypeRef { - let typ = self.method.return_type(&state.db).inferred( - &mut state.db, - &mut self.context, - false, - ); - - if !self.output_type_is_sendable(state, typ) { - let name = self.method.name(&state.db); + // If `self` and all arguments are immutable, we allow owned return + // values provided they don't contain any references. This is safe + // because `self` can't have references to it (because it's immutable), + // we can't "leak" a reference through the arguments (because they too + // are immutable), and the returned value can't refer to `self` because + // we don't allow references anywhere in the type or its sub types. + let ret_sendable = if ref_safe { + self.return_type.is_sendable_output(&state.db) + } else { + self.return_type.is_sendable(&state.db) + }; + if !ret_sendable { state.diagnostics.unsendable_return_type( - name, - format_type_with_context(&state.db, &self.context, typ), + format_type_with_arguments( + &state.db, + &self.type_arguments, + self.return_type, + ), self.module.file(&state.db), location.clone(), ); } - - typ - } - - fn receiver_id(&self) -> TypeId { - self.context.self_type } - fn output_type_is_sendable(&self, state: &State, typ: TypeRef) -> bool { - if !self.require_sendable { - return true; - } + fn resolve_return_type(&mut self, state: &mut State) -> TypeRef { + let raw = self.method.return_type(&state.db); + let typ = TypeResolver::new( + &mut state.db, + &self.type_arguments, + &self.bounds, + ) + .resolve(raw); - // If a method is immutable and doesn't define any arguments, any - // returned or thrown value that is sendable must have been created as - // part of the call, and thus can't have any outside references pointing - // to it. This allows such methods to return/throw owned values, while - // still allowing the use of such methods on unique receivers. - // - // Note that this check still enforces sendable sub values, meaning it's - // invalid to return e.g. `Array[ref Thing]`, as `ref Thing` isn't - // sendable. - if self.method.number_of_arguments(&state.db) == 0 - && self.method.is_immutable(&state.db) - { - typ.is_sendable_output(&state.db) - } else { - typ.is_sendable(&state.db) - } + self.return_type = typ; + typ } } @@ -577,19 +537,36 @@ pub(crate) struct DefineConstants<'a> { impl<'a> DefineConstants<'a> { pub(crate) fn run_all( state: &'a mut State, - modules: &mut Vec, + modules: &mut [hir::Module], ) -> bool { - for module in modules { - DefineConstants { state, module: module.module_id }.run(module); + // Regular constants must be defined first such that complex constants + // (e.g. `A + B` or `[A, B]`) can refer to them, regardless of the order + // in which modules are processed. + for module in modules.iter_mut() { + DefineConstants { state, module: module.module_id } + .run(module, true); + } + + for module in modules.iter_mut() { + DefineConstants { state, module: module.module_id } + .run(module, false); } !state.diagnostics.has_errors() } - fn run(mut self, module: &mut hir::Module) { + fn run(mut self, module: &mut hir::Module, simple_only: bool) { for expression in module.expressions.iter_mut() { - if let hir::TopLevelExpression::Constant(ref mut n) = expression { - self.define_constant(n); + let node = if let hir::TopLevelExpression::Constant(ref mut node) = + expression + { + node + } else { + continue; + }; + + if node.value.is_simple_literal() == simple_only { + self.define_constant(node); } } } @@ -649,13 +626,12 @@ impl<'a> Expressions<'a> { } fn define_class(&mut self, node: &mut hir::DefineClass) { - let bounds = TypeBounds::new(); let id = node.class_id.unwrap(); let num_methods = id.number_of_methods(self.db()); if num_methods > METHODS_IN_CLASS_LIMIT { self.state.diagnostics.error( - DiagnosticId::InvalidClass, + DiagnosticId::InvalidType, format!( "The number of methods defined in this class ({}) \ exceeds the maximum of {} methods", @@ -674,7 +650,7 @@ impl<'a> Expressions<'a> { self.define_async_method(n); } hir::ClassExpression::InstanceMethod(ref mut n) => { - self.define_instance_method(n, &bounds); + self.define_instance_method(n); } hir::ClassExpression::StaticMethod(ref mut n) => { self.define_static_method(n); @@ -685,12 +661,10 @@ impl<'a> Expressions<'a> { } fn reopen_class(&mut self, node: &mut hir::ReopenClass) { - let bounds = TypeBounds::new(); - for node in &mut node.body { match node { hir::ReopenClassExpression::InstanceMethod(ref mut n) => { - self.define_instance_method(n, &bounds) + self.define_instance_method(n) } hir::ReopenClassExpression::StaticMethod(ref mut n) => { self.define_static_method(n) @@ -703,8 +677,6 @@ impl<'a> Expressions<'a> { } fn define_trait(&mut self, node: &mut hir::DefineTrait) { - let bounds = TypeBounds::new(); - self.verify_type_parameter_requirements(&node.type_parameters); self.verify_required_traits( &node.requirements, @@ -713,34 +685,25 @@ impl<'a> Expressions<'a> { for node in &mut node.body { if let hir::TraitExpression::InstanceMethod(ref mut n) = node { - self.define_instance_method(n, &bounds); + self.define_instance_method(n); } } } fn implement_trait(&mut self, node: &mut hir::ImplementTrait) { - let class_id = node.class_instance.unwrap().instance_of(); - let trait_id = node.trait_instance.unwrap().instance_of(); - let bounds = class_id - .trait_implementation(self.db(), trait_id) - .map(|i| i.bounds.clone()) - .unwrap(); - for n in &mut node.body { - self.define_instance_method(n, &bounds); + self.define_instance_method(n); } } fn define_module_method(&mut self, node: &mut hir::DefineModuleMethod) { let method = node.method_id.unwrap(); - let stype = method.self_type(self.db()); + let stype = method.receiver_id(self.db()); let receiver = method.receiver(self.db()); let bounds = TypeBounds::new(); let returns = method.return_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let throws = - method.throw_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let mut scope = LexicalScope::method(receiver, returns, throws); + let mut scope = LexicalScope::method(receiver, returns); self.verify_type_parameter_requirements(&node.type_parameters); @@ -762,23 +725,16 @@ impl<'a> Expressions<'a> { &mut scope, &node.location, ); - - checker.check_if_throws(throws, &node.location); } - fn define_instance_method( - &mut self, - node: &mut hir::DefineInstanceMethod, - bounds: &TypeBounds, - ) { + fn define_instance_method(&mut self, node: &mut hir::DefineInstanceMethod) { let method = node.method_id.unwrap(); - let stype = method.self_type(self.db()); + let bounds = method.bounds(self.db()).clone(); + let stype = method.receiver_id(self.db()); let receiver = method.receiver(self.db()); let returns = - method.return_type(self.db()).as_rigid_type(self.db_mut(), bounds); - let throws = - method.throw_type(self.db()).as_rigid_type(self.db_mut(), bounds); - let mut scope = LexicalScope::method(receiver, returns, throws); + method.return_type(self.db()).as_rigid_type(self.db_mut(), &bounds); + let mut scope = LexicalScope::method(receiver, returns); self.verify_type_parameter_requirements(&node.type_parameters); @@ -786,14 +742,14 @@ impl<'a> Expressions<'a> { scope.variables.add_variable(arg.name, arg.variable); } - self.define_field_types(receiver, method, bounds); + self.define_field_types(receiver, method, &bounds); let mut checker = CheckMethodBody::new( self.state, self.module, method, stype, - bounds, + &bounds, ); checker.expressions_with_return( @@ -802,20 +758,15 @@ impl<'a> Expressions<'a> { &mut scope, &node.location, ); - - checker.check_if_throws(throws, &node.location); } fn define_async_method(&mut self, node: &mut hir::DefineAsyncMethod) { let method = node.method_id.unwrap(); - let stype = method.self_type(self.db()); + let stype = method.receiver_id(self.db()); let receiver = method.receiver(self.db()); let bounds = TypeBounds::new(); - let returns = - method.return_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let throws = - method.throw_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let mut scope = LexicalScope::method(receiver, returns, throws); + let returns = TypeRef::nil(); + let mut scope = LexicalScope::method(receiver, returns); self.verify_type_parameter_requirements(&node.type_parameters); @@ -839,20 +790,16 @@ impl<'a> Expressions<'a> { &mut scope, &node.location, ); - - checker.check_if_throws(throws, &node.location); } fn define_static_method(&mut self, node: &mut hir::DefineStaticMethod) { let method = node.method_id.unwrap(); - let stype = method.self_type(self.db()); + let stype = method.receiver_id(self.db()); let receiver = method.receiver(self.db()); let bounds = TypeBounds::new(); let returns = method.return_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let throws = - method.throw_type(self.db()).as_rigid_type(self.db_mut(), &bounds); - let mut scope = LexicalScope::method(receiver, returns, throws); + let mut scope = LexicalScope::method(receiver, returns); self.verify_type_parameter_requirements(&node.type_parameters); @@ -874,8 +821,6 @@ impl<'a> Expressions<'a> { &mut scope, &node.location, ); - - checker.check_if_throws(throws, &node.location); } fn define_field_types( @@ -886,9 +831,11 @@ impl<'a> Expressions<'a> { ) { for field in receiver.fields(self.db()) { let name = field.name(self.db()).clone(); - let typ = field - .value_type(self.db()) - .as_rigid_type(self.db_mut(), bounds); + let raw_type = field.value_type(self.db()); + let args = TypeArguments::new(); + let typ = TypeResolver::new(self.db_mut(), &args, bounds) + .with_rigid(true) + .resolve(raw_type); method.set_field_type(self.db_mut(), name, field, typ); } @@ -969,14 +916,11 @@ impl<'a> Expressions<'a> { struct CheckConstant<'a> { state: &'a mut State, module: ModuleId, - module_type: TypeRef, } impl<'a> CheckConstant<'a> { fn new(state: &'a mut State, module: ModuleId) -> Self { - let module_type = TypeRef::Owned(TypeId::Module(module)); - - Self { state, module, module_type } + Self { state, module } } fn expression(&mut self, node: &mut hir::ConstExpression) -> TypeRef { @@ -1028,17 +972,23 @@ impl<'a> CheckConstant<'a> { return TypeRef::Error; }; - let mod_type = self.module_type; - let mut call = - MethodCall::new(self.state, self.module, left, left_id, method); + let mut call = MethodCall::new( + self.state, + self.module, + None, + left, + left_id, + method, + ); call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); + call.check_type_bounds(self.state, &node.location); self.positional_argument(&mut call, &mut node.right); - call.check_argument_count(self.state, &node.location); - call.update_receiver_type_arguments(self.state, mod_type); + call.check_arguments(self.state, &node.location); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); - node.resolved_type = call.return_type(self.state, &node.location); + node.resolved_type = call.return_type; node.resolved_type } @@ -1078,13 +1028,18 @@ impl<'a> CheckConstant<'a> { TypeRef::Error } _ => { - self.state.diagnostics.undefined_symbol( - name, - self.file(), - node.location.clone(), - ); + if let Some(cons) = self.db().builtin_constant(name) { + node.kind = ConstantKind::Builtin(cons); + cons.return_type() + } else { + self.state.diagnostics.undefined_symbol( + name, + self.file(), + node.location.clone(), + ); - TypeRef::Error + TypeRef::Error + } } } } @@ -1097,15 +1052,13 @@ impl<'a> CheckConstant<'a> { .collect::>(); if types.len() > 1 { - let stype = TypeId::Module(self.module); let &first = types.first().unwrap(); - let mut ctx = TypeContext::new(stype); for (&typ, node) in types[1..].iter().zip(node.values[1..].iter()) { - if !typ.type_check(self.db_mut(), first, &mut ctx, true) { + if !TypeChecker::check(self.db(), typ, first) { self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, typ), - format_type_with_context(self.db(), &ctx, first), + format_type(self.db(), typ), + format_type(self.db(), first), self.file(), node.location().clone(), ); @@ -1128,11 +1081,7 @@ impl<'a> CheckConstant<'a> { // Mutating constant arrays isn't safe, so they're typed as `ref // Array[T]` instead of `Array[T]`. let ary = TypeRef::Ref(TypeId::ClassInstance( - ClassInstance::generic_with_types( - self.db_mut(), - ClassId::array(), - types, - ), + ClassInstance::with_types(self.db_mut(), ClassId::array(), types), )); node.resolved_type = ary; @@ -1145,14 +1094,13 @@ impl<'a> CheckConstant<'a> { name: &str, location: &SourceLocation, ) -> Option<(TypeId, MethodId)> { - let stype = TypeId::Module(self.module); - let rec_id = match receiver.type_id(self.db(), stype) { + let rec_id = match receiver.type_id(self.db()) { Ok(id) => id, Err(TypeRef::Error) => return None, Err(typ) => { self.state.diagnostics.undefined_method( name, - format_type_with_self(self.db(), stype, typ), + format_type(self.db(), typ), self.file(), location.clone(), ); @@ -1173,7 +1121,7 @@ impl<'a> CheckConstant<'a> { MethodLookup::InstanceOnStatic => { self.state.diagnostics.invalid_instance_call( name, - format_type_with_self(self.db(), stype, receiver), + format_type(self.db(), receiver), self.file(), location.clone(), ); @@ -1181,7 +1129,7 @@ impl<'a> CheckConstant<'a> { MethodLookup::StaticOnInstance => { self.state.diagnostics.invalid_static_call( name, - format_type_with_self(self.db(), stype, receiver), + format_type(self.db(), receiver), self.file(), location.clone(), ); @@ -1189,7 +1137,7 @@ impl<'a> CheckConstant<'a> { MethodLookup::None => { self.state.diagnostics.undefined_method( name, - format_type_with_self(self.db(), stype, receiver), + format_type(self.db(), receiver), self.file(), location.clone(), ); @@ -1238,14 +1186,11 @@ struct CheckMethodBody<'a> { /// The surrounding method. method: MethodId, - /// The type for `Self`, excluding ownership. + /// The type ID of the receiver of the surrounding method. self_type: TypeId, /// Any bounds to apply to type parameters. bounds: &'a TypeBounds, - - /// If a value is thrown from this body. - thrown: bool, } impl<'a> CheckMethodBody<'a> { @@ -1256,23 +1201,7 @@ impl<'a> CheckMethodBody<'a> { self_type: TypeId, bounds: &'a TypeBounds, ) -> Self { - Self { state, module, method, self_type, bounds, thrown: false } - } - - fn check_if_throws( - &mut self, - expected: TypeRef, - location: &SourceLocation, - ) { - if !expected.is_present(self.db()) || self.thrown { - return; - } - - self.state.diagnostics.missing_throw( - self.fmt(expected), - self.file(), - location.clone(), - ); + Self { state, module, method, self_type, bounds } } fn expressions( @@ -1310,21 +1239,20 @@ impl<'a> CheckMethodBody<'a> { fallback_location: &SourceLocation, ) { let typ = self.last_expression_type(nodes, scope); - let mut ctx = TypeContext::new(self.self_type); - if returns.is_nil(self.db(), self.self_type) { + if returns.is_nil(self.db()) { // When the return type is `Nil` (explicit or not), we just ignore // whatever value is returned. return; } - if !typ.type_check(self.db_mut(), returns, &mut ctx, true) { + if !TypeChecker::check(self.db(), typ, returns) { let loc = nodes.last().map(|n| n.location()).unwrap_or(fallback_location); self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, typ), - format_type_with_context(self.db(), &ctx, returns), + format_type(self.db(), typ), + format_type(self.db(), returns), self.file(), loc.clone(), ); @@ -1354,7 +1282,6 @@ impl<'a> CheckMethodBody<'a> { hir::Expression::ReplaceVariable(ref mut n) => { self.replace_variable(n, scope) } - hir::Expression::AsyncCall(ref mut n) => self.async_call(n, scope), hir::Expression::Break(ref n) => self.break_expression(n, scope), hir::Expression::BuiltinCall(ref mut n) => { self.builtin_call(n, scope) @@ -1377,7 +1304,6 @@ impl<'a> CheckMethodBody<'a> { self.class_literal(n, scope) } hir::Expression::Int(ref mut n) => self.int_literal(n, scope), - hir::Expression::Invalid(_) => TypeRef::Error, hir::Expression::Loop(ref mut n) => self.loop_expression(n, scope), hir::Expression::Match(ref mut n) => { self.match_expression(n, scope) @@ -1404,9 +1330,7 @@ impl<'a> CheckMethodBody<'a> { hir::Expression::Nil(ref mut n) => self.nil_literal(n), hir::Expression::Tuple(ref mut n) => self.tuple_literal(n, scope), hir::Expression::TypeCast(ref mut n) => self.type_cast(n, scope), - hir::Expression::Index(ref mut n) => { - self.index_expression(n, scope) - } + hir::Expression::Try(ref mut n) => self.try_expression(n, scope), } } @@ -1417,11 +1341,17 @@ impl<'a> CheckMethodBody<'a> { ) -> TypeRef { let typ = self.expression(node, scope); + if typ.is_uni(self.db()) { + // This ensures that value types such as `uni T` aren't implicitly + // converted to `T`. + return typ; + } + if typ.is_value_type(self.db()) { - typ.as_owned(self.db()) - } else { - typ + return typ.as_owned(self.db()); } + + typ } fn argument_expression( @@ -1429,13 +1359,13 @@ impl<'a> CheckMethodBody<'a> { expected_type: TypeRef, node: &mut hir::Expression, scope: &mut LexicalScope, - context: &mut TypeContext, + type_arguments: &TypeArguments, ) -> TypeRef { match node { hir::Expression::Closure(ref mut n) => { let expected = expected_type - .closure_id(self.db(), self.self_type) - .map(|f| (f, expected_type, context)); + .closure_id(self.db()) + .map(|f| (f, expected_type, type_arguments)); self.closure(n, expected, scope) } @@ -1485,17 +1415,14 @@ impl<'a> CheckMethodBody<'a> { match value { hir::StringValue::Expression(v) => { let val = self.call(v, scope); - let stype = self.self_type; - if val != TypeRef::Error - && !val.is_string(self.db(), self.self_type) - { + if val != TypeRef::Error && !val.is_string(self.db()) { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( "Expected a 'String', 'ref String' or \ 'mut String', found '{}' instead", - format_type_with_self(self.db(), stype, val) + format_type(self.db(), val) ), self.file(), v.location.clone(), @@ -1527,13 +1454,12 @@ impl<'a> CheckMethodBody<'a> { if types.len() > 1 { let &first = types.first().unwrap(); - let mut ctx = TypeContext::new(self.self_type); for (&typ, node) in types[1..].iter().zip(node.values[1..].iter()) { - if !typ.type_check(self.db_mut(), first, &mut ctx, true) { + if !TypeChecker::check(self.db(), typ, first) { self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, typ), - format_type_with_context(self.db(), &ctx, first), + format_type(self.db(), typ), + format_type(self.db(), first), self.file(), node.location().clone(), ); @@ -1552,11 +1478,8 @@ impl<'a> CheckMethodBody<'a> { ); } - let ins = ClassInstance::generic_with_types( - self.db_mut(), - ClassId::array(), - types, - ); + let ins = + ClassInstance::with_types(self.db_mut(), ClassId::array(), types); let ary = TypeRef::Owned(TypeId::ClassInstance(ins)); node.value_type = @@ -1582,11 +1505,7 @@ impl<'a> CheckMethodBody<'a> { }; let tuple = TypeRef::Owned(TypeId::ClassInstance( - ClassInstance::generic_with_types( - self.db_mut(), - class, - types.clone(), - ), + ClassInstance::with_types(self.db_mut(), class, types.clone()), )); node.class_id = Some(class); @@ -1633,14 +1552,7 @@ impl<'a> CheckMethodBody<'a> { }; let require_send = class.kind(self.db()).is_async(); - let ins = if class.is_generic(self.db()) { - ClassInstance::generic_with_placeholders(self.db_mut(), class) - } else { - ClassInstance::new(class) - }; - - let mut ctx = - TypeContext::for_class_instance(self.db(), self.self_type, ins); + let ins = ClassInstance::empty(self.db_mut(), class); let mut assigned = HashSet::new(); for field in &mut node.fields { @@ -1662,7 +1574,7 @@ impl<'a> CheckMethodBody<'a> { && class.module(self.db()) != self.module { self.state.diagnostics.error( - DiagnosticId::PrivateSymbol, + DiagnosticId::InvalidSymbol, format!("The field '{}' is private", name), self.file(), node.location.clone(), @@ -1672,12 +1584,16 @@ impl<'a> CheckMethodBody<'a> { let expected = field_id.value_type(self.db()); let value = self.expression(&mut field.value, scope); let value_casted = value.cast_according_to(expected, self.db()); + let checker = TypeChecker::new(self.db()); + let mut env = Environment::new( + value_casted.type_arguments(self.db()), + ins.type_arguments(self.db()).clone(), + ); - if !value_casted.type_check(self.db_mut(), expected, &mut ctx, true) - { + if !checker.run(value_casted, expected, &mut env) { self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, value), - format_type_with_context(self.db(), &ctx, expected), + format_type_with_arguments(self.db(), &env.left, value), + format_type_with_arguments(self.db(), &env.right, expected), self.file(), field.value.location().clone(), ); @@ -1686,7 +1602,7 @@ impl<'a> CheckMethodBody<'a> { if require_send && !value.is_sendable(self.db()) { self.state.diagnostics.unsendable_field_value( name, - format_type_with_context(self.db(), &ctx, value), + format_type(self.db(), value), self.file(), field.value.location().clone(), ); @@ -1720,8 +1636,6 @@ impl<'a> CheckMethodBody<'a> { ); } - ins.copy_new_arguments_from(self.db_mut(), &ctx.type_arguments); - node.class_id = Some(class); node.resolved_type = TypeRef::Owned(TypeId::ClassInstance(ins)); node.resolved_type @@ -1783,7 +1697,7 @@ impl<'a> CheckMethodBody<'a> { if !value_type.allow_assignment(self.db()) { self.state.diagnostics.cant_assign_type( - &format_type_with_self(self.db(), self.self_type, value_type), + &format_type(self.db(), value_type), self.file(), node.value.location().clone(), ); @@ -1791,23 +1705,15 @@ impl<'a> CheckMethodBody<'a> { let var_type = if let Some(tnode) = node.value_type.as_mut() { let exp_type = self.type_signature(tnode, self.self_type); - let mut typ_ctx = TypeContext::new(self.self_type); let value_casted = value_type.cast_according_to(exp_type, self.db()); - if !value_casted.type_check( - self.db_mut(), - exp_type, - &mut typ_ctx, - true, - ) { - let stype = self.self_type; - + if !TypeChecker::check(self.db(), value_casted, exp_type) { self.state.diagnostics.type_error( - format_type_with_self(self.db(), stype, value_type), - format_type_with_self(self.db(), stype, exp_type), + format_type(self.db(), value_type), + format_type(self.db(), exp_type), self.file(), - node.location.clone(), + node.value.location().clone(), ); } @@ -1887,19 +1793,11 @@ impl<'a> CheckMethodBody<'a> { ) { let var_type = if let Some(tnode) = node.value_type.as_mut() { let exp_type = self.type_signature(tnode, self.self_type); - let mut typ_ctx = TypeContext::new(self.self_type); - - if !value_type.type_check( - self.db_mut(), - exp_type, - &mut typ_ctx, - true, - ) { - let stype = self.self_type; + if !TypeChecker::check(self.db(), value_type, exp_type) { self.state.diagnostics.pattern_type_error( - format_type_with_self(self.db(), stype, value_type), - format_type_with_self(self.db(), stype, exp_type), + format_type(self.db(), value_type), + format_type(self.db(), exp_type), self.file(), node.location.clone(), ); @@ -1925,17 +1823,16 @@ impl<'a> CheckMethodBody<'a> { } if let Some(existing) = pattern.variable_scope.variable(&name) { - let mut ctx = TypeContext::new(self.self_type); let ex_type = existing.value_type(self.db()); - if !var_type.type_check(self.db_mut(), ex_type, &mut ctx, true) { + if !TypeChecker::check(self.db(), var_type, ex_type) { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( "The type of this variable is defined as '{}' \ in another pattern, but here its type is '{}'", - format_type_with_context(self.db(), &ctx, ex_type), - format_type_with_context(self.db(), &ctx, var_type), + format_type(self.db(), ex_type), + format_type(self.db(), var_type), ), self.file(), node.location.clone(), @@ -1977,20 +1874,14 @@ impl<'a> CheckMethodBody<'a> { ) { let name = &node.name; - if let Some(ins) = - value_type.as_enum_instance(self.db(), self.self_type) - { + if let Some(ins) = value_type.as_enum_instance(self.db()) { let variant = if let Some(v) = ins.instance_of().variant(self.db(), name) { v } else { self.state.diagnostics.undefined_variant( name, - format_type_with_self( - self.db(), - self.self_type, - value_type, - ), + format_type(self.db(), value_type), self.file(), node.location.clone(), ); @@ -2020,22 +1911,17 @@ impl<'a> CheckMethodBody<'a> { let exp_type = match symbol { Ok(Some(Symbol::Constant(id))) => { let typ = id.value_type(self.db()); - let cid = typ.class_id(self.db(), self.self_type).unwrap(); - node.kind = if cid == ClassId::int() { + node.kind = if typ.is_int(self.db()) { ConstantPatternKind::Int(id) - } else if cid == ClassId::string() { + } else if typ.is_string(self.db()) { ConstantPatternKind::String(id) } else { self.state.diagnostics.error( DiagnosticId::InvalidPattern, format!( "Expected a 'String' or 'Int', found '{}' instead", - format_type_with_self( - self.db(), - self.self_type, - typ - ), + format_type(self.db(), typ), ), self.file(), node.location.clone(), @@ -2070,14 +1956,10 @@ impl<'a> CheckMethodBody<'a> { } }; - let mut typ_ctx = TypeContext::new(self.self_type); - - if !value_type.type_check(self.db_mut(), exp_type, &mut typ_ctx, true) { - let self_type = self.self_type; - + if !TypeChecker::check(self.db(), value_type, exp_type) { self.state.diagnostics.pattern_type_error( - format_type_with_self(self.db(), self_type, value_type), - format_type_with_self(self.db(), self_type, exp_type), + format_type(self.db(), value_type), + format_type(self.db(), exp_type), self.file(), node.location.clone(), ); @@ -2110,11 +1992,7 @@ impl<'a> CheckMethodBody<'a> { format!( "This pattern expects a tuple, \ but the input type is '{}'", - format_type_with_self( - self.db(), - self.self_type, - value_type - ), + format_type(self.db(), value_type), ), self.file(), node.location.clone(), @@ -2174,7 +2052,10 @@ impl<'a> CheckMethodBody<'a> { | TypeRef::Uni(TypeId::ClassInstance(ins)) | TypeRef::Mut(TypeId::ClassInstance(ins)) | TypeRef::Ref(TypeId::ClassInstance(ins)) - if ins.instance_of().kind(self.db()).is_regular() => + if ins + .instance_of() + .kind(self.db()) + .allow_pattern_matching() => { ins } @@ -2182,13 +2063,9 @@ impl<'a> CheckMethodBody<'a> { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( - "This pattern expects a regular class instance, \ - but the input type is '{}'", - format_type_with_self( - self.db(), - self.self_type, - value_type - ), + "A regular or extern class instance is expected, \ + but the input type is an instance of type '{}'", + format_type(self.db(), value_type), ), self.file(), node.location.clone(), @@ -2207,11 +2084,7 @@ impl<'a> CheckMethodBody<'a> { format!( "The type '{}' can't be destructured as it defines \ a custom destructor", - format_type_with_self( - self.db(), - self.self_type, - value_type - ) + format_type(self.db(), value_type) ), self.file(), node.location.clone(), @@ -2228,8 +2101,7 @@ impl<'a> CheckMethodBody<'a> { } let immutable = value_type.is_ref(self.db()); - let mut ctx = - TypeContext::for_class_instance(self.db(), self.self_type, ins); + let args = TypeArguments::for_class(self.db(), ins); for node in &mut node.values { let name = &node.field.name; @@ -2240,11 +2112,7 @@ impl<'a> CheckMethodBody<'a> { DiagnosticId::InvalidSymbol, format!( "The type '{}' doesn't define the field '{}'", - format_type_with_self( - self.db(), - self.self_type, - value_type - ), + format_type(self.db(), value_type), name ), self.file(), @@ -2255,10 +2123,12 @@ impl<'a> CheckMethodBody<'a> { continue; }; - let field_type = field - .value_type(self.db()) - .inferred(self.db_mut(), &mut ctx, immutable) - .cast_according_to(value_type, self.db()); + let raw_type = field.value_type(self.db()); + let field_type = + TypeResolver::new(&mut self.state.db, &args, self.bounds) + .with_immutable(immutable) + .resolve(raw_type) + .cast_according_to(value_type, self.db()); node.field_id = Some(field); @@ -2302,7 +2172,6 @@ impl<'a> CheckMethodBody<'a> { input_type: TypeRef, location: &SourceLocation, ) { - let mut typ_ctx = TypeContext::new(self.self_type); let compare = if input_type.is_owned_or_uni(self.db()) { input_type } else { @@ -2311,17 +2180,14 @@ impl<'a> CheckMethodBody<'a> { input_type.as_owned(self.db()) }; - if !compare.type_check(self.db_mut(), pattern_type, &mut typ_ctx, true) - { - let self_type = self.self_type; - + if !TypeChecker::check(self.db(), compare, pattern_type) { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( "The type of this pattern is '{}', \ but the input type is '{}'", - format_type_with_self(self.db(), self_type, pattern_type), - format_type_with_self(self.db(), self_type, input_type), + format_type(self.db(), pattern_type), + format_type(self.db(), input_type), ), self.file(), location.clone(), @@ -2340,9 +2206,7 @@ impl<'a> CheckMethodBody<'a> { return; } - let ins = if let Some(ins) = - value_type.as_enum_instance(self.db(), self.self_type) - { + let ins = if let Some(ins) = value_type.as_enum_instance(self.db()) { ins } else { self.state.diagnostics.error( @@ -2350,11 +2214,7 @@ impl<'a> CheckMethodBody<'a> { format!( "This pattern expects an enum class, \ but the input type is '{}'", - format_type_with_self( - self.db(), - self.self_type, - value_type - ), + format_type(self.db(), value_type), ), self.file(), node.location.clone(), @@ -2372,7 +2232,7 @@ impl<'a> CheckMethodBody<'a> { } else { self.state.diagnostics.undefined_variant( name, - format_type_with_self(self.db(), self.self_type, value_type), + format_type(self.db(), value_type), self.file(), node.location.clone(), ); @@ -2396,20 +2256,18 @@ impl<'a> CheckMethodBody<'a> { } let immutable = value_type.is_ref(self.db()); - let mut ctx = TypeContext::new(self.self_type); - - ins.copy_type_arguments_into(self.db(), &mut ctx.type_arguments); + let args = TypeArguments::for_class(self.db(), ins); + let bounds = self.bounds; for (patt, member) in node.values.iter_mut().zip(members.into_iter()) { - let typ = member - .inferred(self.db_mut(), &mut ctx, immutable) + let typ = TypeResolver::new(self.db_mut(), &args, bounds) + .with_immutable(immutable) + .resolve(member) .cast_according_to(value_type, self.db()); self.pattern(patt, typ, pattern); } - ins.copy_new_arguments_from(self.db_mut(), &ctx.type_arguments); - node.variant_id = Some(variant); } @@ -2549,19 +2407,18 @@ impl<'a> CheckMethodBody<'a> { if !val_type.allow_assignment(self.db()) { self.state.diagnostics.cant_assign_type( - &format_type_with_self(self.db(), self.self_type, val_type), + &format_type(self.db(), val_type), self.file(), value_node.location().clone(), ); } let var_type = var.value_type(self.db()); - let mut ctx = TypeContext::new(self.self_type); - if !val_type.type_check(self.db_mut(), var_type, &mut ctx, true) { + if !TypeChecker::check(self.db(), val_type, var_type) { self.state.diagnostics.type_error( - format_type_with_self(self.db(), self.self_type, val_type), - format_type_with_self(self.db(), self.self_type, var_type), + format_type(self.db(), val_type), + format_type(self.db(), var_type), self.file(), location.clone(), ); @@ -2575,7 +2432,7 @@ impl<'a> CheckMethodBody<'a> { fn closure( &mut self, node: &mut hir::Closure, - mut expected: Option<(ClosureId, TypeRef, &mut TypeContext)>, + mut expected: Option<(ClosureId, TypeRef, &TypeArguments)>, scope: &mut LexicalScope, ) -> TypeRef { let self_type = self.self_type; @@ -2585,33 +2442,22 @@ impl<'a> CheckMethodBody<'a> { .map_or(false, |(id, _, _)| id.is_moving(self.db())); let closure = Closure::alloc(self.db_mut(), moving); - let throw_type = if let Some(n) = node.throw_type.as_mut() { - self.type_signature(n, self_type) - } else { - let db = &mut self.state.db; - - expected - .as_mut() - .map(|(id, _, context)| { - id.throw_type(db).inferred(db, *context, false) - }) - .unwrap_or_else(|| TypeRef::placeholder(self.db_mut())) - }; - + let bounds = self.bounds; let return_type = if let Some(n) = node.return_type.as_mut() { self.type_signature(n, self_type) } else { - let db = &mut self.state.db; + let db = self.db_mut(); expected .as_mut() - .map(|(id, _, context)| { - id.return_type(db).inferred(db, *context, false) + .map(|(id, _, targs)| { + let raw = id.return_type(db); + + TypeResolver::new(db, targs, bounds).resolve(raw) }) - .unwrap_or_else(|| TypeRef::placeholder(self.db_mut())) + .unwrap_or_else(|| TypeRef::placeholder(db, None)) }; - closure.set_throw_type(self.db_mut(), throw_type); closure.set_return_type(self.db_mut(), return_type); let surrounding_type = @@ -2625,7 +2471,6 @@ impl<'a> CheckMethodBody<'a> { kind: ScopeKind::Closure(closure), surrounding_type, return_type, - throw_type, variables: VariableScope::new(), parent: Some(scope), in_closure: true, @@ -2637,15 +2482,16 @@ impl<'a> CheckMethodBody<'a> { let typ = if let Some(n) = arg.value_type.as_mut() { self.type_signature(n, self.self_type) } else { - let db = &mut self.state.db; + let db = self.db_mut(); expected .as_mut() - .and_then(|(id, _, context)| { - id.positional_argument_input_type(db, index) - .map(|t| t.inferred(db, context, false)) + .and_then(|(id, _, targs)| { + id.positional_argument_input_type(db, index).map(|t| { + TypeResolver::new(db, targs, bounds).resolve(t) + }) }) - .unwrap_or_else(|| TypeRef::placeholder(db)) + .unwrap_or_else(|| TypeRef::placeholder(db, None)) }; let var = @@ -2661,14 +2507,6 @@ impl<'a> CheckMethodBody<'a> { &node.location, ); - self.check_if_throws(throw_type, &node.location); - - if let TypeRef::Placeholder(id) = throw_type { - if id.value(self.db()).is_none() { - closure.set_throw_type(self.db_mut(), TypeRef::Never); - } - } - node.resolved_type = match expected.as_ref() { // If a closure is immediately passed to a `uni fn`, and we don't // capture any variables, we can safely infer the closure as unique. @@ -2701,7 +2539,7 @@ impl<'a> CheckMethodBody<'a> { let module = self.module; let (rec, rec_id, rec_kind, method) = { let rec = scope.surrounding_type; - let rec_id = rec.type_id(self.db(), self.self_type).unwrap(); + let rec_id = rec.type_id(self.db()).unwrap(); match rec_id.lookup_method(self.db(), name, module, false) { MethodLookup::Ok(method) => { @@ -2778,22 +2616,27 @@ impl<'a> CheckMethodBody<'a> { } }; - let mut call = MethodCall::new(self.state, module, rec, rec_id, method); + let mut call = MethodCall::new( + self.state, + module, + Some((self.self_type, self.method)), + rec, + rec_id, + method, + ); call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); - call.check_argument_count(self.state, &node.location); - - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); + call.check_type_bounds(self.state, &node.location); + call.check_arguments(self.state, &node.location); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); - self.check_missing_try(name, throws, &node.location); + let returns = call.return_type; node.kind = ConstantKind::Method(CallInfo { id: method, receiver: rec_kind, returns, - throws, dynamic: rec_id.use_dynamic_dispatch(), }); @@ -2819,9 +2662,8 @@ impl<'a> CheckMethodBody<'a> { } let (rec, rec_id, rec_kind, method) = { - let stype = self.self_type; let rec = scope.surrounding_type; - let rec_id = rec.type_id(self.db(), stype).unwrap(); + let rec_id = rec.type_id(self.db()).unwrap(); match rec_id.lookup_method(self.db(), name, module, true) { MethodLookup::Ok(method) => { @@ -2852,21 +2694,6 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; } _ => { - if let Some((field, raw_type)) = self.field_type(name) { - let typ = self.field_reference( - raw_type, - scope, - &node.location, - ); - - node.kind = IdentifierKind::Field(FieldInfo { - id: field, - variable_type: typ, - }); - - return typ; - } - if let Some(Symbol::Module(id)) = module.symbol(self.db(), name) { @@ -2907,22 +2734,26 @@ impl<'a> CheckMethodBody<'a> { } }; - let mut call = MethodCall::new(self.state, module, rec, rec_id, method); + let mut call = MethodCall::new( + self.state, + module, + Some((self.self_type, self.method)), + rec, + rec_id, + method, + ); call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); - call.check_argument_count(self.state, &node.location); - - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); - - self.check_missing_try(name, throws, &node.location); + call.check_type_bounds(self.state, &node.location); + call.check_arguments(self.state, &node.location); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); + let returns = call.return_type; node.kind = IdentifierKind::Method(CallInfo { id: method, receiver: rec_kind, returns, - throws, dynamic: rec_id.use_dynamic_dispatch(), }); @@ -3004,7 +2835,7 @@ impl<'a> CheckMethodBody<'a> { if !val_type.allow_assignment(self.db()) { self.state.diagnostics.cant_assign_type( - &format_type_with_self(self.db(), self.self_type, val_type), + &format_type(self.db(), val_type), self.file(), value_node.location().clone(), ); @@ -3022,13 +2853,10 @@ impl<'a> CheckMethodBody<'a> { return None; }; - let stype = self.self_type; - let mut ctx = TypeContext::new(stype); - - if !val_type.type_check(self.db_mut(), var_type, &mut ctx, true) { + if !TypeChecker::check(self.db(), val_type, var_type) { self.state.diagnostics.type_error( - format_type_with_self(self.db(), stype, val_type), - format_type_with_self(self.db(), stype, var_type), + format_type(self.db(), val_type), + format_type(self.db(), var_type), self.file(), location.clone(), ); @@ -3136,8 +2964,8 @@ impl<'a> CheckMethodBody<'a> { let lhs = self.expression(&mut node.left, scope); let rhs = self.expression(&mut node.right, scope); - self.require_boolean(lhs, self.self_type, node.left.location()); - self.require_boolean(rhs, self.self_type, node.right.location()); + self.require_boolean(lhs, node.left.location()); + self.require_boolean(rhs, node.right.location()); node.resolved_type = TypeRef::boolean(); node.resolved_type @@ -3151,8 +2979,8 @@ impl<'a> CheckMethodBody<'a> { let lhs = self.expression(&mut node.left, scope); let rhs = self.expression(&mut node.right, scope); - self.require_boolean(lhs, self.self_type, node.left.location()); - self.require_boolean(rhs, self.self_type, node.right.location()); + self.require_boolean(lhs, node.left.location()); + self.require_boolean(rhs, node.right.location()); node.resolved_type = TypeRef::boolean(); node.resolved_type @@ -3174,19 +3002,17 @@ impl<'a> CheckMethodBody<'a> { } let expected = scope.return_type; - let mut ctx = TypeContext::new(self.self_type); - if !returned.type_check(self.db_mut(), expected, &mut ctx, true) { + if !TypeChecker::check(self.db(), returned, expected) { self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, returned), - format_type_with_context(self.db(), &ctx, expected), + format_type(self.db(), returned), + format_type(self.db(), expected), self.file(), node.location.clone(), ); } node.resolved_type = returned; - TypeRef::Never } @@ -3195,30 +3021,41 @@ impl<'a> CheckMethodBody<'a> { node: &mut hir::Throw, scope: &mut LexicalScope, ) -> TypeRef { - let mut thrown = self.expression(&mut node.value, scope); - let expected = scope.throw_type; - let mut ctx = TypeContext::new(self.self_type); + let expr = self.expression(&mut node.value, scope); - if scope.in_recover() && thrown.is_owned(self.db()) { - thrown = thrown.as_uni(self.db()); + if expr.is_error(self.db()) { + return expr; } - if expected.is_never(self.db()) { - self.state + let ret_type = scope.return_type; + + node.return_type = ret_type; + + match ret_type.throw_kind(self.db()) { + ThrowKind::Unknown | ThrowKind::Option(_) => self + .state .diagnostics - .throw_not_allowed(self.file(), node.location.clone()); - } else if !thrown.type_check(self.db_mut(), expected, &mut ctx, true) { - self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, thrown), - format_type_with_context(self.db(), &ctx, expected), - self.file(), - node.location.clone(), - ); + .throw_not_available(self.file(), node.location.clone()), + ThrowKind::Result(ret_ok, ret_err) => { + node.resolved_type = + if scope.in_recover() && expr.is_owned(self.db()) { + expr.as_uni(self.db()) + } else { + expr + }; + + if !TypeChecker::check(self.db(), expr, ret_err) { + self.state.diagnostics.invalid_throw( + ThrowKind::Result(ret_ok, expr) + .throw_type_name(self.db(), ret_ok), + format_type(self.db(), ret_type), + self.file(), + node.location.clone(), + ); + } + } } - node.resolved_type = thrown; - self.thrown = true; - TypeRef::Never } @@ -3243,7 +3080,7 @@ impl<'a> CheckMethodBody<'a> { let mut scope = new_scope.inherit(ScopeKind::Regular); let typ = self.expression(guard, &mut scope); - self.require_boolean(typ, self.self_type, guard.location()); + self.require_boolean(typ, guard.location()); } if let Some(expected) = rtype { @@ -3278,9 +3115,9 @@ impl<'a> CheckMethodBody<'a> { ) -> TypeRef { let expr = self.expression(&mut node.value, scope); - if !expr.is_owned_or_uni(self.db()) { + if !expr.allow_as_ref(self.db()) { self.state.diagnostics.error( - DiagnosticId::InvalidRef, + DiagnosticId::InvalidType, format!( "A 'ref T' can't be created from a value of type '{}'", self.fmt(expr) @@ -3288,6 +3125,8 @@ impl<'a> CheckMethodBody<'a> { self.file(), node.location.clone(), ); + + return TypeRef::Error; } node.resolved_type = if expr.is_value_type(self.db()) { @@ -3306,9 +3145,9 @@ impl<'a> CheckMethodBody<'a> { ) -> TypeRef { let expr = self.expression(&mut node.value, scope); - if !expr.is_owned_or_uni(self.db()) { + if !expr.allow_as_mut(self.db()) { self.state.diagnostics.error( - DiagnosticId::InvalidRef, + DiagnosticId::InvalidType, format!( "A 'mut T' can't be created from a value of type '{}'", self.fmt(expr) @@ -3350,7 +3189,7 @@ impl<'a> CheckMethodBody<'a> { last_type.as_owned(db) } else { self.state.diagnostics.error( - DiagnosticId::InvalidRef, + DiagnosticId::InvalidType, format!( "Values of type '{}' can't be recovered", self.fmt(last_type) @@ -3371,152 +3210,76 @@ impl<'a> CheckMethodBody<'a> { node: &mut hir::AssignSetter, scope: &mut LexicalScope, ) -> TypeRef { - let loc = &node.location; let (receiver, allow_type_private) = self.call_receiver(&mut node.receiver, scope); let value = self.expression(&mut node.value, scope); let setter = node.name.name.clone() + "="; let module = self.module; - let rec_id = if let Some(id) = self.receiver_id(receiver, loc) { - id - } else { - return TypeRef::Error; - }; - - let method = - match rec_id.lookup_method( - self.db(), - &setter, - module, - allow_type_private, - ) { - MethodLookup::Ok(id) => id, - MethodLookup::Private => { - self.private_method_call(&setter, loc); - - return TypeRef::Error; - } - MethodLookup::InstanceOnStatic => { - self.invalid_instance_call(&setter, receiver, loc); + let rec_id = + if let Some(id) = self.receiver_id(receiver, &node.location) { + id + } else { + return TypeRef::Error; + }; - return TypeRef::Error; - } - MethodLookup::StaticOnInstance => { - self.invalid_static_call(&setter, receiver, loc); + let method = match rec_id.lookup_method( + self.db(), + &setter, + module, + allow_type_private, + ) { + MethodLookup::Ok(id) => id, + MethodLookup::Private => { + self.private_method_call(&setter, &node.location); - return TypeRef::Error; - } - MethodLookup::None => { - let field_name = &node.name.name; + return TypeRef::Error; + } + MethodLookup::InstanceOnStatic => { + self.invalid_instance_call(&setter, receiver, &node.location); - if let TypeId::ClassInstance(ins) = rec_id { - if let Some(field) = - ins.instance_of().field(self.db(), field_name) - { - if !field.is_visible_to(self.db(), module) { - self.state.diagnostics.private_field( - field_name, - self.file(), - loc.clone(), - ); - } - - if !receiver.allow_mutating() { - self.state.diagnostics.error( - DiagnosticId::InvalidCall, - format!( - "Can't assign a new value to field '{}', \ - as its receiver is immutable", - field_name, - ), - self.module.file(self.db()), - loc.clone(), - ); - } - - if node.else_block.is_some() { - self.state - .diagnostics - .never_throws(self.file(), loc.clone()); - } - - let mut ctx = TypeContext::for_class_instance( - self.db(), - self.self_type, - ins, - ); - let var_type = field - .value_type(self.db()) - .inferred(self.db_mut(), &mut ctx, false); - let value = - value.cast_according_to(var_type, self.db()); - - if !value.type_check( - self.db_mut(), - var_type, - &mut ctx, - true, - ) { - self.state.diagnostics.type_error( - self.fmt(value), - self.fmt(var_type), - self.file(), - loc.clone(), - ); - } - - if receiver.require_sendable_arguments(self.db()) - && !value.is_sendable(self.db()) - { - self.state.diagnostics.unsendable_field_value( - field_name, - self.fmt(value), - self.file(), - loc.clone(), - ); - } - - node.kind = CallKind::SetField(FieldInfo { - id: field, - variable_type: var_type, - }); - - return types::TypeRef::nil(); - } - } + return TypeRef::Error; + } + MethodLookup::StaticOnInstance => { + self.invalid_static_call(&setter, receiver, &node.location); + return TypeRef::Error; + } + MethodLookup::None => { + return if self.assign_field_with_receiver( + node, receiver, rec_id, value, scope, + ) { + TypeRef::nil() + } else { self.state.diagnostics.undefined_method( &setter, self.fmt(receiver), self.file(), - loc.clone(), + node.location.clone(), ); - return TypeRef::Error; - } - }; + TypeRef::Error + }; + } + }; - let mut call = - MethodCall::new(self.state, self.module, receiver, rec_id, method); + let loc = &node.location; + let mut call = MethodCall::new( + self.state, + self.module, + Some((self.self_type, self.method)), + receiver, + rec_id, + method, + ); call.check_mutability(self.state, loc); - call.check_bounded_implementation(self.state, loc); + call.check_type_bounds(self.state, loc); self.positional_argument(&mut call, 0, &mut node.value, scope); - call.check_argument_count(self.state, loc); - call.update_receiver_type_arguments(self.state, scope.surrounding_type); - - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); - - if let Some(block) = node.else_block.as_mut() { - if throws.is_never(self.db()) { - self.state.diagnostics.never_throws(self.file(), loc.clone()); - } + call.check_arguments(self.state, loc); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); - self.try_else_block(block, returns, throws, scope); - } else { - self.check_missing_try(&setter, throws, loc); - } + let returns = call.return_type; let rec_info = Receiver::class_or_explicit(self.db(), receiver); @@ -3524,13 +3287,111 @@ impl<'a> CheckMethodBody<'a> { id: method, receiver: rec_info, returns, - throws, dynamic: rec_id.use_dynamic_dispatch(), }); returns } + fn assign_field_with_receiver( + &mut self, + node: &mut hir::AssignSetter, + receiver: TypeRef, + receiver_id: TypeId, + value: TypeRef, + scope: &mut LexicalScope, + ) -> bool { + let name = &node.name.name; + + // When using `self.field = value`, none of the below is applicable, nor + // do we need to calculate the field type as it's already cached. + if receiver_id == self.self_type { + return if let Some((field, typ)) = self.check_field_assignment( + name, + &mut node.value, + &node.name.location, + scope, + ) { + node.kind = CallKind::SetField(FieldInfo { + class: receiver.class_id(self.db()).unwrap(), + id: field, + variable_type: typ, + }); + + true + } else { + false + }; + } + + let (ins, field) = if let TypeId::ClassInstance(ins) = receiver_id { + if let Some(field) = ins.instance_of().field(self.db(), name) { + (ins, field) + } else { + return false; + } + } else { + return false; + }; + + if !field.is_visible_to(self.db(), self.module) { + self.state.diagnostics.private_field( + name, + self.file(), + node.location.clone(), + ); + } + + if !receiver.allow_mutating() { + self.state.diagnostics.error( + DiagnosticId::InvalidCall, + format!( + "Can't assign a new value to field '{}', as its receiver \ + is immutable", + name, + ), + self.module.file(self.db()), + node.location.clone(), + ); + } + + let targs = TypeArguments::for_class(self.db(), ins); + let raw_type = field.value_type(self.db()); + let bounds = self.bounds; + let var_type = + TypeResolver::new(self.db_mut(), &targs, bounds).resolve(raw_type); + + let value = value.cast_according_to(var_type, self.db()); + + if !TypeChecker::check(self.db(), value, var_type) { + self.state.diagnostics.type_error( + self.fmt(value), + self.fmt(var_type), + self.file(), + node.location.clone(), + ); + } + + if receiver.require_sendable_arguments(self.db()) + && !value.is_sendable(self.db()) + { + self.state.diagnostics.unsendable_field_value( + name, + self.fmt(value), + self.file(), + node.location.clone(), + ); + } + + node.kind = CallKind::SetField(FieldInfo { + class: ins.instance_of(), + id: field, + variable_type: var_type, + }); + + true + } + fn call( &mut self, node: &mut hir::Call, @@ -3539,7 +3400,7 @@ impl<'a> CheckMethodBody<'a> { if let Some((rec, allow_type_private)) = node.receiver.as_mut().map(|r| self.call_receiver(r, scope)) { - if let Some(closure) = rec.closure_id(self.db(), self.self_type) { + if let Some(closure) = rec.closure_id(self.db()) { self.call_closure(rec, closure, node, scope) } else { self.call_with_receiver(rec, node, scope, allow_type_private) @@ -3567,6 +3428,17 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; } + if !receiver.allow_mutating() { + self.state.diagnostics.error( + DiagnosticId::InvalidCall, + "Closures can only be called using owned or mutable references", + self.file(), + node.location.clone(), + ); + + return TypeRef::Error; + } + let num_given = node.arguments.len(); let num_exp = closure.number_of_arguments(self.db()); @@ -3581,7 +3453,7 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; } - let mut ctx = TypeContext::new(self.self_type); + let targs = TypeArguments::new(); let mut exp_args = Vec::new(); for (index, arg_node) in node.arguments.iter_mut().enumerate() { @@ -3603,13 +3475,13 @@ impl<'a> CheckMethodBody<'a> { }; let given = self - .argument_expression(exp, arg_expr_node, scope, &mut ctx) + .argument_expression(exp, arg_expr_node, scope, &targs) .cast_according_to(exp, self.db()); - if !given.type_check(self.db_mut(), exp, &mut ctx, true) { + if !TypeChecker::check(self.db(), given, exp) { self.state.diagnostics.type_error( - format_type_with_context(self.db(), &ctx, given), - format_type_with_context(self.db(), &ctx, exp), + format_type(self.db(), given), + format_type(self.db(), exp), self.file(), arg_expr_node.location().clone(), ); @@ -3618,33 +3490,17 @@ impl<'a> CheckMethodBody<'a> { exp_args.push(exp); } - let throws = closure - .throw_type(self.db()) - .as_rigid_type(&mut self.state.db, self.bounds) - .inferred(self.db_mut(), &mut ctx, false); - - let returns = closure - .return_type(self.db()) - .as_rigid_type(&mut self.state.db, self.bounds) - .inferred(self.db_mut(), &mut ctx, false); + let returns = { + let raw = closure.return_type(self.db()); - if let Some(block) = node.else_block.as_mut() { - if throws.is_never(self.db()) { - self.state - .diagnostics - .never_throws(self.file(), node.location.clone()); - } - - self.try_else_block(block, returns, throws, scope); - } else { - self.check_missing_try(CALL_METHOD, throws, &node.location); - } + TypeResolver::new(&mut self.state.db, &targs, self.bounds) + .resolve(raw) + }; - node.kind = CallKind::ClosureCall(ClosureCallInfo { + node.kind = CallKind::CallClosure(ClosureCallInfo { id: closure, expected_arguments: exp_args, returns, - throws, }); returns @@ -3657,141 +3513,88 @@ impl<'a> CheckMethodBody<'a> { scope: &mut LexicalScope, allow_type_private: bool, ) -> TypeRef { - let name = &node.name.name; - let loc = &node.location; - let module = self.module; - let rec_id = if let Some(id) = self.receiver_id(receiver, loc) { - id - } else { - return TypeRef::Error; - }; + let rec_id = + if let Some(id) = self.receiver_id(receiver, &node.location) { + id + } else { + return TypeRef::Error; + }; let method = match rec_id.lookup_method( self.db(), - name, - module, + &node.name.name, + self.module, allow_type_private, ) { MethodLookup::Ok(id) => id, MethodLookup::Private => { - self.private_method_call(name, loc); + self.private_method_call(&node.name.name, &node.location); return TypeRef::Error; } MethodLookup::InstanceOnStatic => { - self.invalid_instance_call(name, receiver, loc); + self.invalid_instance_call( + &node.name.name, + receiver, + &node.location, + ); return TypeRef::Error; } MethodLookup::StaticOnInstance => { - self.invalid_static_call(name, receiver, loc); + self.invalid_static_call( + &node.name.name, + receiver, + &node.location, + ); return TypeRef::Error; } MethodLookup::None if node.arguments.is_empty() => { - if let TypeId::ClassInstance(ins) = rec_id { - if let Some(field) = - ins.instance_of().field(self.db(), name) - { - if !field.is_visible_to(self.db(), module) { - self.state.diagnostics.private_field( - &node.name.name, - self.file(), - node.location.clone(), - ); - } - - if node.else_block.is_some() { - self.state - .diagnostics - .never_throws(self.file(), loc.clone()); - } - - let mut ctx = TypeContext::new(self.self_type); - let db = self.db_mut(); - let raw_typ = field.value_type(db); - - ins.type_arguments(db) - .copy_into(&mut ctx.type_arguments); - - let mut returns = if raw_typ.is_owned_or_uni(db) { - let typ = raw_typ.inferred(db, &mut ctx, false); - - if receiver.is_ref(db) { - typ.as_ref(db) - } else { - typ.as_mut(db) - } - } else { - raw_typ.inferred(db, &mut ctx, receiver.is_ref(db)) - }; - - returns = returns.value_type_as_owned(self.db()); - - if receiver.require_sendable_arguments(self.db()) - && !returns.is_sendable(self.db()) - { - self.state.diagnostics.unsendable_field( - name, - self.fmt(returns), - self.file(), - node.location.clone(), - ); - } - - node.kind = CallKind::GetField(FieldInfo { - id: field, - variable_type: returns, - }); - - return returns; - } + if let Some(typ) = + self.field_with_receiver(node, receiver, rec_id, scope) + { + return typ; } self.state.diagnostics.undefined_method( - name, + &node.name.name, self.fmt(receiver), self.file(), - loc.clone(), + node.location.clone(), ); return TypeRef::Error; } MethodLookup::None => { self.state.diagnostics.undefined_method( - name, + &node.name.name, self.fmt(receiver), self.file(), - loc.clone(), + node.location.clone(), ); return TypeRef::Error; } }; - let mut call = - MethodCall::new(self.state, module, receiver, rec_id, method); + let mut call = MethodCall::new( + self.state, + self.module, + Some((self.self_type, self.method)), + receiver, + rec_id, + method, + ); call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); + call.check_type_bounds(self.state, &node.location); self.call_arguments(&mut node.arguments, &mut call, scope); - call.check_argument_count(self.state, &node.location); - call.update_receiver_type_arguments(self.state, scope.surrounding_type); - - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); - - if let Some(block) = node.else_block.as_mut() { - if throws.is_never(self.db()) { - self.state - .diagnostics - .never_throws(self.file(), node.location.clone()); - } + call.check_arguments(self.state, &node.location); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); - self.try_else_block(block, returns, throws, scope); - } else { - self.check_missing_try(name, throws, &node.location); - } + let returns = call.return_type; let rec_info = Receiver::class_or_explicit(self.db(), receiver); @@ -3799,7 +3602,6 @@ impl<'a> CheckMethodBody<'a> { id: method, receiver: rec_info, returns, - throws, dynamic: rec_id.use_dynamic_dispatch(), }); @@ -3812,10 +3614,9 @@ impl<'a> CheckMethodBody<'a> { scope: &mut LexicalScope, ) -> TypeRef { let name = &node.name.name; - let stype = self.self_type; let module = self.module; let rec = scope.surrounding_type; - let rec_id = rec.type_id(self.db(), stype).unwrap(); + let rec_id = rec.type_id(self.db()).unwrap(); let (rec_info, rec, rec_id, method) = match rec_id.lookup_method(self.db(), name, module, true) { MethodLookup::Ok(method) => { @@ -3876,176 +3677,112 @@ impl<'a> CheckMethodBody<'a> { } }; - let mut call = - MethodCall::new(self.state, self.module, rec, rec_id, method); + let mut call = MethodCall::new( + self.state, + self.module, + Some((self.self_type, self.method)), + rec, + rec_id, + method, + ); call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); + call.check_type_bounds(self.state, &node.location); self.call_arguments(&mut node.arguments, &mut call, scope); - call.check_argument_count(self.state, &node.location); - call.update_receiver_type_arguments(self.state, scope.surrounding_type); - - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); - - if let Some(block) = node.else_block.as_mut() { - if throws.is_never(self.db()) { - self.state - .diagnostics - .never_throws(self.file(), node.location.clone()); - } + call.check_arguments(self.state, &node.location); + call.resolve_return_type(self.state); + call.check_sendable(self.state, &node.location); - self.try_else_block(block, returns, throws, scope); - } else { - self.check_missing_try(name, throws, &node.location); - } + let returns = call.return_type; node.kind = CallKind::Call(CallInfo { id: method, receiver: rec_info, returns, - throws, dynamic: rec_id.use_dynamic_dispatch(), }); returns } - fn async_call( + fn field_with_receiver( &mut self, - node: &mut hir::AsyncCall, + node: &mut hir::Call, + receiver: TypeRef, + receiver_id: TypeId, scope: &mut LexicalScope, - ) -> TypeRef { - let allow_type_private = node.receiver.is_self(); - let rec_type = self.expression(&mut node.receiver, scope); + ) -> Option { let name = &node.name.name; - let (rec_id, method) = if let Some(found) = self.lookup_method( - rec_type, - name, - &node.location, - allow_type_private, - ) { - found - } else { - return TypeRef::Error; - }; - if !matches!( - rec_id, - TypeId::ClassInstance(ins) - if ins.instance_of().kind(self.db()).is_async() - ) { - let rec_name = - format_type_with_self(self.db(), self.self_type, rec_type); + if receiver_id == self.self_type { + return if let Some((field, raw_type)) = self.field_type(name) { + let typ = self.field_reference(raw_type, scope, &node.location); - self.state.diagnostics.error( - DiagnosticId::InvalidCall, - format!("'{}' isn't an async type", rec_name), - self.file(), - node.receiver.location().clone(), - ); + node.kind = CallKind::GetField(FieldInfo { + class: receiver.class_id(self.db()).unwrap(), + id: field, + variable_type: typ, + }); - return TypeRef::Error; + Some(typ) + } else { + self.state.diagnostics.undefined_method( + name, + self.fmt(receiver), + self.file(), + node.location.clone(), + ); + + None + }; } - if !method.is_async(self.db()) { - self.state.diagnostics.error( - DiagnosticId::InvalidCall, - format!("'{}' isn't an async method", name), + let (ins, field) = if let TypeId::ClassInstance(ins) = receiver_id { + ins.instance_of().field(self.db(), name).map(|field| (ins, field)) + } else { + None + }?; + + if !field.is_visible_to(self.db(), self.module) { + self.state.diagnostics.private_field( + &node.name.name, self.file(), - node.name.location.clone(), + node.location.clone(), ); - - return TypeRef::Error; } - let mut call = - MethodCall::new(self.state, self.module, rec_type, rec_id, method); - - call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); - self.call_arguments(&mut node.arguments, &mut call, scope); - call.check_argument_count(self.state, &node.location); - call.update_receiver_type_arguments(self.state, scope.surrounding_type); + let raw_type = field.value_type(self.db_mut()); + let immutable = receiver.is_ref(self.db_mut()); + let args = ins.type_arguments(self.db_mut()).clone(); + let bounds = self.bounds; + let mut returns = TypeResolver::new(self.db_mut(), &args, bounds) + .with_immutable(immutable) + .resolve(raw_type); - let returns = call.return_type(self.state, &node.location); - let throws = call.throw_type(self.state, &node.location); - let fut_class = ClassId::future(); - let mut fut_args = TypeArguments::new(); - let fut_params = fut_class.type_parameters(self.db()); - - fut_args.assign(fut_params[0], returns); - fut_args.assign(fut_params[1], throws); + if returns.is_value_type(self.db_mut()) { + returns = returns.as_owned(self.db_mut()); + } else if !immutable && raw_type.is_owned_or_uni(self.db_mut()) { + returns = returns.as_mut(self.db_mut()); + } - let throws = TypeRef::Never; - let returns = TypeRef::Owned(TypeId::ClassInstance( - ClassInstance::generic(self.db_mut(), fut_class, fut_args), - )); + if receiver.require_sendable_arguments(self.db()) + && !returns.is_sendable(self.db()) + { + self.state.diagnostics.unsendable_field( + name, + self.fmt(returns), + self.file(), + node.location.clone(), + ); + } - node.info = Some(CallInfo { - id: method, - receiver: Receiver::Explicit, - returns, - throws, - dynamic: rec_id.use_dynamic_dispatch(), + node.kind = CallKind::GetField(FieldInfo { + id: field, + class: ins.instance_of(), + variable_type: returns, }); - returns - } - - fn try_else_block( - &mut self, - node: &mut hir::ElseBlock, - returns: TypeRef, - throws: TypeRef, - scope: &mut LexicalScope, - ) { - let mut new_scope = scope.inherit(ScopeKind::Regular); - - if let Some(var_def) = node.argument.as_mut() { - let typ = if let Some(n) = var_def.value_type.as_mut() { - let mut typ_ctx = TypeContext::new(self.self_type); - let exp_type = self.type_signature(n, self.self_type); - - if !throws.type_check( - self.db_mut(), - exp_type, - &mut typ_ctx, - true, - ) { - let self_type = self.self_type; - - self.state.diagnostics.type_error( - format_type_with_self(self.db(), self_type, throws), - format_type_with_self(self.db(), self_type, exp_type), - self.file(), - node.location.clone(), - ); - } - - exp_type - } else { - throws - }; - - if var_def.name.name != IGNORE_VARIABLE { - let var = new_scope.variables.new_variable( - self.db_mut(), - var_def.name.name.clone(), - typ, - false, - ); - - var_def.variable_id = Some(var); - } - } - - self.expressions_with_return( - returns, - &mut node.body, - &mut new_scope, - &node.location, - ); + Some(returns) } fn builtin_call( @@ -4053,11 +3790,9 @@ impl<'a> CheckMethodBody<'a> { node: &mut hir::BuiltinCall, scope: &mut LexicalScope, ) -> TypeRef { - let args: Vec = node - .arguments - .iter_mut() - .map(|n| self.expression(n, scope)) - .collect(); + for n in &mut node.arguments { + self.expression(n, scope); + } let id = if let Some(id) = self.db().builtin_function(&node.name.name) { id @@ -4071,43 +3806,9 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; }; - // `try!` is desugared into `try x else (err) _INKO.panic_thrown(err)`. - // This way we don't need to duplicate a lot of the `try` logic for - // `try!`. We handle this case explicitly here to provide better error - // message in case a thrown type doesn't implement ToString. - if let BuiltinFunctionKind::Macro(CompilerMacro::PanicThrown) = - id.kind(self.db()) - { - let trait_id = - self.db().trait_in_module(STRING_MODULE, TO_STRING_TRAIT); - let arg = args[0]; + let returns = id.return_type(); - if !arg.implements_trait_id(self.db(), trait_id, self.self_type) { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "This expression may panic with a value of type '{}', \ - but this type doesn't implement '{}::{}'", - format_type_with_self(self.db(), self.self_type, arg), - STRING_MODULE, - TO_STRING_TRAIT - ), - self.file(), - node.location.clone(), - ); - } - } - - let returns = id.return_type(self.db()); - let throws = id.throw_type(self.db()); - - if let Some(block) = node.else_block.as_mut() { - self.try_else_block(block, returns, throws, scope); - } else { - self.check_missing_try(&node.name.name, throws, &node.location); - } - - node.info = Some(BuiltinCallInfo { id, returns, throws }); + node.info = Some(BuiltinCallInfo { id, returns }); returns } @@ -4118,16 +3819,39 @@ impl<'a> CheckMethodBody<'a> { scope: &mut LexicalScope, ) -> TypeRef { let expr_type = self.expression(&mut node.value, scope); - let cast_type = self.type_signature(&mut node.cast_to, self.self_type); - let mut ctx = TypeContext::new(self.self_type); - if !expr_type.allow_cast_to(self.db_mut(), cast_type, &mut ctx) { + let rules = Rules { + type_parameters_as_rigid: true, + type_parameters_as_owned: true, + ..Default::default() + }; + let type_scope = TypeScope::with_bounds( + self.module, + self.self_type, + Some(self.method), + self.bounds, + ); + + let cast_type = DefineAndCheckTypeSignature::new( + self.state, + self.module, + &type_scope, + rules, + ) + .define_type(&mut node.cast_to); + + // Casting to/from Any is dangerous but necessary to make the standard + // library work. + if !expr_type.is_any(self.db()) + && !cast_type.is_any(self.db()) + && !TypeChecker::check(self.db_mut(), expr_type, cast_type) + { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( "The type '{}' can't be cast to type '{}'", - format_type_with_context(self.db(), &ctx, expr_type), - format_type_with_context(self.db(), &ctx, cast_type) + format_type(self.db(), expr_type), + format_type(self.db(), cast_type) ), self.file(), node.location.clone(), @@ -4140,108 +3864,78 @@ impl<'a> CheckMethodBody<'a> { node.resolved_type } - fn index_expression( + fn try_expression( &mut self, - node: &mut hir::Index, + node: &mut hir::Try, scope: &mut LexicalScope, ) -> TypeRef { - let index = self.db().trait_in_module(INDEX_MODULE, INDEX_TRAIT); - let index_mut = - self.db().trait_in_module(INDEX_MODULE, INDEX_MUT_TRAIT); - - let stype = self.self_type; - let allow_type_private = node.receiver.is_self(); - let rec = self.expression(&mut node.receiver, scope); - let name = if rec.allow_mutating() - && rec.implements_trait_id(self.db(), index_mut, stype) - { - INDEX_MUT_METHOD - } else if rec.implements_trait_id(self.db(), index, stype) { - INDEX_METHOD - } else { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "The type '{typ}' must implement either \ - {module}::{index} or {module}::{index_mut}", - typ = format_type_with_self(self.db(), stype, rec), - module = INDEX_MODULE, - index = INDEX_TRAIT, - index_mut = INDEX_MUT_TRAIT - ), - self.file(), - node.receiver.location().clone(), - ); - - return TypeRef::Error; - }; - - let (rec_id, method) = if let Some(found) = - self.lookup_method(rec, name, &node.location, allow_type_private) - { - found - } else { - return TypeRef::Error; - }; - - let mut call = - MethodCall::new(self.state, self.module, rec, rec_id, method); - - call.check_mutability(self.state, &node.location); - call.check_bounded_implementation(self.state, &node.location); - self.positional_argument(&mut call, 0, &mut node.index, scope); - call.check_argument_count(self.state, &node.location); - call.update_receiver_type_arguments(self.state, scope.surrounding_type); + let expr = self.expression(&mut node.expression, scope); - let returns = call.return_type(self.state, &node.location); + if expr.is_error(self.db()) { + return expr; + } - node.info = Some(CallInfo { - id: method, - receiver: Receiver::Explicit, - returns, - throws: TypeRef::Never, - dynamic: rec_id.use_dynamic_dispatch(), - }); + let recovery = scope.in_recover(); + let expr_kind = expr.throw_kind(self.db()); + let ret_type = scope.return_type; + let ret_kind = ret_type.throw_kind(self.db()); - returns - } + node.return_type = ret_type; + node.kind = + if recovery { expr_kind.as_uni(self.db()) } else { expr_kind }; - fn lookup_method( - &mut self, - receiver: TypeRef, - name: &str, - location: &SourceLocation, - allow_type_private: bool, - ) -> Option<(TypeId, MethodId)> { - let rec_id = self.receiver_id(receiver, location)?; + match (expr_kind, ret_kind) { + (ThrowKind::Option(some), ThrowKind::Option(_)) => { + // If the value is a None, then `try` produces a new `None`, so + // no type-checking is necessary in this case. + return some; + } + ( + ThrowKind::Result(ok, expr_err), + ThrowKind::Result(ret_ok, ret_err), + ) => { + if TypeChecker::check(self.db(), expr_err, ret_err) { + return ok; + } - match rec_id.lookup_method( - self.db(), - name, - self.module, - allow_type_private, - ) { - MethodLookup::Ok(id) => return Some((rec_id, id)), - MethodLookup::Private => { - self.private_method_call(name, location); + self.state.diagnostics.invalid_throw( + expr_kind.throw_type_name(self.db(), ret_ok), + format_type(self.db(), ret_type), + self.file(), + node.location.clone(), + ); } - MethodLookup::InstanceOnStatic => { - self.invalid_instance_call(name, receiver, location); + (ThrowKind::Unknown, _) => { + self.state.diagnostics.invalid_try( + format_type(self.db(), expr), + self.file(), + node.expression.location().clone(), + ); } - MethodLookup::StaticOnInstance => { - self.invalid_static_call(name, receiver, location); + (_, ThrowKind::Unknown) => { + self.state + .diagnostics + .try_not_available(self.file(), node.location.clone()); } - MethodLookup::None => { - self.state.diagnostics.undefined_method( - name, - self.fmt(receiver), + (ThrowKind::Option(_), ThrowKind::Result(ret_ok, _)) => { + self.state.diagnostics.invalid_throw( + expr_kind.throw_type_name(self.db(), ret_ok), + format_type(self.db(), ret_type), self.file(), - location.clone(), + node.location.clone(), + ); + } + (ThrowKind::Result(_, _), ThrowKind::Option(ok)) => { + self.state.diagnostics.invalid_throw( + expr_kind.throw_type_name(self.db(), ok), + format_type(self.db(), ret_type), + self.file(), + node.location.clone(), ); } } - None + TypeRef::Error } fn receiver_id( @@ -4249,7 +3943,7 @@ impl<'a> CheckMethodBody<'a> { receiver: TypeRef, location: &SourceLocation, ) -> Option { - match receiver.type_id(self.db(), self.self_type) { + match receiver.type_id(self.db()) { Ok(id) => Some(id), Err(TypeRef::Error) => None, Err(TypeRef::Placeholder(_)) => { @@ -4353,7 +4047,7 @@ impl<'a> CheckMethodBody<'a> { expected, node, scope, - &mut call.context, + &call.type_arguments, ); call.check_argument(self.state, given, expected, node.location()); @@ -4381,7 +4075,7 @@ impl<'a> CheckMethodBody<'a> { expected, &mut node.value, scope, - &mut call.context, + &call.type_arguments, ); if call.named_arguments.contains(name) { @@ -4420,27 +4114,6 @@ impl<'a> CheckMethodBody<'a> { } } - fn check_missing_try( - &mut self, - name: &str, - throws: TypeRef, - location: &SourceLocation, - ) { - if throws.is_present(self.db()) { - self.state.diagnostics.error( - DiagnosticId::InvalidCall, - format!( - "The method '{}' may throw a value of type '{}', \ - but the 'try' keyword is missing", - name, - format_type_with_self(self.db(), self.self_type, throws), - ), - self.file(), - location.clone(), - ); - } - } - fn check_if_self_is_allowed( &mut self, scope: &LexicalScope, @@ -4453,13 +4126,8 @@ impl<'a> CheckMethodBody<'a> { } } - fn require_boolean( - &mut self, - typ: TypeRef, - self_type: TypeId, - location: &SourceLocation, - ) { - if typ == TypeRef::Error || typ.is_bool(self.db(), self.self_type) { + fn require_boolean(&mut self, typ: TypeRef, location: &SourceLocation) { + if typ == TypeRef::Error || typ.is_bool(self.db()) { return; } @@ -4468,7 +4136,7 @@ impl<'a> CheckMethodBody<'a> { format!( "Expected a 'Bool', 'ref Bool' or 'mut Bool', \ found '{}' instead", - format_type_with_self(self.db(), self_type, typ), + format_type(self.db(), typ), ), self.file(), location.clone(), @@ -4531,14 +4199,7 @@ impl<'a> CheckMethodBody<'a> { scope: &LexicalScope, location: &SourceLocation, ) -> TypeRef { - let typ = if scope.in_closure && raw_type.is_owned_or_uni(self.db()) { - // Closures capture `self` as a whole, instead of capturing - // individual fields. If `self` is owned, this means we have to - // expose fields as references; not owned values. - raw_type.as_mut(self.db()) - } else { - raw_type.cast_according_to(scope.surrounding_type, self.db()) - }; + let typ = raw_type.cast_according_to(scope.surrounding_type, self.db()); if scope.in_recover() && !typ.is_sendable(self.db()) { self.state.diagnostics.unsendable_type_in_recover( @@ -4565,7 +4226,7 @@ impl<'a> CheckMethodBody<'a> { } fn fmt(&self, typ: TypeRef) -> String { - format_type_with_self(self.db(), self.self_type, typ) + format_type(self.db(), typ) } fn invalid_static_call( diff --git a/compiler/src/type_check/imports.rs b/compiler/src/type_check/imports.rs index 5ed17e9c3..2aed965b7 100644 --- a/compiler/src/type_check/imports.rs +++ b/compiler/src/type_check/imports.rs @@ -110,7 +110,7 @@ impl<'a> DefineImportedTypes<'a> { ); } else if symbol.is_private(self.db()) { self.state.diagnostics.error( - DiagnosticId::PrivateSymbol, + DiagnosticId::InvalidSymbol, format!( "The symbol '{}' is private and can't be imported", name @@ -678,7 +678,7 @@ mod tests { let error = state.diagnostics.iter().next().unwrap(); - assert_eq!(error.id(), DiagnosticId::PrivateSymbol); + assert_eq!(error.id(), DiagnosticId::InvalidSymbol); assert_eq!(error.file(), &PathBuf::from("test.inko")); assert_eq!(error.location(), &cols(3, 3)); } diff --git a/compiler/src/type_check/methods.rs b/compiler/src/type_check/methods.rs index 8511aaf57..b28b2e3ed 100644 --- a/compiler/src/type_check/methods.rs +++ b/compiler/src/type_check/methods.rs @@ -2,15 +2,17 @@ use crate::diagnostics::DiagnosticId; use crate::hir; use crate::state::State; -use crate::type_check::{DefineAndCheckTypeSignature, Rules, TypeScope}; +use crate::type_check::{ + define_type_bounds, DefineAndCheckTypeSignature, Rules, TypeScope, +}; use ast::source_location::SourceLocation; -use bytecode::{BuiltinFunction as BIF, Opcode}; use std::path::PathBuf; +use types::check::{Environment, TypeChecker}; +use types::format::{format_type, format_type_with_arguments}; use types::{ - format_type, Block, BuiltinFunction, BuiltinFunctionKind, ClassId, - ClassInstance, CompilerMacro, Database, Method, MethodId, MethodKind, - MethodSource, ModuleId, Symbol, TraitId, TraitInstance, TypeBounds, - TypeContext, TypeId, TypeRef, Visibility, DROP_METHOD, MAIN_CLASS, + Block, ClassId, ClassInstance, Database, Method, MethodId, MethodKind, + MethodSource, ModuleId, Symbol, TraitId, TraitInstance, TypeArguments, + TypeBounds, TypeId, TypeRef, Visibility, DROP_METHOD, MAIN_CLASS, MAIN_METHOD, }; @@ -83,6 +85,10 @@ trait MethodDefiner { param_node.name.name.clone(), ); + if param_node.mutable { + pid.set_mutable(self.db_mut()); + } + param_node.type_parameter_id = Some(pid); } } @@ -162,7 +168,9 @@ trait MethodDefiner { let file = self.file(); let loc = node.location.clone(); - self.state_mut().diagnostics.unsendable_type(name, file, loc); + self.state_mut() + .diagnostics + .unsendable_async_type(name, file, loc); } let var_type = arg_type.as_rigid_type( @@ -179,32 +187,6 @@ trait MethodDefiner { } } - fn define_throw_type( - &mut self, - node: Option<&mut hir::Type>, - method: MethodId, - rules: Rules, - scope: &TypeScope, - ) { - let typ = if let Some(node) = node { - let typ = self.type_check(node, rules, scope); - - if method.is_async(self.db()) && !typ.is_sendable(self.db()) { - let name = format_type(self.db(), typ); - let file = self.file(); - let loc = node.location().clone(); - - self.state_mut().diagnostics.unsendable_type(name, file, loc); - } - - typ - } else { - TypeRef::Never - }; - - method.set_throw_type(self.db_mut(), typ); - } - fn define_return_type( &mut self, node: Option<&mut hir::Type>, @@ -220,7 +202,9 @@ trait MethodDefiner { let file = self.file(); let loc = node.location().clone(); - self.state_mut().diagnostics.unsendable_type(name, file, loc); + self.state_mut() + .diagnostics + .unsendable_async_type(name, file, loc); } typ @@ -375,13 +359,17 @@ impl<'a> DefineMethods<'a> { for expr in &mut node.body { match expr { hir::ClassExpression::AsyncMethod(ref mut node) => { - self.define_async_method(class_id, node); + self.define_async_method(class_id, node, TypeBounds::new()); } hir::ClassExpression::StaticMethod(ref mut node) => { self.define_static_method(class_id, node) } hir::ClassExpression::InstanceMethod(ref mut node) => { - self.define_instance_method(class_id, node); + self.define_instance_method( + class_id, + node, + TypeBounds::new(), + ); } hir::ClassExpression::Variant(ref mut node) => { self.define_variant_method(class_id, node); @@ -461,16 +449,32 @@ impl<'a> DefineMethods<'a> { } }; + if class_id.kind(self.db()).is_extern() { + self.state.diagnostics.error( + DiagnosticId::InvalidImplementation, + "Methods can't be defined for extern classes", + self.file(), + node.location.clone(), + ); + } + + let bounds = define_type_bounds( + self.state, + self.module, + class_id, + &mut node.bounds, + ); + for expr in &mut node.body { match expr { hir::ReopenClassExpression::InstanceMethod(ref mut n) => { - self.define_instance_method(class_id, n); + self.define_instance_method(class_id, n, bounds.clone()); } hir::ReopenClassExpression::StaticMethod(ref mut n) => { self.define_static_method(class_id, n); } hir::ReopenClassExpression::AsyncMethod(ref mut n) => { - self.define_async_method(class_id, n); + self.define_async_method(class_id, n, bounds.clone()); } } } @@ -484,11 +488,9 @@ impl<'a> DefineMethods<'a> { let method = node.method_id.unwrap(); method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); let scope = TypeScope::new(self.module, self_type, Some(method)); let rules = Rules { - allow_self_type: false, allow_private_types: method.is_private(self.db()), ..Default::default() }; @@ -504,7 +506,6 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -519,9 +520,12 @@ impl<'a> DefineMethods<'a> { node: &mut hir::DefineStaticMethod, ) { let receiver = TypeRef::Owned(TypeId::Class(class_id)); - let self_type = TypeId::ClassInstance( - ClassInstance::for_static_self_type(self.db_mut(), class_id), - ); + let bounds = TypeBounds::new(); + let self_type = TypeId::ClassInstance(ClassInstance::rigid( + self.db_mut(), + class_id, + &bounds, + )); let module = self.module; let method = Method::alloc( self.db_mut(), @@ -532,7 +536,6 @@ impl<'a> DefineMethods<'a> { ); method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); let scope = TypeScope::new(self.module, self_type, Some(method)); let rules = Rules { @@ -552,7 +555,6 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -573,6 +575,7 @@ impl<'a> DefineMethods<'a> { &mut self, class_id: ClassId, node: &mut hir::DefineInstanceMethod, + mut bounds: TypeBounds, ) { let async_class = class_id.kind(self.db()).is_async(); @@ -605,6 +608,10 @@ impl<'a> DefineMethods<'a> { kind, ); + if !method.is_mutable(self.db()) { + bounds.make_immutable(self.db_mut()); + } + // Regular instance methods on an `async class` must be private to the // class itself. if async_class && method.is_public(self.db()) { @@ -623,17 +630,14 @@ impl<'a> DefineMethods<'a> { || method.is_private(self.db()), ..Default::default() }; - let bounds = TypeBounds::new(); - let self_type = - TypeId::ClassInstance(ClassInstance::for_instance_self_type( - self.db_mut(), - class_id, - &bounds, - )); + let self_type = TypeId::ClassInstance(ClassInstance::rigid( + self.db_mut(), + class_id, + &bounds, + )); let receiver = receiver_type(self_type, node.kind); method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); let scope = TypeScope::with_bounds( self.module, @@ -648,7 +652,6 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -662,6 +665,7 @@ impl<'a> DefineMethods<'a> { &node.location, ); + method.set_bounds(self.db_mut(), bounds); node.method_id = Some(method); } @@ -669,6 +673,7 @@ impl<'a> DefineMethods<'a> { &mut self, class_id: ClassId, node: &mut hir::DefineAsyncMethod, + mut bounds: TypeBounds, ) { let self_id = TypeId::Class(class_id); let module = self.module; @@ -685,6 +690,10 @@ impl<'a> DefineMethods<'a> { kind, ); + if !method.is_mutable(self.db()) { + bounds.make_immutable(self.db_mut()); + } + if !class_id.kind(self.db()).is_async() { let file = self.file(); @@ -699,18 +708,16 @@ impl<'a> DefineMethods<'a> { self.define_type_parameters(&mut node.type_parameters, method, self_id); let rules = Rules { - allow_self_type: true, allow_private_types: class_id.is_private(self.db()) || method.is_private(self.db()), ..Default::default() }; let bounds = TypeBounds::new(); - let self_type = - TypeId::ClassInstance(ClassInstance::for_instance_self_type( - self.db_mut(), - class_id, - &bounds, - )); + let self_type = TypeId::ClassInstance(ClassInstance::rigid( + self.db_mut(), + class_id, + &bounds, + )); let receiver = if node.mutable { TypeRef::Mut(self_type) } else { @@ -718,7 +725,7 @@ impl<'a> DefineMethods<'a> { }; method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); + method.set_return_type(self.db_mut(), TypeRef::nil()); let scope = TypeScope::with_bounds( self.module, @@ -733,13 +740,16 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); - self.define_return_type( - node.return_type.as_mut(), - method, - rules, - &scope, - ); + + if node.return_type.is_some() { + self.state.diagnostics.error( + DiagnosticId::InvalidMethod, + "Async methods can't return values", + self.file(), + node.location.clone(), + ); + } + self.add_method_to_class( method, class_id, @@ -747,6 +757,7 @@ impl<'a> DefineMethods<'a> { &node.location, ); + method.set_bounds(self.db_mut(), bounds); node.method_id = Some(method); } @@ -774,7 +785,7 @@ impl<'a> DefineMethods<'a> { ..Default::default() }; let bounds = TypeBounds::new(); - let self_type = TypeId::TraitInstance(TraitInstance::for_self_type( + let self_type = TypeId::TraitInstance(TraitInstance::rigid( self.db_mut(), trait_id, &bounds, @@ -782,7 +793,6 @@ impl<'a> DefineMethods<'a> { let receiver = receiver_type(self_type, node.kind); method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); let scope = TypeScope::with_bounds( self.module, @@ -797,7 +807,6 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -843,7 +852,7 @@ impl<'a> DefineMethods<'a> { ..Default::default() }; let bounds = TypeBounds::new(); - let self_type = TypeId::TraitInstance(TraitInstance::for_self_type( + let self_type = TypeId::TraitInstance(TraitInstance::rigid( self.db_mut(), trait_id, &bounds, @@ -851,7 +860,6 @@ impl<'a> DefineMethods<'a> { let receiver = receiver_type(self_type, node.kind); method.set_receiver(self.db_mut(), receiver); - method.set_self_type(self.db_mut(), self_type); let scope = TypeScope::with_bounds( self.module, @@ -866,7 +874,6 @@ impl<'a> DefineMethods<'a> { &scope, ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -922,10 +929,23 @@ impl<'a> DefineMethods<'a> { let stype = TypeId::Class(class_id); let rec = TypeRef::Owned(stype); + let ret = if class_id.is_generic(self.db()) { + let args = class_id + .type_parameters(self.db()) + .into_iter() + .map(|param| TypeRef::Infer(TypeId::TypeParameter(param))) + .collect(); + + ClassInstance::with_types(self.db_mut(), class_id, args) + } else { + ClassInstance::new(class_id) + }; method.set_receiver(self.db_mut(), rec); - method.set_self_type(self.db_mut(), stype); - method.set_return_type(self.db_mut(), TypeRef::OwnedSelf); + method.set_return_type( + self.db_mut(), + TypeRef::Owned(TypeId::ClassInstance(ret)), + ); class_id.add_method(self.db_mut(), name, method); node.method_id = Some(method); @@ -970,8 +990,10 @@ impl<'a> CheckMainMethod<'a> { let mod_id = self.db().module(main_mod); - if let Some(method) = self.main_method(mod_id) { + if let Some((class, method)) = self.main_method(mod_id) { method.set_main(self.db_mut()); + self.db_mut().set_main_method(method); + self.db_mut().set_main_class(class); true } else { self.state.diagnostics.error( @@ -989,7 +1011,7 @@ impl<'a> CheckMainMethod<'a> { } } - fn main_method(&self, mod_id: ModuleId) -> Option { + fn main_method(&self, mod_id: ModuleId) -> Option<(ClassId, MethodId)> { let class = if let Some(Symbol::Class(class_id)) = mod_id.symbol(self.db(), MAIN_CLASS) { @@ -998,8 +1020,6 @@ impl<'a> CheckMainMethod<'a> { return None; }; - let stype = TypeId::ClassInstance(ClassInstance::new(class)); - if !class.kind(self.db()).is_async() { return None; } @@ -1008,10 +1028,9 @@ impl<'a> CheckMainMethod<'a> { if method.kind(self.db()) == MethodKind::Async && method.number_of_arguments(self.db()) == 0 - && method.throw_type(self.db()).is_never(self.db()) - && method.return_type(self.db()).is_nil(self.db(), stype) + && method.return_type(self.db()).is_nil(self.db()) { - Some(method) + Some((class, method)) } else { None } @@ -1088,14 +1107,13 @@ impl<'a> ImplementTraitMethods<'a> { ); } - let bounded = !node.bounds.is_empty(); let bounds = class_id .trait_implementation(self.db(), trait_id) .map(|i| i.bounds.clone()) .unwrap(); for expr in &mut node.body { - self.implement_method(expr, class_ins, trait_ins, bounded, &bounds); + self.implement_method(expr, class_ins, trait_ins, bounds.clone()); } for req in trait_id.required_methods(self.db()) { @@ -1126,7 +1144,7 @@ impl<'a> ImplementTraitMethods<'a> { continue; } - let source = MethodSource::implementation(bounded, trait_ins); + let source = MethodSource::Implementation(trait_ins, method); let name = method.name(self.db()).clone(); let copy = method.copy_method(self.db_mut()); @@ -1140,8 +1158,7 @@ impl<'a> ImplementTraitMethods<'a> { node: &mut hir::DefineInstanceMethod, class_instance: ClassInstance, trait_instance: TraitInstance, - bounded: bool, - bounds: &TypeBounds, + mut bounds: TypeBounds, ) { let name = &node.name.name; let original = if let Some(method) = @@ -1176,6 +1193,10 @@ impl<'a> ImplementTraitMethods<'a> { method_kind(node.kind), ); + if !method.is_mutable(self.db()) { + bounds.make_immutable(self.db_mut()); + } + self.define_type_parameters( &mut node.type_parameters, method, @@ -1191,18 +1212,17 @@ impl<'a> ImplementTraitMethods<'a> { }; let receiver = receiver_type(self_type, node.kind); - method.set_self_type(self.db_mut(), self_type); method.set_receiver(self.db_mut(), receiver); method.set_source( self.db_mut(), - MethodSource::implementation(bounded, trait_instance), + MethodSource::Implementation(trait_instance, original), ); let scope = TypeScope::with_bounds( self.module, self_type, Some(method), - bounds, + &bounds, ); self.define_type_parameter_requirements( @@ -1212,7 +1232,6 @@ impl<'a> ImplementTraitMethods<'a> { ); self.define_arguments(&mut node.arguments, method, rules, &scope); - self.define_throw_type(node.throw_type.as_mut(), method, rules, &scope); self.define_return_type( node.return_type.as_mut(), method, @@ -1220,24 +1239,19 @@ impl<'a> ImplementTraitMethods<'a> { &scope, ); - let mut check_ctx = TypeContext::new(self_type); + let targs = TypeArguments::for_trait(self.db(), trait_instance); + let mut env = Environment::new(targs.clone(), targs); - if trait_instance.instance_of().is_generic(self.db()) { - trait_instance - .type_arguments(self.db()) - .copy_into(&mut check_ctx.type_arguments); - } - - if !method.type_check(self.db_mut(), original, &mut check_ctx) { + if !TypeChecker::new(self.db()).check_method(method, original, &mut env) + { let file = self.file(); - let expected = format_type(self.db(), original); + let lhs = format_type_with_arguments(self.db(), &env.left, method); + let rhs = + format_type_with_arguments(self.db(), &env.right, original); self.state_mut().diagnostics.error( DiagnosticId::InvalidMethod, - format!( - "This method isn't compatible with the method '{}'", - expected - ), + format!("The method '{}' isn't compatible with '{}'", lhs, rhs), file, node.location.clone(), ); @@ -1251,6 +1265,8 @@ impl<'a> ImplementTraitMethods<'a> { method.mark_as_destructor(self.db_mut()); } + method.set_bounds(self.db_mut(), bounds); + self.add_method_to_class( method, class_instance.instance_of(), @@ -1275,287 +1291,3 @@ impl<'a> MethodDefiner for ImplementTraitMethods<'a> { self.module } } - -/// A compiler pass that defines all built-in function signatures. -/// -/// This is just a regular function as we don't need to traverse any modules. -/// -/// We set these functions up separately as some of them depend on certain types -/// (e.g. `Array`) being set up correctly. -pub(crate) fn define_builtin_functions(state: &mut State) -> bool { - let db = &mut state.db; - let nil = TypeRef::nil(); - let int = TypeRef::int(); - let float = TypeRef::float(); - let string = TypeRef::string(); - let any = TypeRef::Any; - let never = TypeRef::Never; - let boolean = TypeRef::boolean(); - let byte_array = TypeRef::byte_array(); - let string_array = TypeRef::array(db, string); - - // All the functions provided by the VM. - let vm = vec![ - (BIF::ChildProcessDrop, any, never), - (BIF::ChildProcessSpawn, any, int), - (BIF::ChildProcessStderrClose, nil, never), - (BIF::ChildProcessStderrRead, int, int), - (BIF::ChildProcessStdinClose, nil, never), - (BIF::ChildProcessStdinFlush, nil, int), - (BIF::ChildProcessStdinWriteBytes, int, int), - (BIF::ChildProcessStdinWriteString, int, int), - (BIF::ChildProcessStdoutClose, nil, never), - (BIF::ChildProcessStdoutRead, int, int), - (BIF::ChildProcessTryWait, int, int), - (BIF::ChildProcessWait, int, int), - (BIF::EnvArguments, string_array, never), - (BIF::EnvExecutable, string, int), - (BIF::EnvGet, string, never), - (BIF::EnvGetWorkingDirectory, string, int), - (BIF::EnvHomeDirectory, string, never), - (BIF::EnvPlatform, int, never), - (BIF::EnvSetWorkingDirectory, nil, int), - (BIF::EnvTempDirectory, string, never), - (BIF::EnvVariables, string_array, never), - (BIF::FFIFunctionAttach, any, never), - (BIF::FFIFunctionCall, any, never), - (BIF::FFIFunctionDrop, nil, never), - (BIF::FFILibraryDrop, nil, never), - (BIF::FFILibraryOpen, any, never), - (BIF::FFIPointerAddress, int, never), - (BIF::FFIPointerAttach, any, never), - (BIF::FFIPointerFromAddress, any, never), - (BIF::FFIPointerRead, any, never), - (BIF::FFIPointerWrite, nil, never), - (BIF::FFITypeAlignment, int, never), - (BIF::FFITypeSize, int, never), - (BIF::DirectoryCreate, nil, int), - (BIF::DirectoryCreateRecursive, nil, int), - (BIF::DirectoryList, string_array, int), - (BIF::DirectoryRemove, nil, int), - (BIF::DirectoryRemoveRecursive, nil, int), - (BIF::FileCopy, int, int), - (BIF::FileDrop, nil, never), - (BIF::FileFlush, nil, int), - (BIF::FileOpenAppendOnly, any, int), - (BIF::FileOpenReadAppend, any, int), - (BIF::FileOpenReadOnly, any, int), - (BIF::FileOpenReadWrite, any, int), - (BIF::FileOpenWriteOnly, any, int), - (BIF::FileRead, int, int), - (BIF::FileRemove, nil, int), - (BIF::FileSeek, int, int), - (BIF::FileSize, int, int), - (BIF::FileWriteBytes, int, int), - (BIF::FileWriteString, int, int), - (BIF::PathAccessedAt, float, int), - (BIF::PathCreatedAt, float, int), - (BIF::PathExists, boolean, never), - (BIF::PathIsDirectory, boolean, never), - (BIF::PathIsFile, boolean, never), - (BIF::PathModifiedAt, float, int), - (BIF::HasherDrop, nil, never), - (BIF::HasherNew, any, never), - (BIF::HasherToHash, int, never), - (BIF::HasherWriteInt, nil, never), - (BIF::ProcessStacktraceDrop, nil, never), - (BIF::ProcessCallFrameLine, int, never), - (BIF::ProcessCallFrameName, string, never), - (BIF::ProcessCallFramePath, string, never), - (BIF::ProcessStacktrace, any, never), - (BIF::RandomBytes, byte_array, never), - (BIF::RandomFloat, float, never), - (BIF::RandomFloatRange, float, never), - (BIF::RandomIntRange, int, never), - (BIF::RandomInt, int, never), - (BIF::SocketAcceptIp, any, int), - (BIF::SocketAcceptUnix, any, int), - (BIF::SocketAddressPairAddress, string, never), - (BIF::SocketAddressPairDrop, nil, never), - (BIF::SocketAddressPairPort, int, never), - (BIF::SocketAllocateIpv4, any, int), - (BIF::SocketAllocateIpv6, any, int), - (BIF::SocketAllocateUnix, any, int), - (BIF::SocketBind, nil, int), - (BIF::SocketConnect, nil, int), - (BIF::SocketDrop, nil, never), - (BIF::SocketGetBroadcast, boolean, int), - (BIF::SocketGetKeepalive, boolean, int), - (BIF::SocketGetLinger, float, int), - (BIF::SocketGetNodelay, boolean, int), - (BIF::SocketGetOnlyV6, boolean, int), - (BIF::SocketGetRecvSize, int, int), - (BIF::SocketGetReuseAddress, boolean, int), - (BIF::SocketGetReusePort, boolean, int), - (BIF::SocketGetSendSize, int, int), - (BIF::SocketGetTtl, int, int), - (BIF::SocketListen, nil, int), - (BIF::SocketLocalAddress, any, int), - (BIF::SocketPeerAddress, any, int), - (BIF::SocketRead, int, int), - (BIF::SocketReceiveFrom, any, int), - (BIF::SocketSendBytesTo, int, int), - (BIF::SocketSendStringTo, int, int), - (BIF::SocketSetBroadcast, nil, int), - (BIF::SocketSetKeepalive, nil, int), - (BIF::SocketSetLinger, nil, int), - (BIF::SocketSetNodelay, nil, int), - (BIF::SocketSetOnlyV6, nil, int), - (BIF::SocketSetRecvSize, nil, int), - (BIF::SocketSetReuseAddress, nil, int), - (BIF::SocketSetReusePort, nil, int), - (BIF::SocketSetSendSize, nil, int), - (BIF::SocketSetTtl, nil, int), - (BIF::SocketShutdownRead, nil, int), - (BIF::SocketShutdownReadWrite, nil, int), - (BIF::SocketShutdownWrite, nil, int), - (BIF::SocketTryClone, any, int), - (BIF::SocketWriteBytes, int, int), - (BIF::SocketWriteString, int, int), - (BIF::StderrFlush, nil, int), - (BIF::StderrWriteBytes, int, int), - (BIF::StderrWriteString, int, int), - (BIF::StdinRead, int, int), - (BIF::StdoutFlush, nil, int), - (BIF::StdoutWriteBytes, int, int), - (BIF::StdoutWriteString, int, int), - (BIF::TimeMonotonic, int, never), - (BIF::TimeSystem, float, never), - (BIF::TimeSystemOffset, int, never), - (BIF::StringToLower, string, never), - (BIF::StringToUpper, string, never), - (BIF::StringToByteArray, byte_array, never), - (BIF::StringToFloat, float, never), - (BIF::StringToInt, int, never), - (BIF::ByteArrayDrainToString, string, never), - (BIF::ByteArrayToString, string, never), - (BIF::CpuCores, int, never), - (BIF::StringCharacters, any, never), - (BIF::StringCharactersNext, any, never), - (BIF::StringCharactersDrop, nil, never), - (BIF::StringConcatArray, string, never), - (BIF::ArrayReserve, nil, never), - (BIF::ArrayCapacity, int, never), - (BIF::ProcessStacktraceLength, int, never), - (BIF::FloatToBits, int, never), - (BIF::FloatFromBits, float, never), - (BIF::RandomNew, any, never), - (BIF::RandomFromInt, any, never), - (BIF::RandomDrop, nil, never), - (BIF::StringSliceBytes, string, never), - (BIF::ByteArraySlice, byte_array, never), - (BIF::ByteArrayAppend, nil, never), - (BIF::ByteArrayCopyFrom, int, never), - (BIF::ByteArrayResize, nil, never), - ]; - - // Regular VM instructions exposed directly to the standard library. These - // are needed to implement core functionality, such as integer addition. - let instructions = vec![ - (Opcode::ArrayClear, nil), - (Opcode::ArrayGet, any), - (Opcode::ArrayLength, int), - (Opcode::ArrayPop, any), - (Opcode::ArrayPush, nil), - (Opcode::ArrayRemove, any), - (Opcode::ArraySet, any), - (Opcode::ArrayDrop, nil), - (Opcode::ByteArrayAllocate, byte_array), - (Opcode::ByteArrayClear, nil), - (Opcode::ByteArrayClone, byte_array), - (Opcode::ByteArrayEquals, boolean), - (Opcode::ByteArrayGet, int), - (Opcode::ByteArrayLength, int), - (Opcode::ByteArrayPop, int), - (Opcode::ByteArrayPush, nil), - (Opcode::ByteArrayRemove, int), - (Opcode::ByteArraySet, int), - (Opcode::ByteArrayDrop, nil), - (Opcode::Exit, never), - (Opcode::FloatAdd, float), - (Opcode::FloatCeil, float), - (Opcode::FloatClone, float), - (Opcode::FloatDiv, float), - (Opcode::FloatEq, boolean), - (Opcode::FloatFloor, float), - (Opcode::FloatGe, boolean), - (Opcode::FloatGt, boolean), - (Opcode::FloatIsInf, boolean), - (Opcode::FloatIsNan, boolean), - (Opcode::FloatLe, boolean), - (Opcode::FloatLt, boolean), - (Opcode::FloatMod, float), - (Opcode::FloatMul, float), - (Opcode::FloatRound, float), - (Opcode::FloatSub, float), - (Opcode::FloatToInt, int), - (Opcode::FloatToString, string), - (Opcode::FutureDrop, any), - (Opcode::GetNil, nil), - (Opcode::GetUndefined, any), - (Opcode::IntAdd, int), - (Opcode::IntBitAnd, int), - (Opcode::IntBitOr, int), - (Opcode::IntBitXor, int), - (Opcode::IntClone, int), - (Opcode::IntDiv, int), - (Opcode::IntEq, boolean), - (Opcode::IntGe, boolean), - (Opcode::IntGt, boolean), - (Opcode::IntLe, boolean), - (Opcode::IntLt, boolean), - (Opcode::IntMod, int), - (Opcode::IntMul, int), - (Opcode::IntShl, int), - (Opcode::IntShr, int), - (Opcode::IntSub, int), - (Opcode::IntPow, int), - (Opcode::IntToFloat, float), - (Opcode::IntToString, string), - (Opcode::ObjectEq, boolean), - (Opcode::Panic, never), - (Opcode::StringByte, int), - (Opcode::StringEq, boolean), - (Opcode::StringSize, int), - (Opcode::StringDrop, nil), - (Opcode::IsUndefined, boolean), - (Opcode::ProcessSuspend, nil), - (Opcode::FuturePoll, any), - (Opcode::IntBitNot, int), - (Opcode::IntRotateLeft, int), - (Opcode::IntRotateRight, int), - (Opcode::IntWrappingAdd, int), - (Opcode::IntWrappingSub, int), - (Opcode::IntWrappingMul, int), - (Opcode::IntUnsignedShr, int), - ]; - - let macros = vec![ - (CompilerMacro::FutureGet, any, any), - (CompilerMacro::FutureGetFor, any, any), - (CompilerMacro::StringClone, string, never), - (CompilerMacro::Moved, any, never), - (CompilerMacro::PanicThrown, never, never), - (CompilerMacro::Strings, string, never), - ]; - - for (id, returns, throws) in vm.into_iter() { - let kind = BuiltinFunctionKind::Function(id); - - BuiltinFunction::alloc(db, kind, id.name(), returns, throws); - } - - for (opcode, returns) in instructions.into_iter() { - let kind = BuiltinFunctionKind::Instruction(opcode); - - BuiltinFunction::alloc(db, kind, opcode.name(), returns, never); - } - - for (mac, returns, throws) in macros.into_iter() { - let kind = BuiltinFunctionKind::Macro(mac); - - BuiltinFunction::alloc(db, kind, mac.name(), returns, throws); - } - - true -} diff --git a/compiler/src/type_check/mod.rs b/compiler/src/type_check/mod.rs index 7791061c4..9351a266b 100644 --- a/compiler/src/type_check/mod.rs +++ b/compiler/src/type_check/mod.rs @@ -3,10 +3,12 @@ use crate::diagnostics::DiagnosticId; use crate::hir; use crate::state::State; use std::path::PathBuf; +use types::check::TypeChecker; +use types::format::format_type; use types::{ - format_type, Block, ClassId, ClassInstance, Closure, Database, MethodId, - ModuleId, Symbol, TraitId, TraitInstance, TypeArguments, TypeBounds, - TypeContext, TypeId, TypeParameterId, TypeRef, + Block, ClassId, ClassInstance, Closure, Database, MethodId, ModuleId, + Symbol, TraitId, TraitInstance, TypeArguments, TypeBounds, TypeId, + TypeParameter, TypeParameterId, TypeRef, }; pub(crate) mod define_types; @@ -67,20 +69,33 @@ impl<'a> TypeScope<'a> { } pub(crate) fn symbol(&self, db: &Database, name: &str) -> Option { - self.method - .and_then(|id| id.named_type(db, name)) - .or_else(|| self.self_type.named_type(db, name)) - .or_else(|| self.module.symbol(db, name)) + if let Some(id) = self.method { + if let Some(sym) = id.named_type(db, name) { + return Some(sym); + } + + match self.self_type.named_type(db, name) { + Some(Symbol::TypeParameter(pid)) => { + if let Some(bound) = id.bounds(db).get(pid) { + Some(Symbol::TypeParameter(bound)) + } else { + Some(Symbol::TypeParameter(pid)) + } + } + None => self.module.symbol(db, name), + sym => sym, + } + } else { + self.self_type + .named_type(db, name) + .or_else(|| self.module.symbol(db, name)) + } } } /// Rules to apply when defining and checking the types of type signatures. #[derive(Copy, Clone)] pub(crate) struct Rules { - /// When set to `true`, allows the use of the `Self` type in type - /// signatures. - pub(crate) allow_self_type: bool, - /// When set to `true`, type parameters are defined as owned values; rather /// than allowing both owned values and references. pub(crate) type_parameters_as_owned: bool, @@ -95,7 +110,6 @@ pub(crate) struct Rules { impl Default for Rules { fn default() -> Self { Self { - allow_self_type: true, type_parameters_as_owned: false, type_parameters_as_rigid: false, allow_private_types: true, @@ -216,7 +230,7 @@ impl<'a> DefineTypeSignature<'a> { node.resolved_type = if let Some(symbol) = symbol { if !self.rules.allow_private_types && symbol.is_private(self.db()) { self.state.diagnostics.error( - DiagnosticId::PrivateSymbol, + DiagnosticId::InvalidSymbol, format!( "'{}' is private, but private types can't be used here", name @@ -268,12 +282,6 @@ impl<'a> DefineTypeSignature<'a> { return TypeRef::Error; } } - "Self" => match kind { - RefKind::Owned => TypeRef::OwnedSelf, - RefKind::Mut => TypeRef::MutSelf, - RefKind::Uni => TypeRef::UniSelf, - _ => TypeRef::RefSelf, - }, "Any" => match kind { RefKind::Owned => TypeRef::Any, RefKind::Ref | RefKind::Mut => TypeRef::RefAny, @@ -320,7 +328,7 @@ impl<'a> DefineTypeSignature<'a> { let types = node.values.iter_mut().map(|n| self.define_type(n)).collect(); - let ins = TypeId::ClassInstance(ClassInstance::generic_with_types( + let ins = TypeId::ClassInstance(ClassInstance::with_types( self.db_mut(), class, types, @@ -395,7 +403,22 @@ impl<'a> DefineTypeSignature<'a> { RefKind::Owned => TypeRef::Infer(type_id), RefKind::Uni => TypeRef::Uni(type_id), RefKind::Ref => TypeRef::Ref(type_id), - RefKind::Mut => TypeRef::Mut(type_id), + RefKind::Mut => { + if !param_id.is_mutable(self.db()) { + self.state.diagnostics.error( + DiagnosticId::InvalidType, + format!( + "The type 'mut {name}' is invalid, as '{name}' \ + might be immutable at runtime", + name = id.name(self.db()), + ), + self.file(), + node.location.clone(), + ); + } + + TypeRef::Mut(type_id) + } } } @@ -412,14 +435,6 @@ impl<'a> DefineTypeSignature<'a> { block.new_anonymous_argument(self.db_mut(), typ); } - let throw_type = if let Some(type_node) = node.throw_type.as_mut() { - self.define_type(type_node) - } else { - TypeRef::Never - }; - - block.set_throw_type(self.db_mut(), throw_type); - let return_type = if let Some(type_node) = node.return_type.as_mut() { self.define_type(type_node) } else { @@ -497,18 +512,11 @@ impl<'a> DefineTypeSignature<'a> { pub(crate) struct CheckTypeSignature<'a> { state: &'a mut State, module: ModuleId, - self_type: TypeId, - rules: Rules, } impl<'a> CheckTypeSignature<'a> { - pub(crate) fn new( - state: &'a mut State, - module: ModuleId, - self_type: TypeId, - rules: Rules, - ) -> Self { - Self { state, module, self_type, rules } + pub(crate) fn new(state: &'a mut State, module: ModuleId) -> Self { + Self { state, module } } pub(crate) fn check(&mut self, node: &hir::Type) { @@ -536,16 +544,6 @@ impl<'a> CheckTypeSignature<'a> { } _ => {} }, - TypeRef::OwnedSelf | TypeRef::RefSelf - if !self.rules.allow_self_type => - { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - "Self types can't be used here", - self.file(), - node.location.clone(), - ); - } _ => {} } } @@ -635,8 +633,6 @@ impl<'a> CheckTypeSignature<'a> { arguments: Vec<(TypeParameterId, TypeRef)>, allow_any: bool, ) { - let mut context = TypeContext::new(self.self_type); - for ((param, arg), node) in arguments.into_iter().zip(node.arguments.iter()) { @@ -650,11 +646,9 @@ impl<'a> CheckTypeSignature<'a> { ); } - if !arg.is_compatible_with_type_parameter( - self.db_mut(), - param, - &mut context, - ) { + let exp = TypeRef::Infer(TypeId::TypeParameter(param)); + + if !TypeChecker::check(self.db(), arg, exp) { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( @@ -676,10 +670,6 @@ impl<'a> CheckTypeSignature<'a> { self.check(node); } - if let Some(node) = node.throw_type.as_ref() { - self.check(node); - } - if let Some(node) = node.return_type.as_ref() { self.check(node); } @@ -698,10 +688,6 @@ impl<'a> CheckTypeSignature<'a> { fn db(&self) -> &Database { &self.state.db } - - fn db_mut(&mut self) -> &mut Database { - &mut self.state.db - } } /// A visitor that combines `DefineTypeSignature` and `CheckTypeSignature`. @@ -731,13 +717,7 @@ impl<'a> DefineAndCheckTypeSignature<'a> { ) .define_type(node); - CheckTypeSignature::new( - self.state, - self.module, - self.scope.self_type, - self.rules, - ) - .check(node); + CheckTypeSignature::new(self.state, self.module).check(node); typ } @@ -755,19 +735,76 @@ impl<'a> DefineAndCheckTypeSignature<'a> { .as_trait_instance(node); if ins.is_some() { - CheckTypeSignature::new( - self.state, - self.module, - self.scope.self_type, - self.rules, - ) - .check_type_name(node); + CheckTypeSignature::new(self.state, self.module) + .check_type_name(node); } ins } } +pub(crate) fn define_type_bounds( + state: &mut State, + module: ModuleId, + class: ClassId, + nodes: &mut [hir::TypeBound], +) -> TypeBounds { + let mut bounds = TypeBounds::new(); + + for bound in nodes { + let name = &bound.name.name; + let param = if let Some(id) = class.type_parameter(&state.db, name) { + id + } else { + state.diagnostics.undefined_symbol( + name, + module.file(&state.db), + bound.name.location.clone(), + ); + + continue; + }; + + if bounds.get(param).is_some() { + state.diagnostics.error( + DiagnosticId::DuplicateSymbol, + format!( + "Bounds are already defined for type parameter '{}'", + name + ), + module.file(&state.db), + bound.location.clone(), + ); + + continue; + } + + let mut reqs = param.requirements(&state.db); + let new_param = TypeParameter::alloc(&mut state.db, name.clone()); + + for req in &mut bound.requirements { + let rules = Rules::default(); + let scope = TypeScope::new(module, TypeId::Class(class), None); + let mut definer = + DefineTypeSignature::new(state, module, &scope, rules); + + if let Some(ins) = definer.as_trait_instance(req) { + reqs.push(ins); + } + } + + if bound.mutable { + new_param.set_mutable(&mut state.db); + } + + new_param.set_original(&mut state.db, param); + new_param.add_requirements(&mut state.db, reqs); + bounds.set(param, new_param); + } + + bounds +} + #[cfg(test)] mod tests { use super::*; @@ -1050,7 +1087,7 @@ mod tests { let error = state.diagnostics.iter().next().unwrap(); - assert_eq!(error.id(), DiagnosticId::PrivateSymbol); + assert_eq!(error.id(), DiagnosticId::InvalidSymbol); } #[test] @@ -1112,7 +1149,6 @@ mod tests { let module = module_type(&mut state, "foo"); let mut node = hir::Type::Closure(Box::new(hir::ClosureType { arguments: Vec::new(), - throw_type: None, return_type: None, location: cols(1, 1), resolved_type: TypeRef::Unknown, @@ -1176,8 +1212,7 @@ mod tests { DefineTypeSignature::new(&mut state, module, &scope, rules) .define_type(&mut node); - CheckTypeSignature::new(&mut state, module, instance_a, rules) - .check(&node); + CheckTypeSignature::new(&mut state, module).check(&node); assert!(state.diagnostics.has_errors()); @@ -1253,8 +1288,7 @@ mod tests { DefineTypeSignature::new(&mut state, module, &scope, rules) .define_type(&mut node); - CheckTypeSignature::new(&mut state, module, instance_a, rules) - .check(&node); + CheckTypeSignature::new(&mut state, module).check(&node); assert!(state.diagnostics.has_errors()); diff --git a/deny.toml b/deny.toml index 74e4d8346..994570708 100644 --- a/deny.toml +++ b/deny.toml @@ -14,9 +14,8 @@ allow = [ "MIT", "Apache-2.0", "MPL-2.0", - "ISC", - "CC0-1.0", "BSD-3-Clause", + "Unicode-DFS-2016", ] copyleft = "deny" allow-osi-fsf-free = "neither" diff --git a/docs/source/getting-started/installation.md b/docs/source/getting-started/installation.md index b31afacaa..37bd77ef5 100644 --- a/docs/source/getting-started/installation.md +++ b/docs/source/getting-started/installation.md @@ -15,7 +15,7 @@ Unix compatibility layer such as [MSYS2][msys2]. - A 64-bits little-endian platform - A CPU with AES-NI support -- Rust 1.62 or newer +- Rust 1.63 or newer Inko's package manager (ipm) also required Git to be installed, and the `git` executable to be available in your PATH. @@ -152,15 +152,15 @@ and the `ipm` executable in `target/release/ipm` (or `target/debug/ipm` for debug builds). By default Inko uses the standard library provided in the Git repository, -located in `libstd/src`. If you wish to use a different directory, set the -`INKO_LIBSTD` environment variable to a path of your choosing. For example: +located in `std/src`. If you wish to use a different directory, set the +`INKO_STD` environment variable to a path of your choosing. For example: ```bash -INKO_LIBSTD=/tmp/libstd/src cargo build --release +INKO_STD=/tmp/std/src cargo build --release ``` This builds Inko such that it uses the standard library located at -`/tmp/libstd/src`. +`/tmp/std/src`. When building from source you can set certain feature flags to customise the installation. These flags are specified like so: diff --git a/docs/source/getting-started/ivm.md b/docs/source/getting-started/ivm.md index 06e87f2b8..44510654d 100644 --- a/docs/source/getting-started/ivm.md +++ b/docs/source/getting-started/ivm.md @@ -7,7 +7,7 @@ additional system dependencies. ## Installing -ivm itself only requires Rust 1.62 or newer, but to build Inko itself you'll +ivm itself only requires Rust 1.63 or newer, but to build Inko itself you'll need to also meet the requirements listed in the [installation guide](installation.md). diff --git a/docs/source/getting-started/pattern-matching.md b/docs/source/getting-started/pattern-matching.md index d026cba20..3ca9651e6 100644 --- a/docs/source/getting-started/pattern-matching.md +++ b/docs/source/getting-started/pattern-matching.md @@ -290,13 +290,13 @@ For references we just drop the reference before entering the pattern body: ```inko -match values[4] { +match values.get(4) { case Some(42) -> { - # This is valid because the `ref Option[Int]` returned by `values[4]` is - # dropped before we enter this body. If we didn't the line below would - # panic, because we'd try to drop the old value of `values[4]` while a + # This is valid because the `ref Option[Int]` returned by `values.get(4)` is + # dropped before we enter this body. If we didn't, the line below would + # panic, because we'd try to drop the old value of `values.get(4)` while a # reference to it still exists. - values[4] = Option.Some(0) + values.set(4, Option.Some(0)) } case _ -> nil } diff --git a/docs/source/getting-started/syntax.md b/docs/source/getting-started/syntax.md index b424884b9..9647a3153 100644 --- a/docs/source/getting-started/syntax.md +++ b/docs/source/getting-started/syntax.md @@ -734,11 +734,16 @@ async { foo.bar } ### Indexing -Indexing is done using `[]` and `[]=` like so: +Inko doesn't have dedicated indexing syntax such as `array[index]` or +`array[index] = value`, instead you'd use the methods `get`, `get_mut`, and +`set` like so: ```inko -numbers[0] -numbers[0] = 42 +let numbers = [10, 20] + +numbers.get(1) # => 20 +numbers.set(1, 30) +numbers.get(1) # => 30 ``` ### Type casts diff --git a/docs/source/guides/contributing.md b/docs/source/guides/contributing.md index 5ce155fba..b198f5d07 100644 --- a/docs/source/guides/contributing.md +++ b/docs/source/guides/contributing.md @@ -90,9 +90,9 @@ For contributing changes to Inko source code, please follow [the Inko style guide](style-guide.md). We don't have any tools yet to enforce the style guide, so this is done manually during code review. -Unit tests for Inko are located in `libstd/test` and are named `test_X.inko`, +Unit tests for Inko are located in `std/test` and are named `test_X.inko`, where `X` is the module to test. For example, the tests for `std::string` are -located in `libstd/test/std/test_string.inko`. Test modules are structured as +located in `std/test/std/test_string.inko`. Test modules are structured as follows: ```inko @@ -106,7 +106,7 @@ fn pub tests(t: mut Tests) { ``` When adding a new test module, follow this structure then add it to -`libstd/test/main.inko`, following the same style as the existing tests. +`std/test/main.inko`, following the same style as the existing tests. To run the stdlib tests: diff --git a/docs/source/guides/operators.md b/docs/source/guides/operators.md index 48324cb17..c18765d1e 100644 --- a/docs/source/guides/operators.md +++ b/docs/source/guides/operators.md @@ -18,7 +18,7 @@ class Rational { let @denominator: Int } -impl Add[Rational] for Rational { +impl Add[Rational, Rational] for Rational { fn pub +(other: ref Rational) -> Rational { Rational { @numerator = @numerator + other.numerator, diff --git a/docs/source/guides/testing.md b/docs/source/guides/testing.md index 929aae7af..0be7ba820 100644 --- a/docs/source/guides/testing.md +++ b/docs/source/guides/testing.md @@ -38,7 +38,7 @@ mirror the module hierarchy of the module they are testing. For example, the tests for the standard library are organised as follows: ``` -libstd/test/ +std/test/ ├── main.inko └── std ├── fs diff --git a/docs/source/internals/vm.md b/docs/source/internals/vm.md index 5c06ce284..1a1394ac5 100644 --- a/docs/source/internals/vm.md +++ b/docs/source/internals/vm.md @@ -226,9 +226,8 @@ booleans. This is achieved using pointer tagging. If a pointer has its lowest bit set to `1` it means the pointer is an immediate value, instead of pointing to a heap allocated object. -If the lowest two bits are set to `1` (so `xx11`), the pointer is a tagged -integer capable of storing integers up to 62 bits. 63 and 64 bits integers are -heap allocated. +If the lowest bit is set to `1` (so `xx1`), the pointer is a tagged integer +capable of storing integers up to 63 bits. 64 bits integers are heap allocated. If the lowest two bits of a pointer are set to `10`, it means the pointer is a pointer to a permanently allocated heap object, such as a string literal. @@ -241,14 +240,10 @@ unset): | Value | Pattern |:-----------------|:----------- -| Heap object | `xxxx x000` -| Permanent object | `xxxx xx10` -| Reference | `xxxx x1x0` -| Tagged integer | `xxxx xx11` -| `true` | `0000 1101` -| `false` | `0000 0101` -| `nil` | `0000 0001` -| `undefined` | `0000 1001` +| Heap object | `000` +| Permanent object | `x10` +| Reference | `1x0` +| Tagged integer | `xx1` For more information, refer to `vm/src/mem.rs`, which implements most of the memory management logic of the VM. diff --git a/inko/Cargo.toml b/inko/Cargo.toml index c94bfaef3..2f7cb0454 100644 --- a/inko/Cargo.toml +++ b/inko/Cargo.toml @@ -8,10 +8,9 @@ license = "MPL-2.0" [features] default = [] jemalloc = ["jemallocator"] -libffi-system = ["vm/libffi-system"] [dependencies] getopts = "^0.2" -jemallocator = { version = "^0.3", optional = true } -vm = { path = "../vm" } +jemallocator = { version = "^0.5", optional = true } compiler = { path = "../compiler" } +blake2 = "^0.10" diff --git a/inko/src/command.rs b/inko/src/command.rs index 7cfbad94a..6f4f79108 100644 --- a/inko/src/command.rs +++ b/inko/src/command.rs @@ -1,5 +1,7 @@ pub(crate) mod build; pub(crate) mod check; pub(crate) mod main; +pub(crate) mod pkg; +pub(crate) mod print; pub(crate) mod run; pub(crate) mod test; diff --git a/inko/src/command/build.rs b/inko/src/command/build.rs index b417e82cb..12a72a2fc 100644 --- a/inko/src/command/build.rs +++ b/inko/src/command/build.rs @@ -1,8 +1,7 @@ -//! Command for building an Inko bytecode image from a source file. use crate::error::Error; use crate::options::print_usage; use compiler::compiler::{CompileError, Compiler}; -use compiler::config::Config as CompilerConfig; +use compiler::config::{Config, Mode, Output}; use getopts::Options; use std::path::PathBuf; @@ -12,14 +11,13 @@ Compile a source file and its dependencies into a bytecode file. Examples: - inko build # Compile src/main.inko - inko build hello.inko # Compile the file hello.inko"; + inko build # Compile src/main.inko + inko build hello.inko # Compile the file hello.inko"; -pub fn run(arguments: &[String]) -> Result { +pub(crate) fn run(arguments: &[String]) -> Result { let mut options = Options::new(); - options.optflag("h", "help", "Shows this help message"); - + options.optflag("h", "help", "Show this help message"); options.optopt( "f", "format", @@ -27,6 +25,19 @@ pub fn run(arguments: &[String]) -> Result { "FORMAT", ); + options.optopt( + "t", + "target", + "The target platform to compile for", + "TARGET", + ); + + options.optflag( + "", + "release", + "Enable a release build instead of a debug build", + ); + options.optopt( "o", "output", @@ -48,10 +59,18 @@ pub fn run(arguments: &[String]) -> Result { return Ok(0); } - let mut config = CompilerConfig::default(); + let mut config = Config::default(); + + if let Some(val) = matches.opt_str("f") { + config.set_presenter(&val)?; + } + + if let Some(val) = matches.opt_str("t") { + config.set_target(&val)?; + } - if let Some(format) = matches.opt_str("f") { - config.set_presenter(&format)?; + if matches.opt_present("release") { + config.mode = Mode::Release; } for path in matches.opt_strs("i") { @@ -59,12 +78,12 @@ pub fn run(arguments: &[String]) -> Result { } if let Some(path) = matches.opt_str("o") { - config.output = Some(PathBuf::from(path)); + config.output = Output::Path(PathBuf::from(path)); } let mut compiler = Compiler::new(config); let file = matches.free.get(0).map(PathBuf::from); - let result = compiler.compile_to_file(file); + let result = compiler.run(file); compiler.print_diagnostics(); diff --git a/inko/src/command/check.rs b/inko/src/command/check.rs index 3c32e8c49..f8ade4368 100644 --- a/inko/src/command/check.rs +++ b/inko/src/command/check.rs @@ -1,4 +1,3 @@ -//! Command for type-checking Inko source code. use crate::error::Error; use crate::options::print_usage; use compiler::compiler::{CompileError, Compiler}; @@ -16,10 +15,10 @@ Examples: inko check hello.inko # Check the file hello.inko"; /// Type-checks Inko source code. -pub fn run(arguments: &[String]) -> Result { +pub(crate) fn run(arguments: &[String]) -> Result { let mut options = Options::new(); - options.optflag("h", "help", "Shows this help message"); + options.optflag("h", "help", "Show this help message"); options.optopt( "f", "format", diff --git a/inko/src/command/main.rs b/inko/src/command/main.rs index 2e2aa20f8..a72f4a83f 100644 --- a/inko/src/command/main.rs +++ b/inko/src/command/main.rs @@ -1,6 +1,7 @@ -//! The main entry point for the CLI. use crate::command::build; use crate::command::check; +use crate::command::pkg; +use crate::command::print; use crate::command::run; use crate::command::test; use crate::error::Error; @@ -12,12 +13,11 @@ const USAGE: &str = "Usage: inko [OPTIONS] [COMMAND | FILE] Commands: - run Compiles and runs FILE - build Compiles FILE - test Runs Inko unit tests - -If no explicit command is given, the run command is implied. Each command takes -its own set of options. + run Compile and run Inko source code directly + build Compile Inko source code + test Run Inko unit tests + print Print compiler details to STDOUT + pkg Managing of Inko packages Examples: @@ -25,16 +25,15 @@ Examples: inko run hello.inko # Same inko build hello.inko # Compiles the file into a bytecode image inko check hello.inko # Checks hello.inko for errors - inko run --help # Prints the help message for the run command"; + inko run --help # Print the help message for the run command"; -/// Runs the default CLI command. -pub fn run() -> Result { +pub(crate) fn run() -> Result { let args: Vec = env::args().collect(); let mut options = Options::new(); options.parsing_style(ParsingStyle::StopAtFirstFree); - options.optflag("h", "help", "Shows this help message"); - options.optflag("v", "version", "Prints the version number"); + options.optflag("h", "help", "Show this help message"); + options.optflag("v", "version", "Print the version number"); let matches = options.parse(&args[1..])?; @@ -44,10 +43,7 @@ pub fn run() -> Result { } if matches.opt_present("v") { - println!("inko version {}\n", env!("CARGO_PKG_VERSION")); - println!("AES-NI: {}", cfg!(target_feature = "aes")); - println!("jemalloc: {}", cfg!(feature = "jemalloc")); - + println!("inko {}", env!("CARGO_PKG_VERSION")); return Ok(0); } @@ -56,9 +52,14 @@ pub fn run() -> Result { Some("build") => build::run(&matches.free[1..]), Some("check") => check::run(&matches.free[1..]), Some("test") => test::run(&matches.free[1..]), - Some(_) => run::run(&matches.free), - None => Err(Error::generic( - "You must specify a command or input file to run".to_string(), - )), + Some("print") => print::run(&matches.free[1..]), + Some("pkg") => pkg::run(&matches.free[1..]), + Some(cmd) => { + Err(Error::generic(format!("The command '{}' is invalid", cmd))) + } + None => { + print_usage(&options, USAGE); + Ok(0) + } } } diff --git a/inko/src/command/pkg.rs b/inko/src/command/pkg.rs new file mode 100644 index 000000000..c1af1693f --- /dev/null +++ b/inko/src/command/pkg.rs @@ -0,0 +1,54 @@ +mod add; +mod init; +mod remove; +mod sync; +mod update; + +use crate::error::Error; +use crate::options::print_usage; +use getopts::Options; + +const USAGE: &str = "inko pkg [OPTIONS] [COMMAND] + +Package and dependency management for Inko. + +Commands: + + init Create a new package + add Add or update a dependency + remove Remove a dependency + sync Download and install dependencies + update Update all dependencies to the latest version + +Examples: + + inko pkg init + inko pkg add github.com/hello/world 1.2.3"; + +pub(crate) fn run(arguments: &[String]) -> Result { + let mut options = Options::new(); + + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(arguments)?; + + if matches.opt_present("h") { + print_usage(&options, USAGE); + return Ok(0); + } + + match matches.free.get(0).map(|s| s.as_str()) { + Some("init") => init::run(&matches.free[1..]), + Some("add") => add::run(&matches.free[1..]), + Some("remove") => remove::run(&matches.free[1..]), + Some("sync") => sync::run(&matches.free[1..]), + Some("update") => update::run(&matches.free[1..]), + Some(cmd) => { + Err(Error::generic(format!("The command {:?} is invalid", cmd))) + } + None => { + print_usage(&options, USAGE); + Ok(0) + } + } +} diff --git a/inko/src/command/pkg/add.rs b/inko/src/command/pkg/add.rs new file mode 100644 index 000000000..c78c1803a --- /dev/null +++ b/inko/src/command/pkg/add.rs @@ -0,0 +1,85 @@ +use crate::error::Error; +use crate::options::print_usage; +use crate::pkg::git::Repository; +use crate::pkg::manifest::{Checksum, Manifest, Url, MANIFEST_FILE}; +use crate::pkg::util::data_dir; +use crate::pkg::version::Version; +use getopts::Options; + +const USAGE: &str = "inko pkg add [OPTIONS] [URL] [VERSION] + +Add a dependency to the current project. + +This command merely adds the dependency to the manifest. To download it along +with its dependencies, run `inko pkg sync`. + +Examples: + + inko pkg add github.com/inko-lang/example 1.2.3"; + +pub(crate) fn run(args: &[String]) -> Result { + let mut options = Options::new(); + + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(args)?; + + if matches.opt_present("h") || matches.free.is_empty() { + print_usage(&options, USAGE); + return Ok(0); + } + + if matches.free.len() != 2 { + return Err(Error::generic( + "You must specify a package and version to add".to_string(), + )); + } + + let url = matches.free.get(0).and_then(|uri| Url::parse(uri)).ok_or_else( + || Error::generic("The package URL is invalid".to_string()), + )?; + + let version = + matches.free.get(1).and_then(|uri| Version::parse(uri)).ok_or_else( + || Error::generic("The package version is invalid".to_string()), + )?; + + let dir = data_dir()?.join(url.directory_name()); + let (mut repo, fetch) = if dir.is_dir() { + (Repository::open(&dir)?, true) + } else { + (Repository::clone(&url.to_string(), &dir)?, false) + }; + + if fetch { + repo.fetch()?; + } + + let tag_name = version.tag_name(); + let tag = if let Some(tag) = repo.tag(&tag_name) { + Some(tag) + } else if fetch { + println!("Updating {}", url); + repo.fetch()?; + repo.tag(&tag_name) + } else { + None + }; + + let hash = tag.map(|t| t.target).ok_or_else(|| { + Error::generic(format!("Version {} doesn't exist", version)) + })?; + + let checksum = Checksum::new(hash); + let mut manifest = Manifest::load(&MANIFEST_FILE)?; + + if let Some(existing) = manifest.find_dependency(&url) { + existing.version = version; + existing.checksum = checksum; + } else { + manifest.add_dependency(url, version, checksum); + } + + manifest.save(&MANIFEST_FILE)?; + Ok(0) +} diff --git a/inko/src/command/pkg/init.rs b/inko/src/command/pkg/init.rs new file mode 100644 index 000000000..d3615c965 --- /dev/null +++ b/inko/src/command/pkg/init.rs @@ -0,0 +1,62 @@ +use crate::error::Error; +use crate::options::print_usage; +use crate::pkg::manifest::MANIFEST_FILE; +use getopts::Options; +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +const USAGE: &str = "inko pkg init [OPTIONS] [DIR] + +Create a new package in an existing directory + +Examples: + + inko pkg init + inko pkg init example/"; + +const TEMPLATE: &str = "\ +# This file contains your project's dependencies. For more information, refer +# to https://docs.inko-lang.org/manual/latest/getting-started/modules/"; + +pub(crate) fn run(args: &[String]) -> Result { + let mut options = Options::new(); + + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(args)?; + + if matches.opt_present("h") { + print_usage(&options, USAGE); + return Ok(0); + } + + let dir = matches.free.get(0).map(PathBuf::from).unwrap_or_else(|| { + env::current_dir().unwrap_or_else(|_| PathBuf::from(".")) + }); + + if !dir.is_dir() { + return Err(Error::generic(format!( + "The directory {:?} doesn't exist", + dir + ))); + } + + let path = dir.join(MANIFEST_FILE); + + if path.exists() { + return Ok(0); + } + + File::create(&path) + .and_then(|mut f| f.write_all(TEMPLATE.as_bytes())) + .map(|_| 0) + .map_err(|err| { + Error::generic(format!( + "Failed to create {}: {}", + path.display(), + err + )) + }) +} diff --git a/inko/src/command/pkg/remove.rs b/inko/src/command/pkg/remove.rs new file mode 100644 index 000000000..6f611025c --- /dev/null +++ b/inko/src/command/pkg/remove.rs @@ -0,0 +1,41 @@ +use crate::error::Error; +use crate::options::print_usage; +use crate::pkg::manifest::{Manifest, Url, MANIFEST_FILE}; +use getopts::Options; + +const USAGE: &str = "inko pkg remove [OPTIONS] [URI] + +Remove a dependency from the current project. + +This command merely removes the dependency from the manifest. To also remove its +files from your project, along with any unnecessary dependencies, run +`inko pkg sync`. + +Examples: + + inko pkg remove github.com/inko-lang/example"; + +pub(crate) fn run(args: &[String]) -> Result { + let mut options = Options::new(); + + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(args)?; + + if matches.opt_present("h") || matches.free.is_empty() { + print_usage(&options, USAGE); + return Ok(0); + } + + let url = matches + .free + .get(0) + .and_then(|uri| Url::parse(uri)) + .ok_or_else(|| "The package URL is invalid".to_string())?; + + let mut manifest = Manifest::load(&MANIFEST_FILE)?; + + manifest.remove_dependency(&url); + manifest.save(&MANIFEST_FILE)?; + Ok(0) +} diff --git a/ipm/src/command/sync.rs b/inko/src/command/pkg/sync.rs similarity index 81% rename from ipm/src/command/sync.rs rename to inko/src/command/pkg/sync.rs index b230da5b0..5c43632e3 100644 --- a/ipm/src/command/sync.rs +++ b/inko/src/command/pkg/sync.rs @@ -1,8 +1,9 @@ use crate::error::Error; -use crate::git::Repository; -use crate::manifest::{Dependency, Manifest, Url, MANIFEST_FILE}; -use crate::util::{cp_r, data_dir, usage, DEP_DIR}; -use crate::version::{select, Version}; +use crate::options::print_usage; +use crate::pkg::git::Repository; +use crate::pkg::manifest::{Dependency, Manifest, Url, MANIFEST_FILE}; +use crate::pkg::util::{cp_r, data_dir, DEP_DIR}; +use crate::pkg::version::{select, Version}; use getopts::Options; use std::collections::HashMap; use std::collections::HashSet; @@ -16,14 +17,14 @@ const SRC_DIR: &str = "src"; /// The dependant string to use for the root project. const ROOT_DEPENDANT: &str = "Your project"; -const USAGE: &str = "ipm sync [OPTIONS] +const USAGE: &str = "inko pkg sync [OPTIONS] -Installs all necessary dependencies into your project, and removes dependencies -no longer in use. +Install all necessary dependencies, and remove dependencies that are no longer +needed. Examples: - ipm sync"; + inko pkg sync"; #[derive(Clone)] enum Dependant { @@ -37,7 +38,7 @@ struct Package { dependency: Dependency, } -pub(crate) fn run(args: &[String]) -> Result<(), Error> { +pub(crate) fn run(args: &[String]) -> Result { let mut options = Options::new(); options.optflag("h", "help", "Show this help message"); @@ -45,19 +46,20 @@ pub(crate) fn run(args: &[String]) -> Result<(), Error> { let matches = options.parse(args)?; if matches.opt_present("h") { - usage(&options, USAGE); - return Ok(()); + print_usage(&options, USAGE); + return Ok(0); } println!("Updating package cache"); let packages = download_packages()?; let versions = select_versions(&packages)?; - let dep_dir = PathBuf::from(DEP_DIR); + let dep_dir = PathBuf::from(DEP_DIR).join(SRC_DIR); remove_dependencies(&dep_dir)?; println!("Installing"); - install_packages(packages, versions, &dep_dir) + install_packages(packages, versions, &dep_dir)?; + Ok(0) } fn download_packages() -> Result, Error> { @@ -112,7 +114,6 @@ fn download_dependency( Some(tag) } else if fetch { println!(" Updating {}", dependency.url); - repo.fetch()?; repo.tag(&tag_name) } else { @@ -120,21 +121,21 @@ fn download_dependency( }; let tag = tag.ok_or_else(|| { - error!( + format!( "The version {} of package {} doesn't exist", dependency.version, url ) })?; repo.checkout(&tag.target).map_err(|err| { - error!( + format!( "Failed to checkout tag {} of package {}: {}", tag_name, url, err ) })?; if tag.target != dependency.checksum.to_string() { - fail!( + format!( "The checksum of {} version {} didn't match. The checksum that is expected is: @@ -153,10 +154,7 @@ maintainer to ensure this is expected. DO NOT PROCEED BLINDLY, as you may be including unexpected or even malicious changes.", - url, - dependency.version, - dependency.checksum, - tag.target + url, dependency.version, dependency.checksum, tag.target ); } @@ -170,19 +168,19 @@ changes.", } } -fn select_versions(packages: &[Package]) -> Result, Error> { - match select(packages.iter().map(|p| &p.dependency)) { - Ok(versions) => Ok(versions), - Err(url) => Err(conflicting_versions_error(url, packages)), - } +fn select_versions( + packages: &[Package], +) -> Result, String> { + select(packages.iter().map(|p| &p.dependency)) + .map_err(|url| conflicting_versions_error(url, packages)) } -fn remove_dependencies(directory: &Path) -> Result<(), Error> { +fn remove_dependencies(directory: &Path) -> Result<(), String> { if directory.is_dir() { println!("Removing existing ./{}", DEP_DIR); - remove_dir_all(&directory).map_err(|err| { - error!("Failed to remove the existing ./{}: {}", DEP_DIR, err) + remove_dir_all(directory).map_err(|err| { + format!("Failed to remove the existing ./{}: {}", DEP_DIR, err) })?; } @@ -193,7 +191,7 @@ fn install_packages( packages: Vec, versions: Vec<(Url, Version)>, directory: &Path, -) -> Result<(), Error> { +) -> Result<(), String> { let repos = packages .into_iter() .map(|pkg| (pkg.dependency.url, pkg.repository)) @@ -207,7 +205,7 @@ fn install_packages( let tag = repo.tag(&tag_name).unwrap(); repo.checkout(&tag.target).map_err(|err| { - error!("Failed to check out {}: {}", tag_name, err) + format!("Failed to check out {}: {}", tag_name, err) })?; cp_r(&repo.path.join(SRC_DIR), directory)?; @@ -216,7 +214,7 @@ fn install_packages( Ok(()) } -fn conflicting_versions_error(url: Url, packages: &[Package]) -> Error { +fn conflicting_versions_error(url: Url, packages: &[Package]) -> String { let reqs: Vec<_> = packages .iter() .filter_map(|pkg| { @@ -238,7 +236,7 @@ fn conflicting_versions_error(url: Url, packages: &[Package]) -> Error { }) .collect(); - error!( + format!( "\ The dependency graph contains conflicting major version requirements for the \ package {}. diff --git a/ipm/src/command/update.rs b/inko/src/command/pkg/update.rs similarity index 78% rename from ipm/src/command/update.rs rename to inko/src/command/pkg/update.rs index d7b92a49a..e5a4241ac 100644 --- a/ipm/src/command/update.rs +++ b/inko/src/command/pkg/update.rs @@ -1,11 +1,14 @@ use crate::error::Error; -use crate::git::Repository; -use crate::manifest::{Checksum, Dependency, Manifest, Url, MANIFEST_FILE}; -use crate::util::{data_dir, usage}; -use crate::version::Version; +use crate::options::print_usage; +use crate::pkg::git::Repository; +use crate::pkg::manifest::{ + Checksum, Dependency, Manifest, Url, MANIFEST_FILE, +}; +use crate::pkg::util::data_dir; +use crate::pkg::version::Version; use getopts::Options; -const USAGE: &str = "ipm update [OPTIONS] [PACKAGE] +const USAGE: &str = "inko pkg update [OPTIONS] [PACKAGE] Update the version requirements of one or more packages to the latest compatible version. This command only updates the entries in the package manifest. @@ -15,11 +18,11 @@ update them to the latest major version, use the -m/--major flag. Examples: - ipm update - ipm update github.com/inko-lang/example - ipm update github.com/inko-lang/example --major"; + inko pkg update + inko pkg update github.com/inko-lang/example + inko pkg update github.com/inko-lang/example --major"; -pub(crate) fn run(args: &[String]) -> Result<(), Error> { +pub(crate) fn run(args: &[String]) -> Result { let mut options = Options::new(); options.optflag("h", "help", "Show this help message"); @@ -28,8 +31,8 @@ pub(crate) fn run(args: &[String]) -> Result<(), Error> { let matches = options.parse(args)?; if matches.opt_present("h") { - usage(&options, USAGE); - return Ok(()); + print_usage(&options, USAGE); + return Ok(0); } let major = matches.opt_present("m"); @@ -40,7 +43,10 @@ pub(crate) fn run(args: &[String]) -> Result<(), Error> { if let Some(dep) = manifest.find_dependency(&url) { vec![dep] } else { - fail!("The package {} isn't listed in {}", url, MANIFEST_FILE); + return Err(Error::generic(format!( + "The package {} isn't listed in {}", + url, MANIFEST_FILE + ))); } } else { manifest.dependencies_mut() @@ -60,7 +66,10 @@ pub(crate) fn run(args: &[String]) -> Result<(), Error> { let tag_names = repo.version_tag_names(); if tag_names.is_empty() { - fail!("The package {} doesn't have any versions", dep.url); + return Err(Error::generic(format!( + "The package {} doesn't have any versions", + dep.url + ))); } let mut candidates = version_candidates(dep, tag_names, major); @@ -83,7 +92,8 @@ pub(crate) fn run(args: &[String]) -> Result<(), Error> { dep.checksum = Checksum::new(tag.target); } - manifest.save(&MANIFEST_FILE) + manifest.save(&MANIFEST_FILE)?; + Ok(0) } fn version_candidates( diff --git a/inko/src/command/print.rs b/inko/src/command/print.rs new file mode 100644 index 000000000..f5680d139 --- /dev/null +++ b/inko/src/command/print.rs @@ -0,0 +1,49 @@ +use crate::error::Error; +use crate::options::print_usage; +use compiler::config::Config; +use compiler::target::Target; +use getopts::Options; + +const USAGE: &str = "Usage: inko print [OPTIONS] [ARGS] + +Print compiler details, such as the target, to STDOUT. + +Available values: + + target # Print the host's target triple (e.g. amd64-linux-gnu) + runtime # Print the path to the static runtime library + +Examples: + + inko print target # Print the target to STDOUT"; + +pub(crate) fn run(arguments: &[String]) -> Result { + let mut options = Options::new(); + + options.optflag("h", "help", "Show this help message"); + + let matches = options.parse(arguments)?; + + if matches.opt_present("h") { + print_usage(&options, USAGE); + return Ok(0); + } + + match matches.free.get(0).map(|s| s.as_str()) { + Some("target") => { + println!("{}", Target::native()); + Ok(0) + } + Some("runtime") => { + println!("{}", Config::new().runtime.display()); + Ok(0) + } + Some(val) => Err(Error::generic(format!( + "'{}' isn't a valid value to print", + val + ))), + None => Err(Error::generic( + "You must specify a type of value to print".to_string(), + )), + } +} diff --git a/inko/src/command/run.rs b/inko/src/command/run.rs index 3a38455e9..a9f00ea73 100644 --- a/inko/src/command/run.rs +++ b/inko/src/command/run.rs @@ -1,41 +1,37 @@ -//! Command for compiling and running Inko source code or bytecode images. use crate::error::Error; use crate::options::print_usage; use compiler::compiler::{CompileError, Compiler}; -use compiler::config::{Config as CompilerConfig, IMAGE_EXT}; +use compiler::config::{Config, Mode}; use getopts::{Options, ParsingStyle}; +use std::env::temp_dir; +use std::fs::{create_dir, remove_dir_all}; use std::path::PathBuf; -use vm::config::Config; -use vm::image::Image; -use vm::machine::Machine; +use std::process::Command; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; -const USAGE: &str = "Usage: inko run [OPTIONS] [FILE] +const USAGE: &str = "Usage: inko run [OPTIONS] [FILE] [ARGS] -Compile a source file and its dependencies into a bytecode file, then run it. +Compile a source file and its dependencies into an executable, then run it. -If the file is a source file (its extension is .inko), the file is first -compiled into a bytecode file. If the file is a bytecode file (its extension is -.ibi), the file is run directly. +Running source files is meant for development and scripting purposes, as it +requires compiling source code from scratch every time. When distributing or +deploying your Inko software, you should build it ahead of time using the +\"inko build\" command. -Running source files is meant for development and scripting purposes, and comes -with the overhead of having to run the compiler. For production environments -it's best to compile and run your program separately. For example: +When running files directly, the executable is built in release mode. - inko build hello.inko -o hello.ibi # Produces ./hello.ibi - inko run hello.ibi # Run the bytecode file +Arguments passed _after_ the file to run are passed to the resulting executable. Examples: - inko run hello.inko # Compile and runs the file hello.inko - inko run hello.ibi # Run the bytecode file directly"; + inko run hello.inko # Compile and run the file hello.inko + inko run hello.inko --foo # Passes --foo to the resulting executable"; -pub fn run(arguments: &[String]) -> Result { +pub(crate) fn run(arguments: &[String]) -> Result { let mut options = Options::new(); options.parsing_style(ParsingStyle::StopAtFirstFree); - - options.optflag("h", "help", "Shows this help message"); - + options.optflag("h", "help", "Show this help message"); options.optopt( "f", "format", @@ -57,27 +53,10 @@ pub fn run(arguments: &[String]) -> Result { return Ok(0); } - let input = matches.free.get(0); + let mut config = Config::default(); let arguments = if matches.free.len() > 1 { &matches.free[1..] } else { &[] }; - match input { - Some(input) if input.ends_with(IMAGE_EXT) => { - let config = Config::from_env(); - let image = Image::load_file(config, input).map_err(|e| { - Error::generic(format!( - "Failed to parse bytecode image {}: {}", - input, e - )) - })?; - - return Machine::boot(image, arguments).map_err(Error::generic); - } - _ => {} - } - - let mut config = CompilerConfig::default(); - if let Some(format) = matches.opt_str("f") { config.set_presenter(&format)?; } @@ -86,23 +65,52 @@ pub fn run(arguments: &[String]) -> Result { config.sources.add(path.into()); } + let time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_else(|_| Duration::from_secs(0)) + .as_secs(); + let build_dir = temp_dir().join(format!("inko-run-{}", time)); + + if !build_dir.is_dir() { + create_dir(&build_dir).map_err(|err| { + Error::generic(format!( + "Failed to create {}: {}", + build_dir.display(), + err + )) + })?; + } + + config.build = build_dir.clone(); + config.mode = Mode::Release; + let mut compiler = Compiler::new(config); - let result = compiler.compile_to_memory(input.map(PathBuf::from)); + let file = matches.free.get(0).map(PathBuf::from); + let result = compiler.run(file); compiler.print_diagnostics(); - // This ensures we don't keep the compiler instance around beyond this - // point, as we don't need it from now on. - drop(compiler); - match result { - Ok(bytes) => { - let config = Config::from_env(); - let image = Image::load_bytes(config, bytes).map_err(|e| { - Error::generic(format!("Failed to parse bytecode image: {}", e)) - })?; - - Machine::boot(image, arguments).map_err(Error::generic) + Ok(exe) => { + let status = Command::new(exe) + .args(arguments) + .spawn() + .and_then(|mut child| child.wait()) + .map_err(|err| { + Error::generic(format!( + "Failed to run the executable: {}", + err + )) + }) + .map(|status| status.code().unwrap_or(0)); + + if build_dir.is_dir() { + // If this fails that dosen't matter because temporary files are + // removed upon shutdown anyway. + let _ = remove_dir_all(&build_dir); + } + + status } Err(CompileError::Invalid) => Ok(1), Err(CompileError::Internal(msg)) => Err(Error::generic(msg)), diff --git a/inko/src/command/test.rs b/inko/src/command/test.rs index 3b80a5a5f..b53dbcf6a 100644 --- a/inko/src/command/test.rs +++ b/inko/src/command/test.rs @@ -1,29 +1,26 @@ -//! Command for running unit tests. use crate::error::Error; use crate::options::print_usage; use compiler::compiler::{CompileError, Compiler}; -use compiler::config::Config as CompilerConfig; +use compiler::config::{Config, Output}; use getopts::Options; -use vm::config::Config; -use vm::image::Image; -use vm::machine::Machine; +use std::process::Command; const USAGE: &str = "Usage: inko test [OPTIONS] Compiles and runs unit tests -This command adds your tests directory to the module load path, and runs the -`main.inko` file that resides in this tests directory. +This command compiles your unit tests in ./test, then runs the resulting test +executable. Examples: inko test # Runs all unit tests in ./test"; /// Compiles and runs Inko unit tests. -pub fn run(arguments: &[String]) -> Result { +pub(crate) fn run(arguments: &[String]) -> Result { let mut options = Options::new(); - options.optflag("h", "help", "Shows this help message"); + options.optflag("h", "help", "Show this help message"); let matches = options.parse(arguments)?; @@ -32,34 +29,33 @@ pub fn run(arguments: &[String]) -> Result { return Ok(0); } - let arguments = &matches.free; - let mut config = CompilerConfig::default(); + let mut config = Config::default(); let input = config.main_test_module(); - let tests = config.tests.clone(); - if !tests.is_dir() { + if !config.tests.is_dir() { return Err(Error::generic(format!( "The tests directory {:?} doesn't exist", - tests + config.tests ))); } - config.sources.add(tests); + config.sources.add(config.tests.clone()); + config.output = Output::File("inko-tests".to_string()); let mut compiler = Compiler::new(config); - let result = compiler.compile_to_memory(Some(input)); + let result = compiler.run(Some(input)); compiler.print_diagnostics(); match result { - Ok(bytes) => { - let config = Config::from_env(); - let image = Image::load_bytes(config, bytes).map_err(|e| { - Error::generic(format!("Failed to parse bytecode image: {}", e)) - })?; - - Machine::boot(image, arguments).map_err(Error::generic) - } + Ok(exe) => Command::new(exe) + .args(matches.free) + .spawn() + .and_then(|mut child| child.wait()) + .map_err(|err| { + Error::generic(format!("Failed to run the tests: {}", err)) + }) + .map(|status| status.code().unwrap_or(0)), Err(CompileError::Invalid) => Ok(1), Err(CompileError::Internal(msg)) => Err(Error::generic(msg)), } diff --git a/inko/src/error.rs b/inko/src/error.rs index b9abe7a2e..1fc44e78c 100644 --- a/inko/src/error.rs +++ b/inko/src/error.rs @@ -3,16 +3,16 @@ use getopts::Fail; use std::io; /// An error produced by a subprocess. -pub struct Error { +pub(crate) struct Error { /// The exit code to use. - pub status: i32, + pub(crate) status: i32, /// An error message to print to STDERR. - pub message: Option, + pub(crate) message: Option, } impl Error { - pub fn generic(message: String) -> Self { + pub(crate) fn generic(message: String) -> Self { Error { status: 1, message: Some(message) } } } diff --git a/inko/src/main.rs b/inko/src/main.rs index ea9573d2e..a6f334b0d 100644 --- a/inko/src/main.rs +++ b/inko/src/main.rs @@ -9,6 +9,7 @@ static A: jemallocator::Jemalloc = jemallocator::Jemalloc; mod command; mod error; mod options; +mod pkg; use crate::command::main; use std::process::exit; diff --git a/inko/src/options.rs b/inko/src/options.rs index 6f4b6dcf0..5be7d4480 100644 --- a/inko/src/options.rs +++ b/inko/src/options.rs @@ -2,7 +2,7 @@ use getopts::Options; /// Prints a usage message for a set of CLI options. -pub fn print_usage(options: &Options, brief: &str) { +pub(crate) fn print_usage(options: &Options, brief: &str) { let out = options.usage_with_format(|opts| { format!( "{}\n\nOptions:\n\n{}\n", diff --git a/ipm/src/git.rs b/inko/src/pkg/git.rs similarity index 78% rename from ipm/src/git.rs rename to inko/src/pkg/git.rs index fcc3d52c4..3e9cee279 100644 --- a/ipm/src/git.rs +++ b/inko/src/pkg/git.rs @@ -1,4 +1,3 @@ -use crate::error::Error; use std::ffi::OsStr; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; @@ -16,29 +15,32 @@ pub(crate) struct Tag { } impl Repository { - pub(crate) fn open(path: &Path) -> Result { + pub(crate) fn open(path: &Path) -> Result { if path.is_dir() { Ok(Self { path: path.to_path_buf() }) } else { - fail!("The Git repository at {} doesn't exist", path.display()) + Err(format!( + "The Git repository at {} doesn't exist", + path.display() + )) } } - pub(crate) fn clone(url: &str, path: &Path) -> Result { + pub(crate) fn clone(url: &str, path: &Path) -> Result { run("clone", None, &[OsStr::new(url), path.as_os_str()]) - .map_err(|err| error!("Failed to clone {}: {}", url, err))?; + .map_err(|err| format!("Failed to clone {}: {}", url, err))?; Ok(Self { path: path.to_path_buf() }) } - pub(crate) fn fetch(&mut self) -> Result<(), Error> { + pub(crate) fn fetch(&mut self) -> Result<(), String> { run( "fetch", Some(self.path.as_path()), &[OsStr::new(REMOTE), OsStr::new("--tags")], ) .map_err(|err| { - error!("Failed to update {}: {}", self.path.display(), err) + format!("Failed to update {}: {}", self.path.display(), err) })?; Ok(()) @@ -66,7 +68,7 @@ impl Repository { } } - pub(crate) fn checkout(&self, name: &str) -> Result<(), Error> { + pub(crate) fn checkout(&self, name: &str) -> Result<(), String> { run("checkout", Some(self.path.as_path()), &[OsStr::new(name)])?; Ok(()) } @@ -76,7 +78,7 @@ fn run( command: &str, working_directory: Option<&Path>, arguments: &[&OsStr], -) -> Result { +) -> Result { let mut cmd = Command::new("git"); cmd.arg(command); @@ -92,8 +94,8 @@ fn run( let child = cmd .spawn() - .map_err(|err| error!("Failed to spawn 'git {}': {}", command, err))?; - let output = child.wait_with_output().map_err(|err| error!("{}", err))?; + .map_err(|err| format!("Failed to spawn 'git {}': {}", command, err))?; + let output = child.wait_with_output().map_err(|err| format!("{}", err))?; if output.status.success() { Ok(String::from_utf8_lossy(&output.stdout) @@ -101,12 +103,10 @@ fn run( .trim() .to_string()) } else { - Err(Error::new( - String::from_utf8_lossy(&output.stderr) - .into_owned() - .trim() - .to_string(), - )) + Err(String::from_utf8_lossy(&output.stderr) + .into_owned() + .trim() + .to_string()) } } @@ -136,7 +136,7 @@ mod tests { #[test] fn test_repository_clone() { - let temp = temp_dir().join("ipm-test_repository_clone"); + let temp = temp_dir().join("inko-pkg-test_repository_clone"); let repo = Repository::clone(source_dir().to_str().unwrap(), &temp); assert!(repo.is_ok()); @@ -147,7 +147,7 @@ mod tests { #[test] fn test_repository_fetch() { - let temp = temp_dir().join("ipm-test_repository_fetch"); + let temp = temp_dir().join("inko-pkg-test_repository_fetch"); let mut repo = Repository::clone(source_dir().to_str().unwrap(), &temp).unwrap(); @@ -158,7 +158,7 @@ mod tests { #[test] fn test_repository_tag() { - let temp = temp_dir().join("ipm-test_repository_tag"); + let temp = temp_dir().join("inko-pkg-test_repository_tag"); let mut repo = Repository::clone(source_dir().to_str().unwrap(), &temp).unwrap(); @@ -171,7 +171,7 @@ mod tests { #[test] fn test_repository_checkout() { - let temp = temp_dir().join("ipm-test_repository_checkout"); + let temp = temp_dir().join("inko-pkg-test_repository_checkout"); let mut repo = Repository::clone(source_dir().to_str().unwrap(), &temp).unwrap(); @@ -186,7 +186,8 @@ mod tests { #[test] fn test_repository_version_tag_names() { - let temp = temp_dir().join("ipm-test_repository_version_tag_names"); + let temp = + temp_dir().join("inko-pkg-test_repository_version_tag_names"); let mut repo = Repository::clone(source_dir().to_str().unwrap(), &temp).unwrap(); diff --git a/ipm/src/manifest.rs b/inko/src/pkg/manifest.rs similarity index 92% rename from ipm/src/manifest.rs rename to inko/src/pkg/manifest.rs index 36900c684..a5d2fbf93 100644 --- a/ipm/src/manifest.rs +++ b/inko/src/pkg/manifest.rs @@ -1,5 +1,4 @@ -use crate::error::Error; -use crate::version::Version; +use crate::pkg::version::Version; use blake2::{digest::consts::U16, Blake2b, Digest}; use std::fmt; use std::fs::File; @@ -119,22 +118,22 @@ pub(crate) struct Manifest { } impl Manifest { - pub(crate) fn load>(path: &P) -> Result { + pub(crate) fn load>(path: &P) -> Result { let path = path.as_ref(); File::open(path) - .map_err(|e| error!("Failed to read {}: {}", path.display(), e)) + .map_err(|e| format!("Failed to read {}: {}", path.display(), e)) .and_then(|mut file| Self::parse(&mut file)) } - pub(crate) fn parse(stream: &mut R) -> Result { + pub(crate) fn parse(stream: &mut R) -> Result { let reader = BufReader::new(stream); let mut manifest = Self { entries: Vec::new() }; for (index, line) in reader.lines().enumerate() { let lnum = index + 1; let line = line.map_err(|err| { - error!("Failed to read lines from the manifest: {}", err) + format!("Failed to read lines from the manifest: {}", err) })?; let trimmed = line.trim(); @@ -152,25 +151,25 @@ impl Manifest { let chunks: Vec<_> = trimmed.split(' ').collect(); if chunks.len() != 4 { - fail!("The entry on line {} is invalid", lnum); + return Err(format!("The entry on line {} is invalid", lnum)); } // Currently this is the only action we support. if chunks[0] != "require" { - fail!( + return Err(format!( "Expected line {} to start with 'require', not '{}'", - lnum, - chunks[0] - ); + lnum, chunks[0] + )); } - let url = Url::parse(chunks[1]) - .ok_or_else(|| error!("The URI on line {} is invalid", lnum))?; + let url = Url::parse(chunks[1]).ok_or_else(|| { + format!("The URI on line {} is invalid", lnum) + })?; let version = Version::parse(chunks[2]).ok_or_else(|| { - error!("The version on line {} is invalid", lnum) + format!("The version on line {} is invalid", lnum) })?; let checksum = Checksum::parse(chunks[3]).ok_or_else(|| { - error!("The checksum on line {} is invalid", lnum) + format!("The checksum on line {} is invalid", lnum) })?; manifest.entries.push(Entry::Dependency(Dependency { @@ -232,12 +231,12 @@ impl Manifest { .collect() } - pub(crate) fn save>(&self, path: &P) -> Result<(), Error> { + pub(crate) fn save>(&self, path: &P) -> Result<(), String> { let path = path.as_ref(); File::create(path) .and_then(|mut file| file.write_all(self.to_string().as_bytes())) - .map_err(|e| error!("Failed to update {}: {}", path.display(), e)) + .map_err(|e| format!("Failed to update {}: {}", path.display(), e)) } } @@ -309,22 +308,20 @@ mod tests { assert_eq!( Manifest::parse(&mut missing_chunks.as_bytes()), - Err(Error::new("The entry on line 2 is invalid".to_string())) + Err("The entry on line 2 is invalid".to_string()) ); assert_eq!( Manifest::parse(&mut invalid_cmd.as_bytes()), - Err(Error::new( - "Expected line 2 to start with 'require', not 'bla'" - .to_string() - )) + Err("Expected line 2 to start with 'require', not 'bla'" + .to_string()) ); assert_eq!( Manifest::parse(&mut invalid_version.as_bytes()), - Err(Error::new("The version on line 1 is invalid".to_string())) + Err("The version on line 1 is invalid".to_string()) ); assert_eq!( Manifest::parse(&mut invalid_checksum.as_bytes()), - Err(Error::new("The checksum on line 1 is invalid".to_string())) + Err("The checksum on line 1 is invalid".to_string()) ); } diff --git a/inko/src/pkg/mod.rs b/inko/src/pkg/mod.rs new file mode 100644 index 000000000..2c334e345 --- /dev/null +++ b/inko/src/pkg/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod git; +pub(crate) mod manifest; +pub(crate) mod util; +pub(crate) mod version; diff --git a/inko/src/pkg/util.rs b/inko/src/pkg/util.rs new file mode 100644 index 000000000..bd037d50d --- /dev/null +++ b/inko/src/pkg/util.rs @@ -0,0 +1,88 @@ +use std::env; +use std::fs::{copy, create_dir_all, read_dir}; +use std::path::{Path, PathBuf}; + +/// The directory to install dependencies into. +pub(crate) const DEP_DIR: &str = "dep"; + +fn home_dir() -> Option { + env::var_os("HOME").filter(|v| !v.is_empty()).map(PathBuf::from) +} + +pub(crate) fn data_dir() -> Result { + let base = if cfg!(target_os = "macos") { + home_dir().map(|h| h.join("Library").join("Application Support")) + } else { + env::var_os("XDG_DATA_HOME") + .filter(|v| !v.is_empty()) + .map(PathBuf::from) + .or_else(|| home_dir().map(|h| h.join(".local").join("share"))) + }; + + base.map(|p| p.join("inko").join("packages")) + .ok_or_else(|| "No data directory could be determined".to_string()) +} + +pub(crate) fn cp_r(source: &Path, target: &Path) -> Result<(), String> { + create_dir_all(target).map_err(|e| e.to_string())?; + + let mut pending = vec![source.to_path_buf()]; + + while let Some(path) = pending.pop() { + let entries = read_dir(&path).map_err(|e| e.to_string())?; + + for entry in entries { + let entry = entry.map_err(|e| e.to_string())?; + let path = entry.path(); + + if path.is_dir() { + pending.push(path); + continue; + } + + let rel = path.strip_prefix(source).unwrap(); + let target = target.join(rel); + let dir = target.parent().unwrap(); + + create_dir_all(dir) + .map_err(|e| format!("Failed to create {:?}: {}", dir, e))?; + + if target.is_file() { + return Err(format!( + "Failed to copy {} to {} as the target file already exists", + path.display(), + target.display() + )); + } + + copy(&path, &target).map_err(|error| { + format!( + "Failed to copy {} to {}: {}", + path.to_string_lossy(), + target.to_string_lossy(), + error + ) + })?; + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::env; + use std::fs::remove_dir_all; + + #[test] + fn test_cp_r() { + let src = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let temp = env::temp_dir().join("inko-pkg-test_cp_r"); + + assert!(cp_r(&src.join("src"), &temp).is_ok()); + assert!(temp.join("pkg").join("util.rs").is_file()); + + remove_dir_all(temp).unwrap(); + } +} diff --git a/ipm/src/version.rs b/inko/src/pkg/version.rs similarity index 98% rename from ipm/src/version.rs rename to inko/src/pkg/version.rs index f4e195474..2baf1596c 100644 --- a/ipm/src/version.rs +++ b/inko/src/pkg/version.rs @@ -1,4 +1,4 @@ -use crate::manifest::{Dependency, Url}; +use crate::pkg::manifest::{Dependency, Url}; use std::cmp::{Ord, Ordering}; use std::collections::HashMap; use std::fmt; @@ -131,7 +131,7 @@ pub(crate) fn select<'a>( #[cfg(test)] mod tests { use super::*; - use crate::manifest::Checksum; + use crate::pkg::manifest::Checksum; #[test] fn test_version_parse() { diff --git a/ipm/Cargo.toml b/ipm/Cargo.toml deleted file mode 100644 index 5051fb8ae..000000000 --- a/ipm/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "ipm" -version = "0.10.0" # VERSION -authors = ["Yorick Peterse "] -edition = "2021" -license = "MPL-2.0" - -[dependencies] -getopts = "^0.2" -blake2 = "^0.10" diff --git a/ipm/src/command/add.rs b/ipm/src/command/add.rs deleted file mode 100644 index 29e7e8464..000000000 --- a/ipm/src/command/add.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::error::Error; -use crate::git::Repository; -use crate::manifest::{Checksum, Manifest, Url, MANIFEST_FILE}; -use crate::util::{data_dir, usage}; -use crate::version::Version; -use getopts::Options; - -const USAGE: &str = "ipm add [OPTIONS] [URL] [VERSION] - -Add a package to the manifest in the current working directory. - -This command doesn't resolve any sub-dependencies or install the package into -your project, for that you need to run `ipm sync`. - -Examples: - - ipm add github.com/inko-lang/example 1.2.3"; - -pub(crate) fn run(args: &[String]) -> Result<(), Error> { - let mut options = Options::new(); - - options.optflag("h", "help", "Show this help message"); - - let matches = options.parse(args)?; - - if matches.opt_present("h") || matches.free.is_empty() { - usage(&options, USAGE); - return Ok(()); - } - - if matches.free.len() != 2 { - fail!("You must specify a package and version to add"); - } - - let url = matches - .free - .get(0) - .and_then(|uri| Url::parse(uri)) - .ok_or_else(|| error!("The package URL is invalid"))?; - - let version = matches - .free - .get(1) - .and_then(|uri| Version::parse(uri)) - .ok_or_else(|| error!("The package version is invalid"))?; - - let dir = data_dir()?.join(url.directory_name()); - let (mut repo, fetch) = if dir.is_dir() { - (Repository::open(&dir)?, true) - } else { - (Repository::clone(&url.to_string(), &dir)?, false) - }; - - if fetch { - repo.fetch()?; - } - - let tag_name = version.tag_name(); - let tag = if let Some(tag) = repo.tag(&tag_name) { - Some(tag) - } else if fetch { - println!("Updating {}", url); - repo.fetch()?; - repo.tag(&tag_name) - } else { - None - }; - - let hash = tag - .map(|t| t.target) - .ok_or_else(|| error!("Version {} doesn't exist", version))?; - - let checksum = Checksum::new(&hash); - let mut manifest = Manifest::load(&MANIFEST_FILE)?; - - if let Some(existing) = manifest.find_dependency(&url) { - existing.version = version; - existing.checksum = checksum; - } else { - manifest.add_dependency(url, version, checksum); - } - - manifest.save(&MANIFEST_FILE) -} diff --git a/ipm/src/command/init.rs b/ipm/src/command/init.rs deleted file mode 100644 index b17d7267a..000000000 --- a/ipm/src/command/init.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::error::Error; -use crate::manifest::MANIFEST_FILE; -use crate::util::usage; -use getopts::Options; -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -const USAGE: &str = "ipm init [OPTIONS] [DIR] - -Create a new package in an existing directory - -Examples: - - ipm init - ipm init example/"; - -const TEMPLATE: &str = "\ -# This file contains your project's dependencies. For more information, refer -# to https://docs.inko-lang.org/manual/latest/getting-started/modules/"; - -pub(crate) fn run(args: &[String]) -> Result<(), Error> { - let mut options = Options::new(); - - options.optflag("h", "help", "Show this help message"); - - let matches = options.parse(args)?; - - if matches.opt_present("h") { - usage(&options, USAGE); - return Ok(()); - } - - let dir = if matches.free.is_empty() { - env::current_dir()? - } else { - PathBuf::from(&matches.free[0]) - }; - - if !dir.is_dir() { - fail!("The directory {:?} doesn't exist", dir); - } - - let path = dir.join(MANIFEST_FILE); - - if path.exists() { - Ok(()) - } else { - let mut file = File::create(&path) - .map_err(|e| error!("Failed to create {:?}: {}", path, e))?; - - file.write_all(TEMPLATE.as_bytes()) - .map_err(|e| error!("Failed to write to {:?}: {}", path, e)) - } -} diff --git a/ipm/src/command/main.rs b/ipm/src/command/main.rs deleted file mode 100644 index da2057404..000000000 --- a/ipm/src/command/main.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::command::add; -use crate::command::init; -use crate::command::remove; -use crate::command::sync; -use crate::command::update; -use crate::error::Error; -use crate::util::usage; -use getopts::{Options, ParsingStyle}; -use std::env::args; - -const USAGE: &str = "ipm [OPTIONS] [COMMAND] - -ipm is Inko's package manager, used to install and manage dependencies of -your project. - -Commands: - - init Create a new package - add Add or update a dependency - remove Remove a dependency - sync Download and install dependencies - update Update all dependencies to the latest version - -Examples: - - ipm init - ipm add github.com/hello/world 1.2.3"; - -pub(crate) fn run() -> Result<(), Error> { - let args: Vec<_> = args().collect(); - let mut options = Options::new(); - - options.parsing_style(ParsingStyle::StopAtFirstFree); - options.optflag("h", "help", "Show this help message"); - options.optflag("v", "version", "Print the version number"); - - let matches = options.parse(&args[1..])?; - - if matches.opt_present("h") { - usage(&options, USAGE); - return Ok(()); - } - - if matches.opt_present("v") { - println!("ipm version {}", env!("CARGO_PKG_VERSION")); - return Ok(()); - } - - match matches.free.get(0).map(|s| s.as_str()) { - Some("init") => init::run(&matches.free[1..]), - Some("add") => add::run(&matches.free[1..]), - Some("remove") => remove::run(&matches.free[1..]), - Some("sync") => sync::run(&matches.free[1..]), - Some("update") => update::run(&matches.free[1..]), - Some(cmd) => fail!("The command {:?} is invalid", cmd), - None => sync::run(&[]), - } -} diff --git a/ipm/src/command/mod.rs b/ipm/src/command/mod.rs deleted file mode 100644 index 746b068d5..000000000 --- a/ipm/src/command/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub(crate) mod add; -pub(crate) mod init; -pub(crate) mod main; -pub(crate) mod remove; -pub(crate) mod sync; -pub(crate) mod update; diff --git a/ipm/src/command/remove.rs b/ipm/src/command/remove.rs deleted file mode 100644 index cd5f00fc3..000000000 --- a/ipm/src/command/remove.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::error::Error; -use crate::manifest::{Manifest, Url, MANIFEST_FILE}; -use crate::util::usage; -use getopts::Options; - -const USAGE: &str = "ipm remove [OPTIONS] [URI] - -Remove a dependency from the manifest. - -This command doesn't remove the dependency from your project, for that you need -to run `ipm sync`. - -Examples: - - ipm remove github.com/inko-lang/example"; - -pub(crate) fn run(args: &[String]) -> Result<(), Error> { - let mut options = Options::new(); - - options.optflag("h", "help", "Show this help message"); - - let matches = options.parse(args)?; - - if matches.opt_present("h") || matches.free.is_empty() { - usage(&options, USAGE); - return Ok(()); - } - - let url = matches - .free - .get(0) - .and_then(|uri| Url::parse(uri)) - .ok_or_else(|| error!("The package URL is invalid"))?; - - let mut manifest = Manifest::load(&MANIFEST_FILE)?; - - manifest.remove_dependency(&url); - manifest.save(&MANIFEST_FILE) -} diff --git a/ipm/src/error.rs b/ipm/src/error.rs deleted file mode 100644 index 5731e5483..000000000 --- a/ipm/src/error.rs +++ /dev/null @@ -1,32 +0,0 @@ -use getopts::Fail; -use std::fmt; -use std::io; - -#[derive(Debug, PartialEq, Eq)] -pub struct Error { - message: String, -} - -impl Error { - pub fn new>(message: S) -> Self { - Error { message: message.into() } - } -} - -impl From for Error { - fn from(fail: Fail) -> Self { - Error { message: fail.to_string() } - } -} - -impl From for Error { - fn from(error: io::Error) -> Self { - Error { message: error.to_string() } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.message) - } -} diff --git a/ipm/src/macros.rs b/ipm/src/macros.rs deleted file mode 100644 index 50ddc1711..000000000 --- a/ipm/src/macros.rs +++ /dev/null @@ -1,11 +0,0 @@ -macro_rules! fail { - ($message:expr $(,$arg:expr)*) => { - return Err(Error::new(format!($message $(,$arg)*))) - }; -} - -macro_rules! error { - ($message:expr $(,$arg:expr)*) => { - Error::new(format!($message $(,$arg)*)) - }; -} diff --git a/ipm/src/main.rs b/ipm/src/main.rs deleted file mode 100644 index f1c437e00..000000000 --- a/ipm/src/main.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[macro_use] -mod macros; - -mod command; -mod error; -mod git; -mod manifest; -mod util; -mod version; - -use crate::util::red; -use command::main; -use std::process::exit; - -fn main() { - if let Err(err) = main::run() { - eprintln!("{} {}", red("error:"), err); - exit(1); - } -} diff --git a/ipm/src/util.rs b/ipm/src/util.rs deleted file mode 100644 index 71a467fd5..000000000 --- a/ipm/src/util.rs +++ /dev/null @@ -1,127 +0,0 @@ -use crate::error::Error; -use getopts::Options; -use std::env; -use std::fs::{copy, create_dir_all, read_dir}; -use std::path::{Path, PathBuf}; - -const DIR_NAME: &str = "ipm"; - -/// The directory to install dependencies into. -pub(crate) const DEP_DIR: &str = "dep"; - -fn windows_local_appdata() -> Option { - env::var_os("LOCALAPPDATA") - .filter(|v| !v.is_empty()) - .map(PathBuf::from) - .or_else(|| home_dir().map(|h| h.join("AppData").join("Local"))) -} - -fn home_dir() -> Option { - let var = if cfg!(windows) { - env::var_os("USERPROFILE") - } else { - env::var_os("HOME") - }; - - var.filter(|v| !v.is_empty()).map(PathBuf::from) -} - -pub(crate) fn usage(options: &Options, summary: &str) { - let out = options.usage_with_format(|opts| { - format!( - "{}\n\nOptions:\n\n{}", - summary, - opts.collect::>().join("\n") - ) - }); - - println!("{}", out); -} - -pub(crate) fn data_dir() -> Result { - let base = if cfg!(windows) { - windows_local_appdata() - } else if cfg!(target_os = "macos") { - home_dir().map(|h| h.join("Library").join("Application Support")) - } else { - env::var_os("XDG_DATA_HOME") - .filter(|v| !v.is_empty()) - .map(PathBuf::from) - .or_else(|| home_dir().map(|h| h.join(".local").join("share"))) - }; - - base.map(|p| p.join(DIR_NAME)) - .ok_or_else(|| error!("No data directory could be determined")) -} - -pub(crate) fn cp_r(source: &Path, target: &Path) -> Result<(), Error> { - create_dir_all(target)?; - - let mut pending = vec![source.to_path_buf()]; - - while let Some(path) = pending.pop() { - let entries = read_dir(&path)?; - - for entry in entries { - let entry = entry?; - let path = entry.path(); - - if path.is_dir() { - pending.push(path); - continue; - } - - let rel = path.strip_prefix(&source).unwrap(); - let target = target.join(rel); - let dir = target.parent().unwrap(); - - create_dir_all(&dir) - .map_err(|e| error!("Failed to create {:?}: {}", dir, e))?; - - if target.is_file() { - fail!( - "Failed to copy {} to {} as the target file already exists", - path.display(), - target.display() - ); - } - - copy(&path, &target).map_err(|error| { - error!( - "Failed to copy {} to {}: {}", - path.to_string_lossy(), - target.to_string_lossy(), - error - ) - })?; - } - } - - Ok(()) -} - -pub(crate) fn red>(message: S) -> String { - if cfg!(windows) { - message.into() - } else { - format!("\x1b[1m\x1b[31m{}\x1b[0m\x1b[0m", message.into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::env; - use std::fs::remove_dir_all; - - #[test] - fn test_cp_r() { - let src = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let temp = env::temp_dir().join("ipm-test_cp_r"); - - assert!(cp_r(&src.join("src"), &temp).is_ok()); - assert!(temp.join("util.rs").is_file()); - - remove_dir_all(temp).unwrap(); - } -} diff --git a/libstd/src/std/clone.inko b/libstd/src/std/clone.inko deleted file mode 100644 index 387b6a587..000000000 --- a/libstd/src/std/clone.inko +++ /dev/null @@ -1,7 +0,0 @@ -# Cloning of objects. - -# A type that can be cloned into a copy of itself. -trait pub Clone { - # Returns a copy of `self`. - fn pub clone -> Self -} diff --git a/libstd/src/std/ffi.inko b/libstd/src/std/ffi.inko deleted file mode 100644 index b7f91a92f..000000000 --- a/libstd/src/std/ffi.inko +++ /dev/null @@ -1,543 +0,0 @@ -# Foreign function interface for interfacing with C code. -# -# This module provides various types and methods for calling C code from Inko -# code. -# -# # Limitations -# -# * C code calling back into Inko code isn't supported -# * Only builtin types (e.g. Int and String) can be converted from/to C types -# * Memory management of C types has to be done manually (e.g. using `free()`) -# * Variadic arguments aren't supported -import std::clone::Clone -import std::cmp::Equal -import std::drop::Drop -import std::fmt::(Format, Formatter) -import std::hash::Hash -import std::index::(Index, SetIndex) -import std::int::ToInt -import std::process::(panic) -import std::set::Set - -# The address used for NULL pointers. -let NULL_ADDRESS = 0x0 - -# The size (in bytes) of an Inko object header. -let HEADER_SIZE = 16 - -# A pointer to a C value. -class pub Pointer { - # The raw pointer. - let @raw: Any - - # Returns a `Pointer` wrapping the given raw pointer. - fn pub static new(raw: Any) -> Self { - Self { @raw = raw } - } - - # Creates a pointer using the given memory address. - fn pub static from_address(address: Int) -> Self { - Self { @raw = _INKO.ffi_pointer_from_address(address) } - } - - # Returns a NULL `Pointer`. - fn pub static null -> Self { - from_address(NULL_ADDRESS) - } - - # Returns `true` if this `Pointer` is a NULL pointer. - fn pub null? -> Bool { - address == NULL_ADDRESS - } - - # Returns the address of this `Pointer`. - fn pub address -> Int { - _INKO.ffi_pointer_address(@raw) - } - - # Reads the value of a pointer into a certain type. - # - # The `type` argument specifies the `Type` of data that is being read. - # - # The `offset` argument is used to specify an offset in bytes (relative to the - # pointer) to read from. - fn pub read(type: Type, offset: Int) -> Any { - _INKO.ffi_pointer_read(@raw, type.to_int, offset) - } - - # Writes a value to the pointer. - # - # The `type` argument specifies the `Type` of data that is being written. The - # `offset` argument can be used to specify an offset in bytes (relative to - # the pointer) to write to. - fn pub mut write(type: Type, offset: Int, value: Any) { - _INKO.ffi_pointer_write(@raw, type.to_int, offset, value) - } - - # Returns the underlying raw pointer. - fn pub raw -> Any { - @raw - } - - # Returns a pointer to the raw pointer wrapped by `self` (aka a pointer - # pointer). - fn pub pointer -> Pointer { - let addr = _INKO.ffi_pointer_address(self) + HEADER_SIZE - - Pointer.from_address(addr) - } -} - -impl Clone for Pointer { - fn pub clone -> Pointer { - Pointer.from_address(address) - } -} - -impl Equal for Pointer { - # Returns `true` if `self` and the given `Pointer` point to the same memory - # address. - fn pub ==(other: ref Self) -> Bool { - address == other.address - } -} - -impl Format for Pointer { - fn pub fmt(formatter: mut Formatter) { - formatter.write("Pointer(0x{address.to_base16})") - } -} - -# A dynamically loaded C library. -class pub Library { - let @lib: Any - - # Dynamically loads a C library. - # - # The `names` argument is an `Array` of library names to use for loading the - # library. These names are used in order to find the corresponding library. - # - # The names in this `Array` can either be the library name with extension, - # such as `'libc.so.6'`, or absolute paths such as `'/usr/lib/libc.so.6'`. - fn pub static new(names: ref Array[String]) -> Option[Self] { - let lib = _INKO.ffi_library_open(names) - - if _INKO.is_undefined(lib) { - Option.None - } else { - Option.Some(Self { @lib = lib }) - } - } - - # Returns a `Pointer` pointing to the address of the given variable. - fn pub variable(name: String) -> Option[Pointer] { - let ptr = _INKO.ffi_pointer_attach(@lib, name) - - if _INKO.is_undefined(ptr) { - Option.None - } else { - Option.Some(Pointer { @raw = ptr }) - } - } - - # Loads a C function from the given library. - # - # # Panics - # - # This method panics if the argument types or return type are invalid. - fn pub function( - name: String, - arguments: Array[Type], - returns: Type - ) -> Option[Function] { - let args = arguments.into_iter.map fn (type) { type.to_int }.to_array - let func = _INKO.ffi_function_attach(@lib, name, args, returns.to_int) - - if _INKO.is_undefined(func) { - Option.None - } else { - Option.Some(Function { @func = func }) - } - } -} - -impl Drop for Library { - fn mut drop { - _INKO.ffi_library_drop(@lib) - } -} - -# A C function that can be called from Inko. -class pub Function { - let @func: Any - - # Calls the function without providing any arguments. - fn pub call0 -> Any { - _INKO.ffi_function_call(@func) - } - - # Calls the function with a single argument. - fn pub call1(arg0: ref Any) -> Any { - _INKO.ffi_function_call(@func, arg0) - } - - # Calls the function with two arguments. - fn pub call2(arg1: ref Any, arg2: ref Any) -> Any { - _INKO.ffi_function_call(@func, arg1, arg2) - } - - # Calls the function with three arguments. - fn pub call3(arg1: ref Any, arg2: ref Any, arg3: ref Any) -> Any { - _INKO.ffi_function_call(@func, arg1, arg2, arg3) - } - - # Calls the function with four arguments. - fn pub call4( - arg1: ref Any, arg2: ref Any, arg3: ref Any, arg4: ref Any - ) -> Any { - _INKO.ffi_function_call(@func, arg1, arg2, arg3, arg4) - } - - # Calls the function with five arguments. - fn pub call5( - arg1: ref Any, arg2: ref Any, arg3: ref Any, arg4: ref Any, arg5: ref Any - ) -> Any { - _INKO.ffi_function_call(@func, arg1, arg2, arg3, arg4, arg5) - } - - # Calls the function with six arguments. - fn pub call6( - arg1: ref Any, - arg2: ref Any, - arg3: ref Any, - arg4: ref Any, - arg5: ref Any, - arg6: ref Any, - ) -> Any { - _INKO.ffi_function_call(@func, arg1, arg2, arg3, arg4, arg5, arg6) - } -} - -impl Drop for Function { - fn mut drop { - _INKO.ffi_function_drop(@func) - } -} - -# A member in a structure. -class Member { - let @name: String - let @type: Type - let @offset: Int -} - -# The members, alignment, and other information used for building a C -# structure. -class pub Layout { - # The members of this layout, mapped to their names. - let @members: Map[String, Member] - - # The size (in bytes) of this layout. - let pub @size: Int - - # The alignment to use for all fields. This equals the alignment of the - # largest field. - let pub @alignment: Int -} - -# A C structure. -class pub Struct { - let @layout: Layout - - # The pointer to the C address that contains the structure. - let pub @pointer: Pointer - - fn pub static new(pointer: Pointer, layout: Layout) -> Self { - Self { @pointer = pointer, @layout = layout } - } - - # Returns the size in bytes of this `Struct`. - fn pub size -> Int { - @layout.size - } - - # Returns the alignment of the members in this `Struct`. - fn pub alignment -> Int { - @layout.alignment - } -} - -impl Index[String, Any] for Struct { - # Reads the value of the given struct member. - # - # This method will panic if the given member does not exist. - fn pub index(index: String) -> Any { - let member = @layout.members[index] - - @pointer.read(type: member.type.clone, offset: member.offset) - } -} - -impl SetIndex[String, Any] for Struct { - # Writes a value to a struct member. - fn pub mut set_index(index: String, value: Any) { - let member = @layout.members[index] - let type = member.type.clone - let offset = member.offset - - @pointer.write(type, offset, value) - } -} - -# An object used for constructing an immutable `Layout`. -class pub LayoutBuilder { - # The names of all members, in the order they are defined in. - let @members: Array[String] - - # The types of all members, in the order they are defined in. - let @types: Array[Type] - - # A Map that tracks which members have already been defined. - let @existing: Set[String] - - # The alignment of the largest field. - let @alignment: Int - - # A boolean indicating if members should be padded or not. - let @padding: Bool - - fn pub static new -> Self { - Self { - @members = [], - @types = [], - @existing = Set.new, - @alignment = 0, - @padding = true - } - } - - # Disables the padding of members. - fn pub mut no_padding { - @padding = false - @alignment = 1 - } - - # Defines a new member. - # - # Trying to redefine a member will result in a panic. - fn pub mut field(name: String, value: Type) { - if @existing.contains?(name) { - panic("The field '{name}' is already defined") - } - - # If padding is enabled we determine the structure's alignment based on the - # alignment of the largest field. - if @padding and { value.alignment > @alignment } { - @alignment = value.alignment - } - - @existing.insert(name) - @members.push(name) - @types.push(value) - } - - # Creates a new `Layout` based on the current state of this builder. - fn pub move into_layout -> Layout { - if @padding { - into_layout_with_padding - } else { - into_layout_without_padding - } - } - - fn move into_layout_with_padding -> Layout { - let members = Map.new - let mut size = 0 - let mut offset = 0 - let mut remaining_in_hole = @alignment - let mut index = 0 - - while index < @members.length { - let name = @members[index] - let type = @types[index] - let type_align = type.alignment - - # If the value is too great to fit into the current hole, we need to add - # padding to the current hole, then start over at the next hole. - if type_align > remaining_in_hole { - let padding = @alignment - remaining_in_hole - - size += padding - offset += padding - - remaining_in_hole = @alignment - } - - members[name] = - Member { @name = name, @type = type.clone, @offset = offset } - - remaining_in_hole -= type_align - - size += type_align - offset += type_align - - if remaining_in_hole == 0 { remaining_in_hole = @alignment } - - index += 1 - } - - Layout { @members = members, @alignment = @alignment, @size = size } - } - - fn move into_layout_without_padding -> Layout { - let members = Map.new - let mut offset = 0 - let mut index = 0 - - while index < @members.length { - let name = @members[index] - let type = @types[index] - - members[name] = - Member { @name = name, @type = type.clone, @offset = offset } - - offset += type.alignment - index += 1 - } - - Layout { @members = members, @alignment = @alignment, @size = offset } - } -} - -# An identifier for a C type. -class pub enum Type { - # The C type `void`. - case Void - - # The C type `void *`. - case Pointer - - # The C type `double`. - case F64 - - # The C type `float`. - case F32 - - # The C type `char`. - case I8 - - # The C type `short`. - case I16 - - # The C type `int`. - case I32 - - # The C type `long`. - case I64 - - # The C type `unsigned char`. - case U8 - - # The C type `unsigned short`. - case U16 - - # The C type `unsigned int`. - case U32 - - # The C type `unsigned long`. - case U64 - - # The C type `size_t` - case SizeT - - # The C type `char *`. - case String - - # The C type `char *`, but for when you need a `ByteArray` instead of a - # `String`. - case ByteArray - - # Returns the size of this type in bytes. - fn pub size -> Int { - _INKO.ffi_type_size(to_int) - } - - # Returns the alignment of this type in bytes. - fn pub alignment -> Int { - _INKO.ffi_type_alignment(to_int) - } -} - -impl ToInt for Type { - fn pub to_int -> Int { - match self { - case Void -> 0 - case Pointer -> 1 - case F64 -> 2 - case F32 -> 3 - case I8 -> 4 - case I16 -> 5 - case I32 -> 6 - case I64 -> 7 - case U8 -> 8 - case U16 -> 9 - case U32 -> 10 - case U64 -> 11 - case String -> 12 - case ByteArray -> 13 - case SizeT -> 14 - } - } -} - -impl Clone for Type { - fn pub clone -> Self { - match self { - case Void -> Type.Void - case Pointer -> Type.Pointer - case F64 -> Type.F64 - case F32 -> Type.F32 - case I8 -> Type.I8 - case I16 -> Type.I16 - case I32 -> Type.I32 - case I64 -> Type.I64 - case U8 -> Type.U8 - case U16 -> Type.U16 - case U32 -> Type.U32 - case U64 -> Type.U64 - case SizeT -> Type.SizeT - case String -> Type.String - case ByteArray -> Type.ByteArray - } - } -} - -impl Equal for Type { - fn pub ==(other: ref Self) -> Bool { - to_int == other.to_int - } -} - -impl Format for Type { - fn pub fmt(formatter: mut Formatter) { - let write = match self { - case Void -> 'void' - case Pointer -> 'void*' - case F64 -> 'double' - case F32 -> 'float' - case I8 -> 'char' - case I16 -> 'short' - case I32 -> 'int' - case I64 -> 'long' - case U8 -> 'unsignhed char' - case U16 -> 'unsigned short' - case U32 -> 'unsigned int' - case U64 -> 'unsigned long' - case SizeT -> 'size_t' - case String -> 'String' - case ByteArray -> 'ByteArray' - } - - formatter.write(write) - } -} diff --git a/libstd/src/std/fs/dir.inko b/libstd/src/std/fs/dir.inko deleted file mode 100644 index 1d5520f13..000000000 --- a/libstd/src/std/fs/dir.inko +++ /dev/null @@ -1,117 +0,0 @@ -# Manipulating directories on a filesystem. -import std::fs::path::Path -import std::io::Error -import std::string::ToString - -# Creates a new empty directory at the given path. -# -# # Errors -# -# This method may throw if any of the following conditions are met: -# -# 1. The user lacks the necessary permissions to create the directory. -# 2. The directory already exists. -# -# # Examples -# -# Creating a directory: -# -# import std::fs::dir -# -# try! dir.create('/tmp/test') -fn pub create(path: ref ToString) !! Error { - try _INKO.directory_create(path.to_string) else (e) throw Error.from_int(e) -} - -# Creates a new empty directory at the given path, while also creating any -# intermediate directories. -# -# # Errors -# -# This method may throw if any of the following conditions are met: -# -# 1. The user lacks the necessary permissions to create the directory. -# -# # Examples -# -# Creating a directory: -# -# import std::fs::dir -# -# try! dir.create_all('/tmp/foo/bar/test') -fn pub create_all(path: ref ToString) !! Error { - let str = path.to_string - - try _INKO.directory_create_recursive(str) else (e) throw Error.from_int(e) -} - -# Removes the directory at the given path. -# -# # Errors -# -# This method may throw if any of the following conditions are met: -# -# 1. The user lacks the necessary permissions to remove the directory. -# 2. The directory is not empty. -# 3. The directory does not exist. -# -# # Examples -# -# Removing a directory: -# -# import std::fs::dir -# -# try! dir.create('/tmp/test') -# try! dir.remove('/tmp/test') -fn pub remove(path: ref ToString) !! Error { - try _INKO.directory_remove(path.to_string) else (e) throw Error.from_int(e) -} - -# Removes the directory and its contents at the given path. -# -# # Errors -# -# This method may throw if any of the following conditions are met: -# -# 1. The user lacks the necessary permissions to remove the directory. -# 2. The directory does not exist. -# -# # Examples -# -# Removing a directory: -# -# import std::fs::dir -# -# try! dir.create_all('/tmp/foo/bar') -# try! dir.remove_all('/tmp/foo') -fn pub remove_all(path: ref ToString) !! Error { - let str = path.to_string - - try _INKO.directory_remove_recursive(str) else (e) throw Error.from_int(e) -} - -# Returns an `Array` containing the paths to the contents of the directory. -# -# # Errors -# -# This method may throw if any of the following conditions are met: -# -# 1. The user lacks the necessary permissions to read the contents of the -# directory. -# 2. The path does not point to a valid directory. -# -# # Examples -# -# Listing the contents of a directory: -# -# import std::fs::dir -# -# let paths = try! dir.list('.') -# -# paths[0].to_string # => 'README.md' -fn pub list(path: ref ToString) !! Error -> Array[Path] { - let paths = - try _INKO.directory_list(path.to_string) else (e) throw Error.from_int(e) - - paths.into_iter.map fn (path) { Path.new(path) }.to_array -} diff --git a/libstd/src/std/fs/file.inko b/libstd/src/std/fs/file.inko deleted file mode 100644 index 2d7d33cbf..000000000 --- a/libstd/src/std/fs/file.inko +++ /dev/null @@ -1,261 +0,0 @@ -# Types and methods for manipulating files on a filesystem. -# -# Rather than using a single "File" type for all different file modes, Inko uses -# three separate file types: -# -# - `ReadOnlyFile`: read-only file operations -# - `WriteOnlyFile`: write-only file operations -# - `ReadWriteFile`: read-write file operations -# -# Using different types per file mode allows for a type-safe file API. -# -# Files are automatically closed when they are dropped. Any errors that may -# occur when closing a file are ignored. -import std::drop::Drop -import std::fs::path::(IntoPath, Path) -import std::io::(Error, Read, Seek, Size, Write) -import std::string::ToString - -# Removes the file for the given file path. -# -# # Examples -# -# Removing a file: -# -# import std::fs::file::(self, WriteOnlyFile) -# -# let handle = try! WriteOnlyFile.new('/tmp/test.txt') -# -# try! handle.write('hello') -# try! file.remove('/tmp/test.txt') -fn pub remove(path: ref ToString) !! Error { - try _INKO.file_remove(path.to_string) else (e) throw Error.from_int(e) -} - -# Copies a file from the source destination to the target destination, -# returning the number of copied bytes. -# -# # Examples -# -# Copying a file: -# -# import std::fs::file::(self, WriteOnlyFile) -# -# let handle = try! WriteOnlyFile.new('/tmp/test.txt') -# -# try! handle.write('hello') -# try! file.copy(from: '/tmp/test.txt', to: '/tmp/test2.txt') -fn pub copy(from: ref ToString, to: ref ToString) !! Error -> Int { - try { - _INKO.file_copy(from.to_string, to.to_string) - } else (e) { - throw Error.from_int(e) - } -} - -# A file that can only be used for reads. -class pub ReadOnlyFile { - # The path of the file. - let pub @path: Path - - # The internal file descriptor. - let @fd: Any - - # Returns a new `ReadOnlyFile`. - # - # # Examples - # - # Opening a file in read-only mode: - # - # import std::fs::file::ReadOnlyFile - # - # let handle = try! ReadOnlyFile.new('/dev/null') - fn pub static new(path: IntoPath) !! Error -> Self { - let path_obj = path.into_path - let fd = try { - _INKO.file_open_read_only(path_obj.to_string) - } else (e) { - throw Error.from_int(e) - } - - Self { @path = path_obj, @fd = fd } - } -} - -impl Drop for ReadOnlyFile { - fn mut drop { - _INKO.file_drop(@fd) - } -} - -impl Read for ReadOnlyFile { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try _INKO.file_read(@fd, into, size) else (e) throw Error.from_int(e) - } -} - -impl Seek for ReadOnlyFile { - fn pub mut seek(position: Int) !! Error -> Int { - try _INKO.file_seek(@fd, position) else (err) throw Error.from_int(err) - } -} - -impl Size for ReadOnlyFile { - fn pub size !! Error -> Int { - try _INKO.file_size(@path.to_string) else (err) throw Error.from_int(err) - } -} - -# A file that can only be used for writes. -class pub WriteOnlyFile { - # The path of the file. - let pub @path: Path - - # The internal file descriptor. - let @fd: Any - - # Opens a file in write-only mode. - # - # # Examples - # - # import std::fs::file::WriteOnlyFile - # - # let file = try! WriteOnlyFile.new('/dev/null') - fn pub static new(path: IntoPath) !! Error -> Self { - let path_obj = path.into_path - let fd = try { - _INKO.file_open_write_only(path_obj.to_string) - } else (e) { - throw Error.from_int(e) - } - - Self { @path = path_obj, @fd = fd } - } - - # Opens a file in append-only mode. - # - # # Examples - # - # import std::fs::file::WriteOnlyFile - # - # let file = try! WriteOnlyFile.append('/dev/null') - fn pub static append(path: IntoPath) !! Error -> Self { - let path_obj = path.into_path - let fd = try { - _INKO.file_open_append_only(path_obj.to_string) - } else (e) { - throw Error.from_int(e) - } - - Self { @path = path_obj, @fd = fd } - } -} - -impl Drop for WriteOnlyFile { - fn mut drop { - _INKO.file_drop(@fd) - } -} - -impl Write for WriteOnlyFile { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try _INKO.file_write_bytes(@fd, bytes) else (e) throw Error.from_int(e) - } - - fn pub mut write_string(string: String) !! Error -> Int { - try _INKO.file_write_string(@fd, string) else (e) throw Error.from_int(e) - } - - fn pub mut flush !! Error { - try _INKO.file_flush(@fd) else (e) throw Error.from_int(e) - } -} - -impl Seek for WriteOnlyFile { - fn pub mut seek(position: Int) !! Error -> Int { - try _INKO.file_seek(@fd, position) else (e) throw Error.from_int(e) - } -} - -# A file that can be used for both reads and writes. -class pub ReadWriteFile { - # The path of the file. - let pub @path: Path - - # The internal file descriptor. - let @fd: Any - - # Opens a file for both reading and writing: - # - # # Examples - # - # import std::fs::file::ReadWriteFile - # - # let handle = try! ReadWriteFile.new('/dev/null') - fn pub static new(path: IntoPath) !! Error -> Self { - let path_obj = path.into_path - let fd = try { - _INKO.file_open_read_write(path_obj.to_string) - } else (e) { - throw Error.from_int(e) - } - - Self { @path = path_obj, @fd = fd } - } - - # Opens a file for both reading and appending: - # - # # Examples - # - # import std::fs::file::ReadWriteFile - # - # let handle = try! ReadWriteFile.append('/dev/null') - fn pub static append(path: IntoPath) !! Error -> Self { - let path_obj = path.into_path - let fd = try { - _INKO.file_open_read_append(path_obj.to_string) - } else (e) { - throw Error.from_int(e) - } - - Self { @path = path_obj, @fd = fd } - } -} - -impl Drop for ReadWriteFile { - fn mut drop { - _INKO.file_drop(@fd) - } -} - -impl Read for ReadWriteFile { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try _INKO.file_read(@fd, into, size) else (e) throw Error.from_int(e) - } -} - -impl Write for ReadWriteFile { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try _INKO.file_write_bytes(@fd, bytes) else (e) throw Error.from_int(e) - } - - fn pub mut write_string(string: String) !! Error -> Int { - try _INKO.file_write_string(@fd, string) else (e) throw Error.from_int(e) - } - - fn pub mut flush !! Error { - try _INKO.file_flush(@fd) else (err) throw Error.from_int(err) - } -} - -impl Seek for ReadWriteFile { - fn pub mut seek(position: Int) !! Error -> Int { - try _INKO.file_seek(@fd, position) else (e) throw Error.from_int(e) - } -} - -impl Size for ReadWriteFile { - fn pub size !! Error -> Int { - try _INKO.file_size(@path.to_string) else (e) throw Error.from_int(e) - } -} diff --git a/libstd/src/std/index.inko b/libstd/src/std/index.inko deleted file mode 100644 index 174ce5562..000000000 --- a/libstd/src/std/index.inko +++ /dev/null @@ -1,54 +0,0 @@ -# Reading and writing of values using indexes. - -# A type that can be indexed. -trait pub Index[K, R] { - # Returns the value of the given index. - # - # # Panics - # - # This method may panic if the index doesn't exist. - fn pub index(index: K) -> R -} - -# A type that can be indexed mutably. -# -# The `index` method of the `Index` type takes its receiver as an immutable -# reference. This limits it to returning either newly created values, or -# immutable references to data stored in the receiver. -# -# The `IndexMut` trait defines the method `index_mut`, which takes its receiver -# as a mutable reference, allowing the method to return mutable references to -# data stored in the receiver. -# -# In other words: `Index` is used for when you just want to get data out of a -# type, while `IndexMut` is used if you also want to mutate that data. -trait pub IndexMut[K, R] { - # Returns a mutable reference to the index' value. - # - # # Panics - # - # This method may panic if the index doesn't exist. - fn pub mut index_mut(index: K) -> R -} - -# A type that allows writing a value to an index. -trait pub SetIndex[K, V] { - # Sets the given index to the given value. - # - # # Panics - # - # This method may panic if the index is out of bounds, though this depends on - # the type that implements this method. - fn pub mut set_index(index: K, value: V) -} - -# Checks if `index` is in the range of zero up to (but excluding) `length`. -# -# # Panics -# -# This method panics if the index is out of bounds. -fn pub bounds_check(index: Int, length: Int) { - if index >= 0 and index < length { return } - - _INKO.panic("The index {index} is out of bounds (length: {length})") -} diff --git a/libstd/src/std/process.inko b/libstd/src/std/process.inko deleted file mode 100644 index f0f8e309d..000000000 --- a/libstd/src/std/process.inko +++ /dev/null @@ -1,92 +0,0 @@ -# Lightweight Inko processes. -# -# Processes are lightweight, isolated tasks that can run concurrently. Processes -# use a type-safe form of message passing, instead of sharing memory. Futures -# are used whenever a message is sent and the sender wants to resolve the result -# at a later point in time. -import std::drop::Drop -import std::time::Duration - -# Terminates the program with an error message. -# -# A panic is an unrecoverable error meant to guard against code bugs. For -# runtime errors, use `try` and `throw` instead. -fn pub panic(message: String) -> Never { - _INKO.panic(message) -} - -# Suspends the current process for at least the given duration. -# -# The actual time the process is suspended for may be larger than the given -# duration. -# -# If the specified duration is less than or equal to zero, the process is -# rescheduled immediately. -fn pub sleep(time: ref Duration) { - _INKO.process_suspend(time.to_nanos) -} - -# Suspends the current process until one or more futures in the `pending` array -# are ready. -# -# The return value is an array containing all futures that are ready. -# -# After calling this method, `pending` only contain futures that aren't ready. -# -# If the `pending` array is empty, this method returns immediately. -# -# This method is useful when you have multiple futures to resolve, but want to -# act as soon as any futures are ready, instead of waiting until all of them are -# ready. -# -# # Performance -# -# Due to how polling works, performance of this method is `O(n)` where `n` is -# the number of futures in the `pending` array. -fn pub poll[T, E](pending: mut Array[Future[T, E]]) -> Array[Future[T, E]] { - _INKO.future_poll(pending) as Array[Future[T, E]] -} - -# The result of an asynchronous operation. -# -# A future can be resolved into its value using either `await` or `await_for`, -# both taking over ownership of the future. Both may block the calling process, -# but they won't block the OS thread the process is running on. -# -# If the message that the future belongs to threw a value, awaiting the future -# will re-throw said value. -class builtin Future[T, E] { - # Waits for and returns the result of a message. - # - # If no value is present at the time this method is called, the calling - # processes is suspended until a result is produced. - fn pub move await !! E -> T { - let val = try _INKO.future_get(self) else (e) { throw e as E } - - val as T - } - - # Waits for the result or until the timeout expires. - # - # Similar to `Future.await`, this method blocks the calling process until - # either a value is produced or the timeout expires. - # - # If the given duration is less than or equal to zero, the process is - # rescheduled (more or less, but not necessarily) immediately after this call. - fn pub move await_for(time: ref Duration) !! E -> Option[T] { - let nanos = time.to_nanos - let val = try { _INKO.future_get_for(self, nanos) } else (e) { throw e as E } - - if _INKO.is_undefined(val) { Option.None } else { Option.Some(val as T) } - } -} - -impl Drop for Future { - fn mut drop { - let result = _INKO.future_drop(self) - - if _INKO.is_undefined(result) { return } - - result as T - } -} diff --git a/libstd/src/std/stdio.inko b/libstd/src/std/stdio.inko deleted file mode 100644 index 50b174070..000000000 --- a/libstd/src/std/stdio.inko +++ /dev/null @@ -1,74 +0,0 @@ -# STDIN, STDOUT, and STDERR streams. -import std::io::(Error, Read, Write) - -# The standard input stream of the current OS process. -class pub STDIN { - # Returns a new handle to the input stream. - fn pub static new -> Self { - Self {} - } -} - -impl Read for STDIN { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try _INKO.stdin_read(into, size) else (error) throw Error.from_int(error) - } -} - -# The standard output stream of the current OS process. -# -# To make it easier to write to STDOUT, any errors produced while writing are -# ignored. -class pub STDOUT { - # Returns a new handle to the output stream. - fn pub static new -> Self { - Self {} - } -} - -impl Write for STDOUT { - fn pub mut write_bytes(bytes: ref ByteArray) -> Int { - try _INKO.stdout_write_bytes(bytes) else 0 - } - - fn pub mut write_string(string: String) -> Int { - try _INKO.stdout_write_string(string) else 0 - } - - fn pub mut flush { - try _INKO.stdout_flush else return - } - - fn pub mut print(string: String) -> Int { - write_string(string) + write_string("\n") - } -} - -# The standard error stream of the current OS process. -# -# To make it easier to write to STDERR, any errors produced while writing are -# ignored. -class pub STDERR { - # Returns a new handle to the error stream. - fn pub static new -> Self { - Self {} - } -} - -impl Write for STDERR { - fn pub mut write_bytes(bytes: ref ByteArray) -> Int { - try _INKO.stderr_write_bytes(bytes) else 0 - } - - fn pub mut write_string(string: String) -> Int { - try _INKO.stderr_write_string(string) else 0 - } - - fn pub mut flush { - try _INKO.stderr_flush else return - } - - fn pub mut print(string: String) -> Int { - write_string(string) + write_string("\n") - } -} diff --git a/libstd/test/helpers.inko b/libstd/test/helpers.inko deleted file mode 100644 index 772950b6c..000000000 --- a/libstd/test/helpers.inko +++ /dev/null @@ -1,114 +0,0 @@ -import std::drop::(drop) -import std::env -import std::fmt::(DefaultFormatter, Format, Formatter) -import std::fs::file::(WriteOnlyFile, remove) -import std::fs::path::Path -import std::hash::(Hash, Hasher) -import std::map::DefaultHasher -import std::sys::(Command, Stream) - -fn pub hash(value: ref Hash) -> Int { - let hasher: Hasher = DefaultHasher.new - - value.hash(hasher) - hasher.hash -} - -fn pub fmt(value: ref Format) -> String { - let fmt: Formatter = DefaultFormatter.new - - value.fmt(fmt) - fmt.into_string -} - -class pub Script { - let @id: Int - let @path: Path - let @code: String - let @cmd: Command - let @stdin: Option[String] - let @imports: Array[String] - - fn pub static new(id: Int, code: String) -> Self { - let path = env.temporary_directory.join("inko_test_script_{id}.inko") - let exe = try! env.executable - let cmd = Command.new(exe) - - cmd.argument('run') - cmd.argument('-f') - cmd.argument('plain') - cmd.argument(path.to_string) - cmd.stdin(Stream.Null) - cmd.stderr(Stream.Piped) - cmd.stdout(Stream.Piped) - - Self { - @id = id, - @path = path, - @code = code, - @cmd = cmd, - @stdin = Option.None, - @imports = ['std::stdio::(STDIN, STDERR, STDOUT)'] - } - } - - fn pub move import(module: String) -> Self { - @imports.push(module) - self - } - - fn pub move stdin(input: String) -> Self { - @stdin = Option.Some(input) - - @cmd.stdin(Stream.Piped) - self - } - - fn pub move argument(value: String) -> Self { - @cmd.argument(value) - self - } - - fn pub move variable(name: String, value: String) -> Self { - @cmd.variable(name, value) - self - } - - fn pub move run -> String { - let file = try! WriteOnlyFile.new(@path.clone) - - @imports.into_iter.each fn (mod) { - try! file.write_string("import {mod}\n") - } - - try! file.write_string( -"class async Main \{ - fn async main \{ - {@code} - } -} -" - ) - - try! file.flush - - let exe = try! env.executable - let child = try! @cmd.spawn - let bytes = ByteArray.new - - match @stdin { - case Some(input) -> try! child.stdin.write_string(input) - case _ -> 0 - } - - try! child.wait - try! child.stdout.read_all(bytes) - try! child.stderr.read_all(bytes) - - drop(file) - - try! remove(@path) - - bytes.into_string - } -} diff --git a/libstd/test/std/net/test_socket.inko b/libstd/test/std/net/test_socket.inko deleted file mode 100644 index f6d3f317a..000000000 --- a/libstd/test/std/net/test_socket.inko +++ /dev/null @@ -1,1461 +0,0 @@ -import helpers::(fmt) -import std::drop::Drop -import std::env -import std::fs::file -import std::fs::path::Path -import std::net::ip::(IpAddress, Ipv4Address, Ipv6Address) -import std::net::socket::( - Socket, SocketAddress, TcpServer, TcpClient, Type, UdpSocket, UnixAddress, - UnixDatagram, UnixServer, UnixSocket, UnixClient -) -import std::string::ToString -import std::sys -import std::test::Tests -import std::time::(Duration, Instant) - -class SocketPath { - let @path: Path - - fn static pair(id: Int) -> (Self, Self) { - let p1 = env.temporary_directory.join("inko-test-{id}-1.sock") - let p2 = env.temporary_directory.join("inko-test-{id}-2.sock") - - try file.remove(p1) else nil - try file.remove(p2) else nil - - (Self { @path = p1 }, Self { @path = p2 }) - } - - fn static new(id: Int) -> Self { - let path = env.temporary_directory.join("inko-test-{id}.sock") - - try file.remove(path) else nil - - Self { @path = path } - } -} - -impl ToString for SocketPath { - fn pub to_string -> String { - @path.to_string - } -} - -impl Drop for SocketPath { - fn mut drop { - try file.remove(@path) else nil - } -} - -fn pub tests(t: mut Tests) { - t.test('SocketAddress.new') fn (t) { - let addr = SocketAddress.new(address: '127.0.0.1', port: 1234) - - t.equal(addr.ip, Option.Some(IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)))) - t.equal(addr.port, 1234) - } - - t.test('SocketAddress.==') fn (t) { - let addr1 = SocketAddress.new(address: '127.0.0.1', port: 1234) - let addr2 = SocketAddress.new(address: '127.0.0.1', port: 4567) - - t.equal(addr1, addr1) - t.not_equal(addr1, addr2) - } - - t.test('SocketAddress.fmt') fn (t) { - t.equal( - fmt(SocketAddress.new(address: '127.0.0.1', port: 1234)), - '127.0.0.1:1234' - ) - } - - t.test('Socket.ipv4') fn (t) { - t.no_throw fn { try Socket.ipv4(Type.STREAM) } - t.no_throw fn { try Socket.ipv4(Type.DGRAM) } - } - - t.test('Socket.ipv6') fn (t) { - t.no_throw fn { try Socket.ipv6(Type.STREAM) } - t.no_throw fn { try Socket.ipv6(Type.DGRAM) } - } - - t.test('Socket.bind') fn (t) { - t.throw fn { - let sock = try! Socket.ipv4(Type.STREAM) - - try sock.bind(ip: 'foo', port: 0) - } - - t.no_throw fn { - let sock = try! Socket.ipv4(Type.STREAM) - - try sock.bind(ip: '0.0.0.0', port: 0) - } - } - - t.test('Socket.connect') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream1 = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - t.no_throw fn { - try stream1.connect(ip: addr.ip.unwrap, port: addr.port.clone) - } - - let stream2 = try! Socket.ipv4(Type.STREAM) - - t.throw fn { - # connect() may not immediately raise a "connection refused" error, due - # to connect() being non-blocking. In this case the "connection refused" - # error is raised on the next operation. - # - # Since a connect() _might_ still raise the error right away, we have to - # both connect and try to use the socket in some way. - try stream2.connect(ip: '0.0.0.0', port: 40_000) - try stream2.write_string('ping') - } - } - - t.test('Socket.listen') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.bind(ip: '0.0.0.0', port: 0) - - t.no_throw fn { try socket.listen } - } - - t.test('Socket.accept') fn (t) { - let server = try! Socket.ipv4(Type.STREAM) - let client = try! Socket.ipv4(Type.STREAM) - - try! server.bind(ip: '127.0.0.1', port: 0) - try! server.listen - - let addr = try! server.local_address - - try! client.connect(addr.address.clone, addr.port.clone) - - let connection = try! server.accept - - t.equal(try! connection.local_address, try! server.local_address) - } - - t.test('Socket.send_string_to') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - # On Windows one can not use sendto() with 0.0.0.0 being the target IP - # address, so instead we bind (and send to) 127.0.0.1. - try! socket.bind(ip: '127.0.0.1', port: 0) - - let send_to = try! socket.local_address - let buffer = ByteArray.new - - try! socket - .send_string_to('ping', ip: send_to.address, port: send_to.port.clone) - - t.equal(try! socket.read(into: buffer, size: 4), 4) - t.equal(buffer.into_string, 'ping') - } - - t.test('Socket.send_bytes_to') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - # On Windows one can not use sendto() with 0.0.0.0 being the target IP - # address, so instead we bind (and send to) 127.0.0.1. - try! socket.bind(ip: '127.0.0.1', port: 0) - - let send_to = try! socket.local_address - let buffer = ByteArray.new - - try! socket.send_bytes_to( - bytes: 'ping'.to_byte_array, - ip: send_to.address, - port: send_to.port.clone - ) - - t.equal(try! socket.read(into: buffer, size: 4), 4) - t.equal(buffer.into_string, 'ping') - } - - t.test('Socket.receive_from') fn (t) { - let listener = try! Socket.ipv4(Type.DGRAM) - let client = try! Socket.ipv4(Type.DGRAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! client.bind(ip: '127.0.0.1', port: 0) - - let send_to = try! listener.local_address - - try! client - .send_string_to('ping', ip: send_to.address, port: send_to.port.clone) - - let bytes = ByteArray.new - let sender = try! listener.receive_from(bytes: bytes, size: 4) - - t.equal(sender, try! client.local_address) - t.equal(bytes.into_string, 'ping') - } - - t.test('Socket.local_address with an unbound socket') fn (t) { - if sys.windows? { - let socket = try! Socket.ipv4(Type.DGRAM) - - t.throw fn { try socket.local_address } - } else { - let socket = try! Socket.ipv4(Type.DGRAM) - let address = try! socket.local_address - - t.equal(address.address, '0.0.0.0') - t.equal(address.port, 0) - } - } - - t.test('Socket.local_address with a bound socket') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - try! socket.bind(ip: '127.0.0.1', port: 0) - - let local_address = try! socket.local_address - - t.equal(local_address.address, '127.0.0.1') - t.true(local_address.port > 0) - } - - t.test('Socket.peer_address with a disconnected socket') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - t.throw fn { try socket.peer_address } - } - - t.test('Socket.peer_address with a connected socket') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let client = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! client.connect(ip: addr.address, port: addr.port.clone) - - t.equal(try! client.peer_address, addr) - } - - t.test('Socket.ttl') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.ttl = 10 - - t.equal(try! socket.ttl, 10) - } - - t.test('Socket.only_ipv6?') fn (t) { - let socket = try! Socket.ipv6(Type.STREAM) - - try! socket.only_ipv6 = true - - t.true(try! socket.only_ipv6?) - } - - t.test('Socket.no_delay?') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.no_delay = true - - t.true(try! socket.no_delay?) - } - - t.test('Socket.broadcast?') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - try! socket.broadcast = true - - t.true(try! socket.broadcast?) - } - - t.test('Socket.linger') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - let duration = Duration.from_secs(5) - - try! socket.linger = duration - - t.equal(try! socket.linger, duration) - } - - t.test('Socket.receive_buffer_size') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.receive_buffer_size = 256 - - t.true(try! { socket.receive_buffer_size } >= 256) - } - - t.test('Socket.send_buffer_size') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.send_buffer_size = 256 - - t.true(try! { socket.send_buffer_size } >= 256) - } - - # Obtaining the TCP keepalive setting fails on Windows. See - # https://github.com/alexcrichton/socket2-rs/issues/24 for more information. - if sys.windows?.false? { - t.test('Socket.keepalive') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - try! socket.keepalive = true - - t.true(try! socket.keepalive) - } - } - - t.test('Socket.reuse_adress') fn (t) { - let socket = try! Socket.ipv6(Type.DGRAM) - - try! socket.reuse_address = true - - t.true(try! socket.reuse_address) - } - - t.test('Socket.reuse_port') fn (t) { - let socket = try! Socket.ipv6(Type.DGRAM) - - try! socket.reuse_port = true - - # Windows does not support SO_REUSEPORT, so the return value is always - # `false`. - if sys.windows? { - t.false(try! socket.reuse_port) - } else { - t.true(try! socket.reuse_port) - } - } - - t.test('Socket.shutdown_read') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - try! stream.shutdown_read - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes.length, 0) - } - - t.test('Socket.shutdown_write') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - try! stream.shutdown_write - - t.throw fn { try stream.write_string('ping') } - } - - t.test('Socket.shutdown shuts down the writing half') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - - t.throw fn { - try stream.shutdown - try stream.write_string('ping') - } - } - - t.test('Socket.shutdown shuts down the reading half') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - try! stream.shutdown - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes.length, 0) - } - - t.test('Socket.try_clone') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - t.no_throw fn { try socket.try_clone } - } - - t.test('Socket.read') fn (t) { - let socket = try! Socket.ipv4(Type.DGRAM) - - try! socket.bind(ip: '127.0.0.1', port: 0) - - let addr = try! socket.local_address - let bytes = ByteArray.new - - try! socket.send_string_to('ping', ip: addr.address, port: addr.port.clone) - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('Socket.write_bytes') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - - let written = try! stream.write_bytes('ping'.to_byte_array) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('Socket.write_string') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - let stream = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - try! stream.connect(ip: addr.address, port: addr.port.clone) - - let written = try! stream.write_string('ping') - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('Socket.flush') fn (t) { - let socket = try! Socket.ipv4(Type.STREAM) - - t.equal(socket.flush, nil) - } - - t.test('UdpSocket.new') fn (t) { - t.no_throw fn { - try UdpSocket - .new(ip: IpAddress.V4(Ipv4Address.new(0, 0, 0, 0)), port: 0) - } - - t.no_throw fn { - try! UdpSocket - .new(ip: IpAddress.V6(Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 0)), port: 0) - } - } - - t.test('UdpSocket.connect') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket1 = try! UdpSocket.new(ip: ip.clone, port: 0) - let socket2 = try! UdpSocket.new(ip: ip, port: 0) - let addr = try! socket2.local_address - - t.no_throw fn { - try socket1.connect(ip: addr.address, port: addr.port.clone) - } - } - - t.test('UdpSocket.send_string_to') fn (t) { - let socket = try! UdpSocket - .new(ip: IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)), port: 0) - - let addr = try! socket.local_address - - try! socket.send_string_to('ping', ip: addr.address, port: addr.port.clone) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UdpSocket.send_bytes_to') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket = try! UdpSocket.new(ip: ip, port: 0) - let addr = try! socket.local_address - - try! socket.send_bytes_to( - 'ping'.to_byte_array, - ip: addr.address, - port: addr.port.clone - ) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UdpSocket.receive_from') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let listener = try! UdpSocket.new(ip: ip.clone, port: 0) - let client = try! UdpSocket.new(ip: ip, port: 0) - let addr = try! listener.local_address - - try! client - .send_string_to('ping', ip: addr.address, port: addr.port.clone) - - let bytes = ByteArray.new - let sender = try! listener.receive_from(bytes: bytes, size: 4) - - t.equal(sender, try! client.local_address) - t.equal(bytes.into_string, 'ping') - } - - t.test('UdpSocket.local_address') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket = try! UdpSocket.new(ip: ip, port: 0) - let local_address = try! socket.local_address - - t.equal(local_address.address, '127.0.0.1') - t.true(local_address.port > 0) - } - - t.test('UdpSocket.try_clone') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket = try! UdpSocket.new(ip: ip, port: 0) - - t.no_throw fn { try socket.try_clone } - } - - t.test('UdpSocket.read') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket = try! UdpSocket.new(ip: ip, port: 0) - let addr = try! socket.local_address - - try! socket.send_string_to('ping', ip: addr.address, port: addr.port.clone) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.to_string, 'ping') - } - - t.test('UdpSocket.write_bytes') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let server_socket = try! UdpSocket.new(ip: ip.clone, port: 0) - let client_socket = try! UdpSocket.new(ip: ip, port: 0) - let addr = try! server_socket.local_address - - try! client_socket.connect(ip: addr.address, port: addr.port.clone) - try! client_socket.write_bytes('ping'.to_byte_array) - - let bytes = ByteArray.new - - t.equal(try! server_socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UdpSocket.flush') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let socket = try! UdpSocket.new(ip: ip, port: 0) - - t.equal(socket.flush, nil) - } - - t.test('TcpClient.new') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - t.no_throw fn { - try TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - } - } - - t.test('TcpClient.with_timeout') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - - t.no_throw fn { - try TcpClient.with_timeout( - ip: addr.ip.unwrap, - port: addr.port.clone, - timeout_after: Duration.from_secs(2) - ) - } - - t.throw fn { - # This address is unroutable and so the connect times out. - try TcpClient.with_timeout( - ip: IpAddress.v4(192, 168, 0, 0), - port: addr.port.clone, - timeout_after: Duration.from_micros(500) - ) - } - } - - t.test('TcpClient.local_address') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let local_addr = try! stream.local_address - - t.equal(local_addr.address, '127.0.0.1') - t.true(local_addr.port > 0) - } - - t.test('TcpClient.peer_address') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let peer_addr = try! stream.peer_address - - t.equal(peer_addr.address, addr.address) - t.equal(peer_addr.port, addr.port) - } - - t.test('TcpClient.read') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let bytes = ByteArray.new - let client = try! listener.accept - - try! client.write_string('ping') - try! stream.read(into: bytes, size: 4) - - t.equal(bytes.into_string, 'ping') - } - - t.test('TcpClient.write_bytes') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! stream.write_bytes('ping'.to_byte_array), 4) - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('TcpClient.write_string') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! stream.write_string('ping'), 4) - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('TcpClient.flush') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - t.equal(stream.flush, nil) - } - - t.test('TcpClient.shutdown_read') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - try! stream.shutdown_read - - let bytes = ByteArray.new - - try! stream.read(into: bytes, size: 4) - - t.equal(bytes, ByteArray.new) - } - - t.test('TcpClient.shutdown_write') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - try! stream.shutdown_write - - t.throw fn { try stream.write_string('ping') } - } - - t.test('TcpClient.shutdown shuts down the writing half') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - try! stream.shutdown - - t.throw fn { try stream.write_string('ping') } - } - - t.test('TcpClient.shutdown shuts down the reading half') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - try! stream.shutdown - - let bytes = ByteArray.new - - let message = try! stream.read(into: bytes, size: 4) - - t.equal(bytes, ByteArray.new) - } - - t.test('TcpClient.try_clone') fn (t) { - let listener = try! Socket.ipv4(Type.STREAM) - - try! listener.bind(ip: '127.0.0.1', port: 0) - try! listener.listen - - let addr = try! listener.local_address - let client = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - - t.no_throw fn { try client.try_clone } - } - - t.test('TcpServer.new') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(0, 0, 0, 0)) - - t.no_throw fn { try TcpServer.new(ip: ip.clone, port: 0) } - - let listener = try! TcpServer.new(ip: ip, port: 0) - let addr = try! listener.local_address - - t.no_throw fn { - try TcpServer.new(ip: addr.ip.unwrap, port: addr.port.clone) - } - } - - t.test('TcpServer.accept') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let listener = try! TcpServer.new(ip: ip, port: 0) - let addr = try! listener.local_address - let stream = try! TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone) - let connection = try! listener.accept - - t.equal(try! connection.local_address, try! stream.peer_address) - } - - t.test('TcpServer.local_address') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let listener = try! TcpServer.new(ip: ip, port: 0) - let addr = try! listener.local_address - - t.equal(addr.address, '127.0.0.1') - t.true(addr.port > 0) - } - - t.test('TcpServer.try_clone') fn (t) { - let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) - let server = try! TcpServer.new(ip: ip, port: 0) - - t.no_throw fn { try server.try_clone } - } - - t.test('UnixAddress.to_path') fn (t) { - t.equal(UnixAddress.new('foo.sock').to_path, Option.Some('foo.sock'.to_path)) - t.true(UnixAddress.new("\0foo").to_path.none?) - t.true(UnixAddress.new('').to_path.none?) - } - - t.test('UnixAddress.to_string') fn (t) { - t.equal(UnixAddress.new('foo.sock').to_string, 'foo.sock') - t.equal(UnixAddress.new("\0foo").to_string, "\0foo") - t.equal(UnixAddress.new('').to_string, '') - } - - t.test('UnixAddress.abstract?') fn (t) { - t.false(UnixAddress.new('').abstract?) - t.false(UnixAddress.new('foo.sock').abstract?) - t.true(UnixAddress.new("\0foo").abstract?) - } - - t.test('UnixAddress.unnamed?') fn (t) { - t.false(UnixAddress.new('foo.sock').unnamed?) - t.false(UnixAddress.new("\0foo").unnamed?) - t.true(UnixAddress.new('').unnamed?) - } - - t.test('UnixAddress.fmt') fn (t) { - t.equal(fmt(UnixAddress.new('foo.sock')), 'foo.sock') - t.equal(fmt(UnixAddress.new("\0foo")), '@foo') - t.equal(fmt(UnixAddress.new('')), 'unnamed') - } - - t.test('UnixAddress.==') fn (t) { - t.equal(UnixAddress.new('a.sock'), UnixAddress.new('a.sock')) - t.not_equal(UnixAddress.new('a.sock'), UnixAddress.new('b.sock')) - } - - t.test('UnixAddress.to_string') fn (t) { - t.equal(UnixAddress.new('foo.sock').to_string, 'foo.sock') - t.equal(UnixAddress.new("\0foo").to_string, "\0foo") - t.equal(UnixAddress.new('').to_string, '') - } - - if sys.unix? { unix_tests(t) } -} - -fn unix_tests(t: mut Tests) { - t.test('UnixSocket.new') fn (t) { - t.no_throw fn { try UnixSocket.new(Type.DGRAM) } - t.no_throw fn { try UnixSocket.new(Type.STREAM) } - } - - t.test('UnixSocket.bind') fn (t) { - let socket1 = try! UnixSocket.new(Type.STREAM) - let socket2 = try! UnixSocket.new(Type.STREAM) - let path = SocketPath.new(t.id) - - t.no_throw fn { try socket1.bind(path) } - t.throw fn { try socket2.bind(path) } - - if sys.linux? { - let socket = try! UnixSocket.new(Type.STREAM) - - t.no_throw fn { try socket.bind("\0inko-test-{t.id}") } - } - } - - t.test('UnixSocket.connect') fn (t) { - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - let path = SocketPath.new(t.id) - - t.throw fn { try stream.connect(path) } - - try! listener.bind(path) - try! listener.listen - - t.no_throw fn { try stream.connect(path) } - - if sys.linux? { - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - let addr = "\0inko-test-{t.id}" - - try! listener.bind(addr) - try! listener.listen - - t.no_throw fn { try stream.connect(addr) } - } - } - - t.test('UnixSocket.listen') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.STREAM) - - try! socket.bind(path) - - t.no_throw fn { try socket.listen } - } - - t.test('UnixSocket.accept') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - - let client = try! listener.accept - - t.equal(try! client.peer_address, try! stream.local_address) - } - - t.test('UnixSocket.send_string_to') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.DGRAM) - - try! socket.bind(path) - try! socket.send_string_to('ping', path) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.send_bytes_to') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.DGRAM) - - try! socket.bind(path) - try! socket.send_bytes_to('ping'.to_byte_array, path) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.receive_from') fn (t) { - let pair = SocketPath.pair(t.id) - let listener = try! UnixSocket.new(Type.DGRAM) - let client = try! UnixSocket.new(Type.DGRAM) - - try! listener.bind(pair.0) - try! client.bind(pair.1) - try! client.send_string_to('ping', pair.0) - - let bytes = ByteArray.new - let sender = try! listener.receive_from(bytes: bytes, size: 4) - - t.equal(sender, try! client.local_address) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.local_address with an unbound socket') fn (t) { - let socket = try! UnixSocket.new(Type.DGRAM) - let address = try! socket.local_address - - t.equal(address, UnixAddress.new('')) - } - - t.test('UnixSocket.local_address with a bound socket') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.DGRAM) - - try! socket.bind(path) - - t.equal(try! socket.local_address, UnixAddress.new(path.to_string)) - } - - t.test('UnixSocket.peer_address with a disconnected socket') fn (t) { - let socket = try! UnixSocket.new(Type.DGRAM) - - t.throw fn { try socket.peer_address } - } - - t.test('UnixSocket.peer_address with a connected socket') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let client = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! client.connect(path) - - t.equal(try! client.peer_address, try! listener.local_address) - } - - t.test('UnixSocket.read') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.DGRAM) - - try! socket.bind(path) - try! socket.send_string_to('ping', path) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.write_bytes') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - - let written = try! stream.write_bytes('ping'.to_byte_array) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.write_string') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - - let written = try! stream.write_string('ping') - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixSocket.flush') fn (t) { - let socket = try! UnixSocket.new(Type.STREAM) - - t.equal(socket.flush, nil) - } - - t.test('UnixSocket.receive_buffer_size') fn (t) { - let socket = try! UnixSocket.new(Type.STREAM) - - try! socket.receive_buffer_size = 256 - - t.true(try! { socket.receive_buffer_size } >= 256) - } - - t.test('UnixSocket.send_buffer_size') fn (t) { - let socket = try! UnixSocket.new(Type.STREAM) - - try! socket.send_buffer_size = 256 - - t.true(try! { socket.send_buffer_size } >= 256) - } - - t.test('UnixSocket.shutdown_read') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - try! stream.shutdown_read - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes, ByteArray.new) - } - - t.test('UnixSocket.shutdown_write') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - try! stream.shutdown_write - - t.throw fn { try stream.write_string('ping') } - } - - t.test('UnixSocket.shutdown shuts down the writing half') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - try! stream.shutdown - - t.throw fn { try stream.write_string('ping') } - } - - t.test('UnixSocket.shutdown shuts down the reading half') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - let stream = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - try! stream.connect(path) - try! stream.shutdown - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes, ByteArray.new) - } - - t.test('UnixSocket.try_clone') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixSocket.new(Type.STREAM) - - t.no_throw fn { try socket.try_clone } - } - - t.test('UnixDatagram.new') fn (t) { - let path = SocketPath.new(t.id) - - t.no_throw fn { try UnixDatagram.new(path) } - t.throw fn { try UnixDatagram.new(path) } - } - - t.test('UnixDatagram.connect') fn (t) { - let pair = SocketPath.pair(t.id) - let socket1 = try! UnixDatagram.new(pair.0) - let socket2 = try! UnixDatagram.new(pair.1) - - t.no_throw fn { try socket2.connect(pair.0) } - } - - t.test('UnixDatagram.send_to') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixDatagram.new(path) - - try! socket.send_string_to('ping', address: path) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixDatagram.receive_from') fn (t) { - let pair = SocketPath.pair(t.id) - let listener = try! UnixDatagram.new(pair.0) - let client = try! UnixDatagram.new(pair.1) - - try! client.send_string_to('ping', pair.0) - - let bytes = ByteArray.new - let sender = try! listener.receive_from(bytes: bytes, size: 4) - - t.equal(sender, try! client.local_address) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixDatagram.local_address') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixDatagram.new(path) - - t.equal(try! socket.local_address, UnixAddress.new(path.to_string)) - } - - t.test('UnixDatagram.try_clone') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixDatagram.new(path) - - t.no_throw fn { try socket.try_clone } - } - - t.test('UnixDatagram.read') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixDatagram.new(path) - - try! socket.send_string_to('ping', address: path) - - let bytes = ByteArray.new - - t.equal(try! socket.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixDatagram.write_bytes') fn (t) { - let pair = SocketPath.pair(t.id) - let server = try! UnixDatagram.new(pair.0) - let client = try! UnixDatagram.new(pair.1) - - try! client.connect(pair.0) - - let bytes = ByteArray.new - - t.equal(try! client.write_bytes('ping'.to_byte_array), 4) - t.equal(try! server.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixDatagram.flush') fn (t) { - let path = SocketPath.new(t.id) - let socket = try! UnixDatagram.new(path) - - t.equal(socket.flush, nil) - } - - t.test('UnixClient.new') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - t.no_throw fn { try UnixClient.new(path) } - } - - t.test('UnixClient.with_timeout') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - # Unlike IP sockets there's no unroutable address we can use, so at best we - # can assert the following doesn't time out. This means this test is mostly - # a smoke test, unlikely to actually fail unless our runtime is bugged. - t.no_throw fn { - try UnixClient - .with_timeout(address: path, timeout_after: Duration.from_secs(5)) - } - } - - t.test('UnixClient.local_address') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - t.equal(try! stream.local_address, UnixAddress.new('')) - } - - t.test('UnixClient.peer_address') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - t.equal(try! stream.peer_address, UnixAddress.new(path.to_string)) - } - - t.test('UnixClient.read') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - let connection = try! listener.accept - - try! connection.write_string('ping') - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixClient.write_bytes') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! stream.write_bytes('ping'.to_byte_array), 4) - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixClient.write_string') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - let connection = try! listener.accept - let bytes = ByteArray.new - - t.equal(try! stream.write_string('ping'), 4) - t.equal(try! connection.read(into: bytes, size: 4), 4) - t.equal(bytes.into_string, 'ping') - } - - t.test('UnixClient.flush') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - t.equal(stream.flush, nil) - } - - t.test('UnixClient.shutdown_read') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - try! stream.shutdown_read - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes, ByteArray.new) - } - - t.test('UnixClient.shutdown_write') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - try! stream.shutdown_write - - t.throw fn { try stream.write_string('ping') } - } - - t.test('UnixClient.shutdown shuts down the writing half') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - try! stream.shutdown - - t.throw fn { try stream.write_string('ping') } - } - - t.test('UnixClient.shutdown shuts down the reading half') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let stream = try! UnixClient.new(path) - - try! stream.shutdown - - let bytes = ByteArray.new - - t.equal(try! stream.read(into: bytes, size: 4), 0) - t.equal(bytes, ByteArray.new) - } - - t.test('UnixClient.try_clone') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixSocket.new(Type.STREAM) - - try! listener.bind(path) - try! listener.listen - - let client = try! UnixClient.new(path) - - t.no_throw fn { try client.try_clone } - } - - t.test('UnixServer.new') fn (t) { - let path = SocketPath.new(t.id) - - t.no_throw fn { try UnixServer.new(path) } - } - - t.test('UnixServer.accept') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixServer.new(path) - let stream = try! UnixClient.new(path) - let connection = try! listener.accept - - t.equal(try! connection.local_address, try! stream.peer_address) - } - - t.test('UnixServer.local_address') fn (t) { - let path = SocketPath.new(t.id) - let listener = try! UnixServer.new(path) - let addr = try! listener.local_address - - t.equal(addr, UnixAddress.new(path.to_string)) - } - - t.test('UnixServer.try_clone') fn (t) { - let path = SocketPath.new(t.id) - let server = try! UnixServer.new(path) - - t.no_throw fn { try server.try_clone } - } -} diff --git a/libstd/test/std/test_env.inko b/libstd/test/std/test_env.inko deleted file mode 100644 index b274d01ce..000000000 --- a/libstd/test/std/test_env.inko +++ /dev/null @@ -1,92 +0,0 @@ -import helpers::Script -import std::env -import std::test::Tests - -fn pub tests(t: mut Tests) { - t.test('env.get') fn (t) { - let code = "STDOUT.new.write_string(env.get('INKO_TEST').unwrap_or('?'))" - let present = - Script.new(t.id, code).variable('INKO_TEST', 'foo').import('std::env').run - let missing = Script.new(t.id, code).import('std::env').run - - t.equal(present, 'foo') - t.equal(missing, '?') - } - - t.test('env.variables') fn (t) { - let code = ' - let out = STDOUT.new - let vars = env.variables - - out.print(vars["INKO_FOO"]) - out.print(vars["INKO_BAR"]) - ' - - let output = Script - .new(t.id, code) - .variable('INKO_FOO', 'foo') - .variable('INKO_BAR', 'bar') - .import('std::env') - .run - - t.equal(output, "foo\nbar\n") - } - - t.test('env.home_directory') fn (t) { - # Home directories are optional, and even if they're set the actual path may - # not exist. As such there's not really anything we can test for, other than - # asserting the path isn't empty. - match env.home_directory { - case Some(path) -> t.true(path.to_string.size > 0) - case _ -> {} - } - } - - t.test('env.working_directory') fn (t) { - let path = try! env.working_directory - - t.true(path.directory?) - } - - t.test('env.working_directory=') fn (t) { - let tmp = env.temporary_directory.to_string - let code = ' - try! env.working_directory = env.get("INKO_PWD").unwrap - - STDOUT.new.write_string(try! { env.working_directory }.to_string) - ' - - let output = Script - .new(t.id, code) - .variable('INKO_PWD', tmp.clone) - .import('std::env') - .run - - t.equal(output, tmp) - } - - t.test('env.arguments') fn (t) { - let code = ' - let out = STDOUT.new - let args = env.arguments - - out.print(args[0]) - out.print(args[1]) - ' - - let output = Script - .new(t.id, code) - .argument('foo') - .argument('bar') - .import('std::env') - .run - - t.equal(output, "foo\nbar\n") - } - - t.test('env.executable') fn (t) { - let path = try! env.executable - - t.true(path.file?) - } -} diff --git a/libstd/test/std/test_ffi.inko b/libstd/test/std/test_ffi.inko deleted file mode 100644 index 5910f0d19..000000000 --- a/libstd/test/std/test_ffi.inko +++ /dev/null @@ -1,285 +0,0 @@ -import std::drop::Drop -import std::ffi::(Function, LayoutBuilder, Library, Pointer, Struct, Type) -import std::test::Tests - -fn libc_paths -> Array[String] { - [ - # Linux, sometimes a GNU ld script - 'libc.so', - - # Linux, glibc 2.x - 'libc.so.6', - - # CI uses Ubuntu, which puts libc here - '/lib/x86_64-linux-gnu/libc.so.6', - - # Mac OS - 'libSystem.dylib', - - # Windows - 'msvcrt.dll' - ] -} - -class Mem { - let @ptr: Pointer - let @free: Function - - fn static alloc(libc: ref Library, size: Int) -> Self { - let calloc = libc - .function( - 'calloc', - arguments: [Type.SizeT, Type.SizeT], - returns: Type.Pointer - ) - .unwrap - - let free = libc - .function('free', arguments: [Type.Pointer], returns: Type.Void) - .unwrap - - let ptr = Pointer.new(calloc.call2(1, size)) - - if ptr.null? { panic("calloc(1, {size}) failed") } - - Self { @ptr = ptr, @free = free } - } -} - -impl Drop for Mem { - fn mut drop { - @free.call1(@ptr.raw) - } -} - -fn pub tests(t: mut Tests) { - t.test('Pointer.from_address') fn (t) { - t.equal(Pointer.from_address(0x4).address, 0x4) - } - - t.test('Pointer.null') fn (t) { - t.equal(Pointer.null.address, 0x0) - } - - t.test('Pointer.null?') fn (t) { - t.true(Pointer.null.null?) - } - - t.test('Pointer.read') fn (t) { - let libc = Library.new(libc_paths).unwrap - - let mem = Mem.alloc(libc, size: Type.U8.size) - - t.equal(mem.ptr.read(type: Type.U8, offset: 0) as Int, 0) - } - - t.test('Pointer.write') fn (t) { - let libc = Library.new(libc_paths).unwrap - let mem = Mem.alloc(libc, size: Type.U8.size) - - mem.ptr.write(type: Type.U8, offset: 0, value: 10) - t.equal(mem.ptr.read(type: Type.U8, offset: 0) as Int, 10) - } - - t.test('Pointer.pointer') fn (t) { - let a = Pointer.null - let b = a.pointer - - b.write(type: Type.U8, offset: 0, value: 10) - t.equal(a.address, 10) - t.not_equal(b.address, 10) - } - - t.test('Pointer.clone') fn (t) { - let ptr = Pointer.from_address(0x4) - - t.equal(ptr.clone, ptr) - } - - t.test('Pointer.==') fn (t) { - t.equal(Pointer.from_address(0x4), Pointer.from_address(0x4)) - t.not_equal(Pointer.from_address(0x4), Pointer.from_address(0x5)) - } - - t.test('Library.new') fn (t) { - t.true(Library.new(['inko-test-this-wont-exist']).none?) - t.true(Library.new(libc_paths).some?) - } - - t.test('Library.variable') fn (t) { - let libc = Library.new(libc_paths).unwrap - - t.true(libc.variable('malloc').some?) - t.true(libc.variable('inko_test_this_wont_exist').none?) - } - - t.test('Library.function') fn (t) { - let libc = Library.new(libc_paths).unwrap - - t.true( - libc - .function('malloc', arguments: [Type.SizeT], returns: Type.Pointer) - .some? - ) - - t.true( - libc.function('inko_test_invalid', arguments: [], returns: Type.U8).none? - ) - } - - t.test('Function.call0') fn (t) { - let libc = Library.new(libc_paths).unwrap - let func = - libc.function('clock', arguments: [], returns: Type.I64).unwrap - - t.true((func.call0 as Int) >= 0) - } - - t.test('Function.call1') fn (t) { - let libc = Library.new(libc_paths).unwrap - let func = - libc.function('abs', arguments: [Type.I32], returns: Type.I32).unwrap - - t.equal(func.call1(-4) as Int, 4) - } - - t.test('Function.call2') fn (t) { - let libc = Library.new(libc_paths).unwrap - let func = libc - .function( - 'strcmp', - arguments: [Type.String, Type.String], - returns: Type.I32 - ) - .unwrap - - t.equal(func.call2('foo', 'foo') as Int, 0) - } - - t.test('Function.call3') fn (t) { - let libc = Library.new(libc_paths).unwrap - let func = libc - .function( - 'strtol', - arguments: [Type.String, Type.Pointer, Type.I32], - returns: Type.I32 - ) - .unwrap - - let value = func.call3('123', Pointer.null.raw, 10) as Int - - t.equal(value, 123) - } - - t.test('Function.call4') fn (t) { - let libc = Library.new(libc_paths).unwrap - } - - t.test('Struct.pointer') fn (t) { - let builder = LayoutBuilder.new - - builder.field('foo', Type.I32) - - let layout = builder.into_layout - let pointer = Pointer.null - let struct = Struct.new(pointer: pointer.clone, layout: layout) - - t.equal(struct.pointer, pointer) - } - - t.test('Struct.index') fn (t) { - let libc = Library.new(libc_paths).unwrap - let builder = LayoutBuilder.new - - builder.field('foo', Type.I32) - builder.field('bar', Type.I64) - - let layout = builder.into_layout - let mem = Mem.alloc(libc, size: layout.size.clone) - let struct = Struct.new(pointer: mem.ptr.clone, layout: layout) - - t.equal(struct['foo'] as Int, 0) - t.equal(struct['bar'] as Int, 0) - - struct['foo'] = 4 - struct['bar'] = 8 - - t.equal(struct['foo'] as Int, 4) - t.equal(struct['bar'] as Int, 8) - } - - t.test('LayoutBuilder.into_layout with padding') fn (t) { - let builder = LayoutBuilder.new - - builder.field('foo', Type.I32) - builder.field('bar', Type.I64) - - let layout = builder.into_layout - let struct = Struct.new(pointer: Pointer.null, layout: layout) - - t.equal(struct.size, Type.I64.size * 2) - t.equal(struct.alignment, Type.I64.alignment) - } - - t.test('LayoutBuilder.into_layout without padding') fn (t) { - let builder = LayoutBuilder.new - - builder.no_padding - builder.field('foo', Type.I32) - builder.field('bar', Type.I64) - - let layout = builder.into_layout - let struct = Struct.new(pointer: Pointer.null, layout: layout) - - t.equal(struct.size, Type.I64.size + Type.I32.size) - t.equal(struct.alignment, 1) - } - - t.test('Type.size') fn (t) { - t.equal(Type.I16.size, 2) - t.equal(Type.String.size, 8) - t.equal(Type.ByteArray.size, 8) - } - - t.test('Type.alignment') fn (t) { - t.equal(Type.I16.alignment, 2) - t.equal(Type.String.alignment, 8) - t.equal(Type.ByteArray.alignment, 8) - } - - t.test('Type.to_int') fn (t) { - t.equal(Type.Void.to_int, 0) - t.equal(Type.Pointer.to_int, 1) - t.equal(Type.F64.to_int, 2) - t.equal(Type.F32.to_int, 3) - t.equal(Type.I8.to_int, 4) - t.equal(Type.I16.to_int, 5) - t.equal(Type.I32.to_int, 6) - t.equal(Type.I64.to_int, 7) - t.equal(Type.U8.to_int, 8) - t.equal(Type.U16.to_int, 9) - t.equal(Type.U32.to_int, 10) - t.equal(Type.U64.to_int, 11) - t.equal(Type.String.to_int, 12) - t.equal(Type.ByteArray.to_int, 13) - t.equal(Type.SizeT.to_int, 14) - } - - t.test('Type.clone') fn (t) { - t.equal(Type.Void.clone, Type.Void) - t.equal(Type.Pointer.clone, Type.Pointer) - t.equal(Type.F64.clone, Type.F64) - t.equal(Type.F32.clone, Type.F32) - t.equal(Type.I8.clone, Type.I8) - t.equal(Type.I16.clone, Type.I16) - t.equal(Type.I32.clone, Type.I32) - t.equal(Type.I64.clone, Type.I64) - t.equal(Type.U8.clone, Type.U8) - t.equal(Type.U16.clone, Type.U16) - t.equal(Type.U32.clone, Type.U32) - t.equal(Type.U64.clone, Type.U64) - t.equal(Type.String.clone, Type.String) - t.equal(Type.ByteArray.clone, Type.ByteArray) - t.equal(Type.SizeT.clone, Type.SizeT) - } -} diff --git a/libstd/test/std/test_json.inko b/libstd/test/std/test_json.inko deleted file mode 100644 index f4d6dfdf9..000000000 --- a/libstd/test/std/test_json.inko +++ /dev/null @@ -1,407 +0,0 @@ -# import helpers::(fmt) -import helpers::(fmt) -import std::json::(self, Error, Json, Parser) -import std::test::Tests - -fn parse(input: String) !! Error -> Json { - try Parser.new(input).parse -} - -fn parse_invalid(input: String) -> Option[String] { - try Parser.new(input).parse else (err) { return Option.Some(err.to_string) } - Option.None -} - -fn pub tests(t: mut Tests) { - t.test('Error.fmt') fn (t) { - let err = Error { @message = 'foo', @line = 1, @offset = 5 } - - t.equal(fmt(err), 'foo, on line 1 at byte offset 5') - } - - t.test('Error.to_string') fn (t) { - let err = Error { @message = 'foo', @line = 1, @offset = 5 } - - t.equal(err.to_string, 'foo, on line 1 at byte offset 5') - } - - t.test('Json.fmt') fn (t) { - let map = Map.new - - map['a'] = Json.Int(10) - - t.equal(fmt(Json.Int(42)), 'Int(42)') - t.equal(fmt(Json.Float(42.0)), 'Float(42.0)') - t.equal(fmt(Json.String('test')), 'String("test")') - t.equal(fmt(Json.Array([Json.Int(10)])), 'Array([Int(10)])') - t.equal(fmt(Json.Object(map)), 'Object({"a": Int(10)})') - t.equal(fmt(Json.Bool(true)), 'Bool(true)') - t.equal(fmt(Json.Bool(false)), 'Bool(false)') - t.equal(fmt(Json.Null), 'Null') - } - - t.test('Json.==') fn (t) { - let map1 = Map.new - let map2 = Map.new - let map3 = Map.new - - map1['a'] = Json.Int(10) - map2['a'] = Json.Int(10) - map3['a'] = Json.Int(10) - - t.equal(Json.Int(10), Json.Int(10)) - t.not_equal(Json.Int(10), Json.Int(20)) - t.not_equal(Json.Int(10), Json.Float(20.0)) - - t.equal(Json.Float(10.0), Json.Float(10.0)) - t.not_equal(Json.Float(10.0), Json.Float(20.0)) - t.not_equal(Json.Float(10.0), Json.Int(10)) - - t.equal(Json.String('foo'), Json.String('foo')) - t.not_equal(Json.String('foo'), Json.String('bar')) - t.not_equal(Json.String('foo'), Json.Int(10)) - - t.equal(Json.Array([Json.Int(10)]), Json.Array([Json.Int(10)])) - t.not_equal(Json.Array([Json.Int(10)]), Json.Array([Json.Int(20)])) - t.not_equal(Json.Array([Json.Int(10)]), Json.Int(10)) - - t.equal(Json.Object(map1), Json.Object(map2)) - t.not_equal(Json.Object(map3), Json.Object(Map.new)) - t.not_equal(Json.Object(Map.new), Json.Int(10)) - - t.equal(Json.Bool(true), Json.Bool(true)) - t.not_equal(Json.Bool(true), Json.Bool(false)) - t.not_equal(Json.Bool(true), Json.Int(10)) - - t.equal(Json.Null, Json.Null) - t.not_equal(Json.Null, Json.Int(10)) - } - - t.test('Json.to_string') fn (t) { - let map = Map.new - - map['a'] = Json.Int(1) - map['b'] = Json.Int(2) - - t.equal(Json.Int(42).to_string, '42') - t.equal(Json.Float(1.2).to_string, '1.2') - t.equal(Json.String('foo').to_string, '"foo"') - t.equal(Json.String("a\nb").to_string, '"a\nb"') - t.equal(Json.String("a\rb").to_string, '"a\rb"') - t.equal(Json.String("a\tb").to_string, '"a\tb"') - t.equal(Json.String("a\u{C}b").to_string, '"a\fb"') - t.equal(Json.String("a\u{8}b").to_string, '"a\bb"') - t.equal(Json.String('a\\b').to_string, '"a\\\\b"') - t.equal(Json.Array([]).to_string, '[]') - t.equal(Json.Array([Json.Int(1), Json.Int(2)]).to_string, '[1, 2]') - t.equal(Json.Object(map).to_string, '{"a": 1, "b": 2}') - t.equal(Json.Object(Map.new).to_string, '{}') - t.equal(Json.Bool(true).to_string, 'true') - t.equal(Json.Bool(false).to_string, 'false') - t.equal(Json.Null.to_string, 'null') - } - - t.test('Json.to_pretty_string') fn (t) { - t.equal(Json.Int(42).to_pretty_string, '42') - t.equal(Json.Float(1.2).to_pretty_string, '1.2') - t.equal(Json.String('foo').to_pretty_string, '"foo"') - t.equal(Json.String("a\nb").to_pretty_string, '"a\nb"') - t.equal(Json.String("a\rb").to_pretty_string, '"a\rb"') - t.equal(Json.String("a\tb").to_pretty_string, '"a\tb"') - t.equal(Json.String("a\u{C}b").to_pretty_string, '"a\fb"') - t.equal(Json.String("a\u{8}b").to_pretty_string, '"a\bb"') - t.equal(Json.String('a\\b').to_pretty_string, '"a\\\\b"') - t.equal(Json.Bool(true).to_pretty_string, 'true') - t.equal(Json.Bool(false).to_pretty_string, 'false') - t.equal(Json.Null.to_pretty_string, 'null') - - t.equal(Json.Array([]).to_pretty_string, '[]') - t.equal( - Json.Array([Json.Int(1), Json.Int(2)]).to_pretty_string, - '[ - 1, - 2 -]' - ) - - t.equal( - Json.Array([Json.Array([Json.Int(1), Json.Int(2)])]).to_pretty_string, - '[ - [ - 1, - 2 - ] -]' - ) - - let map1 = Map.new - let map2 = Map.new - let map3 = Map.new - - map1['a'] = Json.Int(1) - map1['b'] = Json.Int(2) - map2['a'] = Json.Array([Json.Int(1), Json.Int(2)]) - map3['a'] = Json.Int(1) - map3['b'] = Json.Object(map2) - - t.equal(Json.Object(Map.new).to_pretty_string, '{}') - t.equal( - Json.Object(map1).to_pretty_string, - '{ - "a": 1, - "b": 2 -}' - ) - - t.equal( - Json.Object(map3).to_pretty_string, - '{ - "a": 1, - "b": { - "a": [ - 1, - 2 - ] - } -}' - ) - } - - t.test('Parsing integers') fn (t) { - t.try_equal(fn { try parse('0') }, Json.Int(0)) - t.try_equal(fn { try parse('42') }, Json.Int(42)) - t.try_equal(fn { try parse(' 42') }, Json.Int(42)) - t.try_equal(fn { try parse('42 ') }, Json.Int(42)) - t.try_equal(fn { try parse("\t42") }, Json.Int(42)) - t.try_equal(fn { try parse("\r42") }, Json.Int(42)) - t.try_equal(fn { try parse('-42') }, Json.Int(-42)) - - t.throw fn { try parse('00') } - t.throw fn { try parse('10,') } - t.throw fn { try parse('-') } - t.throw fn { try parse('-01') } - t.throw fn { try parse('01') } - t.throw fn { try parse('1a') } - t.throw fn { try parse('-a') } - } - - t.test('Parsing floats') fn (t) { - t.try_equal(fn { try parse(' 1.2') }, Json.Float(1.2)) - t.try_equal(fn { try parse('1.2 ') }, Json.Float(1.2)) - t.try_equal(fn { try parse('1.2') }, Json.Float(1.2)) - t.try_equal(fn { try parse('-1.2') }, Json.Float(-1.2)) - t.try_equal(fn { try parse('1.2e+123') }, Json.Float(1.2e+123)) - t.try_equal(fn { try parse('1.2e-123') }, Json.Float(1.2e-123)) - t.try_equal(fn { try parse('1.2E+123') }, Json.Float(1.2e+123)) - t.try_equal(fn { try parse('1.2E-123') }, Json.Float(1.2e-123)) - t.try_equal(fn { try parse('-1.2E-123') }, Json.Float(-1.2e-123)) - t.try_equal(fn { try parse('0.0') }, Json.Float(0.0)) - t.try_equal(fn { try parse('0E0') }, Json.Float(0.0)) - t.try_equal(fn { try parse('0e+1') }, Json.Float(0.0)) - t.try_equal(fn { try parse('1.2E1') }, Json.Float(1.2e1)) - t.try_equal(fn { try parse('1.2e1') }, Json.Float(1.2e1)) - t.try_equal( - fn { try parse('1.7976931348623157e+310') }, - Json.Float(Float.infinity) - ) - t.try_equal( - fn { try parse('4.940656458412465441765687928682213723651e-330') }, - Json.Float(0.0) - ) - t.try_equal( - fn { try parse('-0.000000000000000000000000000000000000000000000000000000000000000000000000000001') }, - Json.Float(-1.0E-78) - ) - - # These numbers are too big for regular integers, so we promote them to - # floats. - t.try_equal( - fn { try parse('11111111111111111111111111111111111111111') }, - Json.Float(11111111111111111111111111111111111111111.0) - ) - t.try_equal( - fn { try parse('10000000000000000999') }, - Json.Float(10000000000000000999.0) - ) - - t.throw fn { try parse('00.0') } - t.throw fn { try parse('1.2e') } - t.throw fn { try parse('1.2e+') } - t.throw fn { try parse('1.2e-') } - t.throw fn { try parse('1.2E') } - t.throw fn { try parse('1.2E+') } - t.throw fn { try parse('1.2E-') } - t.throw fn { try parse('1.2E+a') } - t.throw fn { try parse('1.2E-a') } - t.throw fn { try parse('0E') } - t.throw fn { try parse('10.2,') } - - t.equal( - parse_invalid("\n1.2e"), - Option.Some( - 'One or more tokens are required, but we ran out of input, \ - on line 2 at byte offset 5' - ) - ) - } - - t.test('Parsing arrays') fn (t) { - t.try_equal(fn { try parse('[]') }, Json.Array([])) - t.try_equal(fn { try parse('[10]') }, Json.Array([Json.Int(10)])) - t.try_equal( - fn { try parse('[10, 20]') }, - Json.Array([Json.Int(10), Json.Int(20)]) - ) - - t.throw fn { try parse('[') } - t.throw fn { try parse(']') } - t.throw fn { try parse('[,10]') } - t.throw fn { try parse('[10,]') } - t.throw fn { try parse('[10') } - t.throw fn { try parse('[10,') } - t.throw fn { try parse('[10true]') } - t.throw fn { try parse('[],') } - - t.throw fn { - let parser = Parser.new('[[[[10]]]]') - - parser.max_depth = 2 - try parser.parse - } - } - - t.test('Parsing booleans') fn (t) { - t.try_equal(fn { try parse('true') }, Json.Bool(true)) - t.try_equal(fn { try parse('false') }, Json.Bool(false)) - - t.throw fn { try parse('t') } - t.throw fn { try parse('tr') } - t.throw fn { try parse('tru') } - t.throw fn { try parse('f') } - t.throw fn { try parse('fa') } - t.throw fn { try parse('fal') } - t.throw fn { try parse('fals') } - } - - t.test('Parsing null') fn (t) { - t.try_equal(fn { try parse('null') }, Json.Null) - - t.throw fn { try parse('n') } - t.throw fn { try parse('nu') } - t.throw fn { try parse('nul') } - } - - t.test('Parsing strings') fn (t) { - t.try_equal(fn { try parse('"foo"') }, Json.String('foo')) - t.try_equal(fn { try parse('"foo bar"') }, Json.String('foo bar')) - t.try_equal(fn { try parse('"foo\nbar"') }, Json.String("foo\nbar")) - t.try_equal(fn { try parse('"foo\tbar"') }, Json.String("foo\tbar")) - t.try_equal(fn { try parse('"foo\rbar"') }, Json.String("foo\rbar")) - t.try_equal(fn { try parse('"foo\bbar"') }, Json.String("foo\u{0008}bar")) - t.try_equal(fn { try parse('"foo\fbar"') }, Json.String("foo\u{000C}bar")) - t.try_equal(fn { try parse('"foo\\"bar"') }, Json.String('foo"bar')) - t.try_equal(fn { try parse('"foo\\/bar"') }, Json.String('foo/bar')) - t.try_equal(fn { try parse('"foo\\\\bar"') }, Json.String("foo\\bar")) - t.try_equal(fn { try parse('"foo\u005Cbar"') }, Json.String('foo\\bar')) - t.try_equal( - fn { try parse('"foo\\u001Fbar"') }, - Json.String("foo\u{001F}bar") - ) - t.try_equal(fn { try parse('"\uD834\uDD1E"') }, Json.String("\u{1D11E}")) - t.try_equal( - fn { try parse('"\uE000\uE000"') }, - Json.String("\u{E000}\u{E000}") - ) - - t.throw fn { try parse("\"\0\"") } - t.throw fn { try parse("\"\n\"") } - t.throw fn { try parse("\"\t\"") } - t.throw fn { try parse("\"\r\"") } - t.throw fn { try parse("\"\u{8}\"") } # \b - t.throw fn { try parse("\"\u{c}\"") } # \f - - t.throw fn { try parse('"\x42"') } - t.throw fn { try parse('"\u1"') } - t.throw fn { try parse('"\u12"') } - t.throw fn { try parse('"\u123"') } - t.throw fn { try parse('"\u{XXXX}"') } - t.throw fn { try parse('"\uD834\uE000"') } - t.throw fn { try parse('"\uD834\uZZZZ"') } - t.throw fn { try parse('"\uDFFF\uDFFF"') } - - t.throw fn { - let parser = Parser.new('"foo"') - - parser.max_string_size = 2 - try parser.parse - } - - t.equal( - parse_invalid('"a'), - Option.Some( - 'One or more tokens are required, but we ran out of input, \ - on line 1 at byte offset 2' - ) - ) - } - - t.test('Parsing objects') fn (t) { - let map1 = Map.new - let map2 = Map.new - let map3 = Map.new - let map4 = Map.new - let map5 = Map.new - - map2['a'] = Json.Int(10) - map3['a'] = Json.Int(20) - map4['a'] = Json.Int(10) - map4['b'] = Json.Int(20) - map5['a'] = Json.Int(10) - map5['b'] = Json.Int(20) - - t.try_equal(fn { try parse('{}') }, Json.Object(map1)) - t.try_equal(fn { try parse('{ "a": 10 }') }, Json.Object(map2)) - t.try_equal(fn { try parse('{"a": 10, "a": 20}') }, Json.Object(map3)) - t.try_equal(fn { try parse('{"a": 10, "b": 20}') }, Json.Object(map4)) - t.try_equal( - fn { - try parse('{ - "a": 10, - "b": 20 - }') - }, - Json.Object(map5) - ) - - t.throw fn { try parse('{') } - t.throw fn { try parse('}') } - t.throw fn { try parse('{{}}') } - t.throw fn { try parse('{"a"}') } - t.throw fn { try parse('{"a":}') } - t.throw fn { try parse('{"a":10,}') } - t.throw fn { try parse('{},') } - t.throw fn { try parse('{"a": true} "x"') } - - t.throw fn { - let parser = Parser.new('{"a": {"b": {"c": 10}}}') - - parser.max_depth = 2 - try parser.parse - } - - t.equal( - parse_invalid('{"a"}'), - Option.Some("The character '}' is unexpected, on line 1 at byte offset 4") - ) - } - - t.test('Parsing Unicode BOMs') fn (t) { - t.throw fn { try parse("\u{FEFF}10") } - t.throw fn { try parse("\u{FFFE}10") } - t.throw fn { try parse("\u{EF}\u{BB}\u{BF}10") } - } - - t.test('json.parse') fn (t) { - t.try_equal(fn { try json.parse('[10]') }, Json.Array([Json.Int(10)])) - } -} diff --git a/libstd/test/std/test_process.inko b/libstd/test/std/test_process.inko deleted file mode 100644 index 5fe53382e..000000000 --- a/libstd/test/std/test_process.inko +++ /dev/null @@ -1,54 +0,0 @@ -import std::process -import std::test::Tests -import std::time::(Duration, Instant) - -class async Proc { - fn async send_back(value: Int) -> Int { - value - } - - fn async error(value: Int) !! Int { - throw value - } - - fn async slow { - process.sleep(Duration.from_secs(5)) - } -} - -fn pub tests(t: mut Tests) { - t.test('process.sleep') fn (t) { - let start = Instant.new - - process.sleep(Duration.from_millis(10)) - t.true(start.elapsed.to_millis >= 10) - } - - t.test('process.poll') fn (t) { - let pending = [async Proc {}.send_back(42), async Proc {}.send_back(42)] - let mut ready = [] - - while pending.length > 0 { - process.poll(pending).into_iter.each fn (fut) { ready.push(fut.await) } - } - - t.equal(pending.length, 0) - t.equal(ready, [42, 42]) - } - - t.test('Future.await') fn (t) { - let proc = Proc {} - - t.equal(async { proc.send_back(1) }.await, 1) - t.throw fn move { try { async { proc.error(2) }.await } } - } - - t.test('Future.await_for') fn (t) { - let proc = Proc {} - let fast = async proc.send_back(42) - let slow = async proc.slow - - t.equal(fast.await_for(Duration.from_secs(5)), Option.Some(42)) - t.true(slow.await_for(Duration.from_millis(10)).none?) - } -} diff --git a/libstd/test/std/test_stdio.inko b/libstd/test/std/test_stdio.inko deleted file mode 100644 index e54fce392..000000000 --- a/libstd/test/std/test_stdio.inko +++ /dev/null @@ -1,81 +0,0 @@ -import helpers::Script -import std::test::Tests - -fn pub tests(t: mut Tests) { - t.test('STDIN.read') fn (t) { - let code = ' - let out = STDOUT.new - let in = STDIN.new - let bytes = ByteArray.new - - try! in.read_all(bytes) - out.write_bytes(bytes) - ' - - let output = Script.new(t.id, code).stdin('hello').run - - t.equal(output, 'hello') - } - - t.test('STDOUT.write_bytes') fn (t) { - let code = 'STDOUT.new.write_bytes("hello".to_byte_array)' - - t.equal(Script.new(t.id, code).run, 'hello') - } - - t.test('STDOUT.write_string') fn (t) { - let code = 'STDOUT.new.write_string("hello")' - - t.equal(Script.new(t.id, code).run, 'hello') - } - - t.test('STDOUT.print') fn (t) { - let code = ' -let out = STDOUT.new -let len = out.print("hello") - -out.print(len.to_string)' - - t.equal(Script.new(t.id, code).run, "hello\n6\n") - } - - t.test('STDOUT.flush') fn (t) { - let code = ' - let out = STDOUT.new - - out.write_string("hello") - out.flush - ' - - t.equal(Script.new(t.id, code).run, "hello") - } - - t.test('STDERR.write_bytes') fn (t) { - let code = 'STDERR.new.write_bytes("hello".to_byte_array)' - - t.equal(Script.new(t.id, code).run, 'hello') - } - - t.test('STDERR.write_string') fn (t) { - let code = 'STDERR.new.write_string("hello")' - - t.equal(Script.new(t.id, code).run, 'hello') - } - - t.test('STDERR.print') fn (t) { - let code = 'STDERR.new.print("hello")' - - t.equal(Script.new(t.id, code).run, "hello\n") - } - - t.test('STDERR.flush') fn (t) { - let code = ' - let out = STDERR.new - - out.write_string("hello") - out.flush - ' - - t.equal(Script.new(t.id, code).run, "hello") - } -} diff --git a/rt/Cargo.toml b/rt/Cargo.toml new file mode 100644 index 000000000..6e5515527 --- /dev/null +++ b/rt/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "rt" +version = "0.10.0" # VERSION +authors = ["Yorick Peterse "] +edition = "2021" +build = "build.rs" +license = "MPL-2.0" + +[lib] +name = "inko" +doctest = false +crate-type = ["staticlib", "lib"] + +[dependencies] +crossbeam-utils = "^0.8" +crossbeam-queue = "^0.3" +libc = "^0.2" +rand = { version = "^0.8", features = ["default", "small_rng"] } +polling = "^2.8" +unicode-segmentation = "^1.8" +backtrace = "^0.3" + +[dependencies.socket2] +version = "^0.5" +features = ["all"] diff --git a/vm/build.rs b/rt/build.rs similarity index 100% rename from vm/build.rs rename to rt/build.rs diff --git a/vm/src/arc_without_weak.rs b/rt/src/arc_without_weak.rs similarity index 99% rename from vm/src/arc_without_weak.rs rename to rt/src/arc_without_weak.rs index a0794ce9e..86154f4a0 100644 --- a/vm/src/arc_without_weak.rs +++ b/rt/src/arc_without_weak.rs @@ -20,6 +20,7 @@ pub(crate) struct Inner { } /// A thread-safe reference counted pointer. +#[repr(C)] pub(crate) struct ArcWithoutWeak { inner: NonNull>, } diff --git a/vm/src/config.rs b/rt/src/config.rs similarity index 78% rename from vm/src/config.rs rename to rt/src/config.rs index aa8595ae9..989b3e8c9 100644 --- a/vm/src/config.rs +++ b/rt/src/config.rs @@ -2,8 +2,8 @@ //! //! Various virtual machine settings that can be changed by the user, such as //! the number of threads to run. +use crate::scheduler::number_of_cores; use std::env::var; -use std::thread::available_parallelism; /// Sets a configuration field based on an environment variable. macro_rules! set_from_env { @@ -18,10 +18,6 @@ macro_rules! set_from_env { }}; } -/// The default number of reductions to consume before a process suspends -/// itself. -const DEFAULT_REDUCTIONS: u16 = 1000; - /// The default number of network poller threads to use. /// /// We default to one thread because for most setups this is probably more than @@ -31,6 +27,12 @@ const DEFAULT_NETPOLL_THREADS: u8 = 1; /// The maximum number of netpoll threads that are allowed. const MAX_NETPOLL_THREADS: u8 = 127; +/// The default size of each process' stack in bytes. +/// +/// The default size is chosen as we believe it to be large enough for most +/// cases, and to ensure foreign function calls don't overflow the stack. +const DEFAULT_STACK_SIZE: u32 = 1024 * 1024; + /// Structure containing the configuration settings for the virtual machine. pub struct Config { /// The number of process threads to run. @@ -39,6 +41,9 @@ pub struct Config { /// The number of backup process threads to spawn. pub backup_threads: u16, + /// The size of each process' stack in bytes. + pub stack_size: u32, + /// The number of network poller threads to use. /// /// While this value is stored as an u8, it's limited to a maximum of 127. @@ -46,31 +51,27 @@ pub struct Config { /// and use the value -1 to signal a file descriptor isn't registered with /// any poller. pub netpoll_threads: u8, - - /// The number of reductions a process can perform before being suspended. - pub reductions: u16, } impl Config { - pub fn new() -> Config { - let cpu_count = - available_parallelism().map(|v| v.get()).unwrap_or(1) as u16; + pub(crate) fn new() -> Config { + let cpu_count = number_of_cores() as u16; Config { process_threads: cpu_count, backup_threads: cpu_count * 4, netpoll_threads: DEFAULT_NETPOLL_THREADS, - reductions: DEFAULT_REDUCTIONS, + stack_size: DEFAULT_STACK_SIZE, } } - pub fn from_env() -> Config { + pub(crate) fn from_env() -> Config { let mut config = Config::new(); set_from_env!(config, process_threads, "PROCESS_THREADS", u16); set_from_env!(config, backup_threads, "BACKUP_THREADS", u16); - set_from_env!(config, reductions, "REDUCTIONS", u16); set_from_env!(config, netpoll_threads, "NETPOLL_THREADS", u8); + set_from_env!(config, stack_size, "STACK_SIZE", u32); config.verify(); config @@ -101,7 +102,6 @@ mod tests { let config = Config::new(); assert!(config.process_threads >= 1); - assert_eq!(config.reductions, DEFAULT_REDUCTIONS); } #[test] @@ -109,14 +109,8 @@ mod tests { let mut cfg = Config::new(); set_from_env!(cfg, process_threads, "FOO", u16); - set_from_env!(cfg, reductions, "BAR", u16); assert_eq!(cfg.process_threads, 1); - assert_eq!(cfg.reductions, DEFAULT_REDUCTIONS); - - set_from_env!(cfg, reductions, "BAZ", u16); - - assert_eq!(cfg.reductions, DEFAULT_REDUCTIONS); } #[test] diff --git a/rt/src/context.rs b/rt/src/context.rs new file mode 100644 index 000000000..5d87547f7 --- /dev/null +++ b/rt/src/context.rs @@ -0,0 +1,67 @@ +//! Context switching between thread/process stacks +//! +//! Context switching is implemented using two functions: `start` and `switch`. +//! +//! The `start` function starts a new message by calling the appropriate native +//! function. The `switch` function is used for switching from a thread to a +//! process and back. This function shouldn't be used to switch between two +//! processes directly. +//! +//! The `start` function passes the necessary arguments using a `Context` type. +//! This type is dropped after the first yield, so the native code must load its +//! components into variables/registers in order to continue using them. +use crate::process::{NativeAsyncMethod, ProcessPointer}; +use crate::state::State; +mod unix; + +/// A type storing state used when first starting a process. +/// +/// Using this type allows us to keep the assembly used for setting up a process +/// simple, as we only need to pass a small number of arguments (which all fit +/// in registers). +/// +/// This type is dropped after the first yield from a process back to its +/// thread. +#[repr(C)] +pub struct Context { + pub state: *const State, + pub process: ProcessPointer, + pub arguments: *mut u8, +} + +// These functions are defined in the inline assembly macros, found in modules +// such as context/unix/x86_64.rs. +extern "system" { + fn inko_context_init( + high: *mut *mut u8, + func: NativeAsyncMethod, + ctx: *mut u8, + ); + + fn inko_context_switch(stack: *mut *mut u8); +} + +#[inline(never)] +pub(crate) unsafe fn start( + state: &State, + mut process: ProcessPointer, + func: NativeAsyncMethod, + mut args: Vec<*mut u8>, +) { + let ctx = Context { + state: state as _, + process, + arguments: args.as_mut_ptr() as _, + }; + + inko_context_init( + &mut process.stack_pointer, + func as _, + &ctx as *const _ as _, + ); +} + +#[inline(never)] +pub(crate) unsafe fn switch(mut process: ProcessPointer) { + inko_context_switch(&mut process.stack_pointer); +} diff --git a/rt/src/context/unix.rs b/rt/src/context/unix.rs new file mode 100644 index 000000000..5db896330 --- /dev/null +++ b/rt/src/context/unix.rs @@ -0,0 +1,5 @@ +#[cfg(target_arch = "x86_64")] +mod x86_64; + +#[cfg(target_arch = "aarch64")] +mod aarch64; diff --git a/rt/src/context/unix/aarch64.rs b/rt/src/context/unix/aarch64.rs new file mode 100644 index 000000000..03478d536 --- /dev/null +++ b/rt/src/context/unix/aarch64.rs @@ -0,0 +1,66 @@ +// Taken from +// https://github.com/bytecodealliance/wasmtime/blob/95ecb7e4d440605165a74de607de5389508779be/crates/fiber/src/unix/aarch64.rs + +// X0: a pointer pointer to the new stack, replaced with the new stack. +// X1: a pointer to a function to call. +// X2: an extra data argument to pass to the function. +asm_func!( + "inko_context_init", + " + stp x29, x30, [sp, -16]! + stp x20, x19, [sp, -16]! + stp x22, x21, [sp, -16]! + stp x24, x23, [sp, -16]! + stp x26, x25, [sp, -16]! + stp x28, x27, [sp, -16]! + stp d9, d8, [sp, -16]! + stp d11, d10, [sp, -16]! + stp d13, d12, [sp, -16]! + stp d15, d14, [sp, -16]! + + // Swap the stack pointers + ldr x8, [x0] + mov x9, sp + str x9, [x0] + mov sp, x8 + + mov x0, x2 + blr x1 + " +); + +// X0: a pointer pointer to a stack to restore. +asm_func!( + "inko_context_switch", + " + stp x29, x30, [sp, -16]! + stp x20, x19, [sp, -16]! + stp x22, x21, [sp, -16]! + stp x24, x23, [sp, -16]! + stp x26, x25, [sp, -16]! + stp x28, x27, [sp, -16]! + stp d9, d8, [sp, -16]! + stp d11, d10, [sp, -16]! + stp d13, d12, [sp, -16]! + stp d15, d14, [sp, -16]! + + // Swap the stack pointers + ldr x8, [x0] + mov x9, sp + str x9, [x0] + mov sp, x8 + + ldp d15, d14, [sp], 16 + ldp d13, d12, [sp], 16 + ldp d11, d10, [sp], 16 + ldp d9, d8, [sp], 16 + ldp x28, x27, [sp], 16 + ldp x26, x25, [sp], 16 + ldp x24, x23, [sp], 16 + ldp x22, x21, [sp], 16 + ldp x20, x19, [sp], 16 + ldp x29, x30, [sp], 16 + + ret + " +); diff --git a/rt/src/context/unix/x86_64.rs b/rt/src/context/unix/x86_64.rs new file mode 100644 index 000000000..694201461 --- /dev/null +++ b/rt/src/context/unix/x86_64.rs @@ -0,0 +1,49 @@ +// RDI: a pointer pointer to the new stack, replaced with the new stack. +// RSI: a pointer to a function to call. +// RDX: an extra data argument to pass to the function. +asm_func!( + "inko_context_init", + " + push rbp + push rbx + push r12 + push r13 + push r14 + push r15 + + // Swap the stack pointers + mov r8, [rdi] + mov [rdi], rsp + mov rsp, r8 + + mov rdi, rdx + call rsi + " +); + +// RDI: a pointer pointer to a stack to restore. +asm_func!( + "inko_context_switch", + " + push rbp + push rbx + push r12 + push r13 + push r14 + push r15 + + // Swap the stack pointers + mov r8, [rdi] + mov [rdi], rsp + mov rsp, r8 + + pop r15 + pop r14 + pop r13 + pop r12 + pop rbx + pop rbp + + ret + " +); diff --git a/vm/src/immutable_string.rs b/rt/src/immutable_string.rs similarity index 80% rename from vm/src/immutable_string.rs rename to rt/src/immutable_string.rs index a13029ec3..99e55e470 100644 --- a/vm/src/immutable_string.rs +++ b/rt/src/immutable_string.rs @@ -1,7 +1,6 @@ //! Immutable strings that can be shared with C code. use std::fmt; use std::ops::Deref; -use std::os::raw::c_char; use std::str; const NULL_BYTE: u8 = 0; @@ -14,7 +13,7 @@ const NULL_BYTE: u8 = 0; /// to still store NULL bytes any where in the `ImmutableString`. #[derive(Eq, PartialEq, Hash, Clone)] pub(crate) struct ImmutableString { - bytes: Vec, + bytes: Box<[u8]>, } #[cfg_attr(feature = "cargo-clippy", allow(clippy::len_without_is_empty))] @@ -42,20 +41,10 @@ impl ImmutableString { &self.bytes[0..self.len()] } - /// Returns a C `char` pointer that can be passed to C. - pub(crate) fn as_c_char_pointer(&self) -> *const c_char { - self.bytes.as_ptr() as *const _ - } - /// Returns the number of bytes in this String. pub(crate) fn len(&self) -> usize { self.bytes.len() - 1 } - - /// Returns the number of bytes in this string, including the null byte. - pub(crate) fn len_with_null_byte(&self) -> usize { - self.bytes.len() - } } impl Deref for ImmutableString { @@ -73,7 +62,7 @@ impl From> for ImmutableString { bytes.reserve_exact(1); bytes.push(NULL_BYTE); - ImmutableString { bytes } + ImmutableString { bytes: bytes.into_boxed_slice() } } } @@ -124,15 +113,6 @@ mod tests { assert_eq!(string.as_bytes(), &[105, 110, 107, 111]); } - #[test] - fn test_as_c_char_pointer() { - let string = ImmutableString::from("inko".to_string()); - - assert!( - string.as_c_char_pointer() == string.bytes.as_ptr() as *const _ - ); - } - #[test] fn test_len() { let string = ImmutableString::from("inko".to_string()); @@ -140,18 +120,11 @@ mod tests { assert_eq!(string.len(), 4); } - #[test] - fn test_len_with_null_byte() { - let string = ImmutableString::from("inko".to_string()); - - assert_eq!(string.len_with_null_byte(), 5); - } - #[test] fn test_from_bytes() { let string = ImmutableString::from(vec![10]); - assert_eq!(string.bytes, vec![10, 0]); + assert_eq!(string.bytes.as_ref(), &[10, 0]); } #[test] diff --git a/vm/src/lib.rs b/rt/src/lib.rs similarity index 63% rename from vm/src/lib.rs rename to rt/src/lib.rs index 01eb4a253..7fe1e6ff3 100644 --- a/vm/src/lib.rs +++ b/rt/src/lib.rs @@ -6,28 +6,19 @@ pub mod macros; pub mod arc_without_weak; -pub mod builtin_functions; -pub mod chunk; pub mod config; -pub mod execution_context; -pub mod ffi; -pub mod hasher; -pub mod image; +pub mod context; pub mod immutable_string; -pub mod indexes; -pub mod instructions; -pub mod location_table; -pub mod machine; pub mod mem; +pub mod memory_map; pub mod network_poller; -pub mod numeric; -pub mod permanent_space; -pub mod platform; +pub mod page; pub mod process; -pub mod registers; -pub mod runtime_error; +pub mod result; +pub mod runtime; pub mod scheduler; pub mod socket; +pub mod stack; pub mod state; #[cfg(test)] diff --git a/vm/src/macros/mod.rs b/rt/src/macros/mod.rs similarity index 59% rename from vm/src/macros/mod.rs rename to rt/src/macros/mod.rs index a07db0a14..349d8f008 100644 --- a/vm/src/macros/mod.rs +++ b/rt/src/macros/mod.rs @@ -21,3 +21,25 @@ macro_rules! init { } }; } + +#[cfg(target_os = "macos")] +macro_rules! asm_func { + ($name: expr, $($body: tt)*) => { + std::arch::global_asm!(concat!( + ".global _", $name, "\n", + "_", $name, ":\n", + $($body)* + )); + } +} + +#[cfg(not(target_os = "macos"))] +macro_rules! asm_func { + ($name: expr, $($body: tt)*) => { + std::arch::global_asm!(concat!( + ".global ", $name, "\n", + $name, ":\n", + $($body)* + )); + } +} diff --git a/rt/src/mem.rs b/rt/src/mem.rs new file mode 100644 index 000000000..586a98efa --- /dev/null +++ b/rt/src/mem.rs @@ -0,0 +1,624 @@ +use crate::immutable_string::ImmutableString; +use crate::process::Process; +use std::alloc::{alloc, alloc_zeroed, dealloc, handle_alloc_error, Layout}; +use std::mem::{align_of, size_of, swap}; +use std::ops::Deref; +use std::ptr::drop_in_place; +use std::string::String as RustString; + +/// The alignment to use for Inko objects. +const ALIGNMENT: usize = align_of::(); + +/// The number of bits to shift for tagged integers. +const INT_SHIFT: usize = 1; + +/// The minimum integer value that can be stored as a tagged signed integer. +pub(crate) const MIN_INT: i64 = i64::MIN >> INT_SHIFT; + +/// The maximum integer value that can be stored as a tagged signed integer. +pub(crate) const MAX_INT: i64 = i64::MAX >> INT_SHIFT; + +/// The mask to use for tagged integers. +const INT_MASK: usize = 0b01; + +/// A type indicating what sort of value we're dealing with in a certain place +/// (e.g. a ref or a permanent value). +/// +/// The values of the variants are specified explicitly to make it more explicit +/// we depend on these exact values (e.g. in the compiler). +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Kind { + /// The value is a regular heap allocated, owned value. + Owned = 0, + + /// The value is a reference to a heap allocated value. + Ref = 1, + + /// The value is an owned value that uses atomic reference counting. + Atomic = 2, + + /// The value musn't be dropped until the program stops. + Permanent = 3, + + /// The value is a boxed Int. + Int = 4, + + /// The value is a boxed Float. + Float = 5, +} + +pub(crate) fn allocate(layout: Layout) -> *mut u8 { + unsafe { + let ptr = alloc(layout); + + if ptr.is_null() { + handle_alloc_error(layout); + } else { + ptr + } + } +} + +fn with_mask(ptr: *const T, mask: usize) -> *const T { + (ptr as usize | mask) as _ +} + +fn mask_is_set(ptr: *const T, mask: usize) -> bool { + (ptr as usize & mask) == mask +} + +pub(crate) unsafe fn header_of<'a, T>(ptr: *const T) -> &'a mut Header { + &mut *(ptr as *mut Header) +} + +pub(crate) fn is_tagged_int(ptr: *const T) -> bool { + mask_is_set(ptr, INT_MASK) +} + +fn fits_in_tagged_int(value: i64) -> bool { + (MIN_INT..=MAX_INT).contains(&value) +} + +pub(crate) fn tagged_int(value: i64) -> *const Int { + with_mask((value << INT_SHIFT) as *const Int, INT_MASK) +} + +pub(crate) unsafe fn free(ptr: *mut T) { + let layout = header_of(ptr).class.instance_layout(); + + dealloc(ptr as *mut u8, layout); +} + +/// The header used by heap allocated objects. +/// +/// The layout is fixed to ensure we can safely assume certain fields are at +/// certain offsets in an object, even when not knowing what type of object +/// we're dealing with. +#[repr(C)] +pub struct Header { + /// The class of the object. + pub class: ClassPointer, + + /// A flag indicating what kind of pointer/object we're dealing with. + pub kind: Kind, + + /// The number of references to the object of this header. + /// + /// If this count overflows the program terminates. In practise this should + /// never happen, as one needs _a lot_ of references to achieve this. + /// + /// We're using a u32 here instead of a u16, as the likelihood of + /// overflowing a u32 is very tiny, but overflowing a u16 is something that + /// _could_ happen (i.e. a process reference shared with many other + /// processes). + pub references: u32, +} + +impl Header { + pub(crate) fn init(&mut self, class: ClassPointer) { + self.class = class; + self.kind = Kind::Owned; + self.references = 0; + } + + pub(crate) fn init_atomic(&mut self, class: ClassPointer) { + self.class = class; + self.kind = Kind::Atomic; + + // Atomic values start with a reference count of 1, so + // `decrement_atomic()` returns the correct result for a value for which + // no extra references have been created (instead of overflowing). + self.references = 1; + } + + pub(crate) fn set_permanent(&mut self) { + self.kind = Kind::Permanent; + } + + pub(crate) fn is_permanent(&self) -> bool { + matches!(self.kind, Kind::Permanent) + } + + pub(crate) fn references(&self) -> u32 { + self.references + } +} + +/// A function bound to an object. +/// +/// Methods don't have headers as there's no need for any, as methods aren't +/// values one can pass around in Inko. +#[repr(C)] +pub struct Method { + /// The hash of this method, used when performing dynamic dispatch. + pub hash: u64, + + /// A pointer to the native function that backs this method. + pub code: extern "system" fn(), +} + +/// An Inko class. +#[repr(C)] +pub struct Class { + /// The name of the class. + pub(crate) name: RustString, + + /// The size (in bytes) of instances of this class. + pub(crate) instance_size: u32, + + /// The number of method slots this class has. + /// + /// The actual number of methods may be less than this value. + pub(crate) method_slots: u16, + + /// The methods of this class, as pointers to native functions. + /// + /// Methods are accessed frequently, and we want to do so with as little + /// indirection and as cache-friendly as possible. For this reason we use a + /// flexible array member, instead of a Vec. + /// + /// The length of this array _must_ be a power of two. + pub methods: [Method; 0], +} + +impl Class { + pub(crate) unsafe fn drop(ptr: ClassPointer) { + let layout = Self::layout(ptr.method_slots); + let raw_ptr = ptr.0; + + drop_in_place(raw_ptr); + dealloc(raw_ptr as *mut u8, layout); + } + + pub(crate) fn alloc( + name: RustString, + methods: u16, + size: u32, + ) -> ClassPointer { + let ptr = unsafe { + let layout = Self::layout(methods); + + // For classes we zero memory out, so unused method slots are set to + // zeroed memory, instead of random garbage. + let ptr = alloc_zeroed(layout) as *mut Class; + + if ptr.is_null() { + handle_alloc_error(layout); + } + + ptr + }; + let obj = unsafe { &mut *ptr }; + + init!(obj.name => name); + init!(obj.instance_size => size); + init!(obj.method_slots => methods); + + ClassPointer(ptr) + } + + /// Returns a new class for a regular object. + pub(crate) fn object( + name: RustString, + fields: u8, + methods: u16, + ) -> ClassPointer { + let size = + size_of::
() + (fields as usize * size_of::<*mut u8>()); + + Self::alloc(name, methods, size as u32) + } + + /// Returns a new class for a process. + pub(crate) fn process( + name: RustString, + fields: u8, + methods: u16, + ) -> ClassPointer { + let size = + size_of::() + (fields as usize * size_of::<*mut u8>()); + + Self::alloc(name, methods, size as u32) + } + + /// Returns the `Layout` for a class itself. + unsafe fn layout(methods: u16) -> Layout { + let size = + size_of::() + (methods as usize * size_of::()); + + Layout::from_size_align_unchecked(size, align_of::()) + } + + pub(crate) unsafe fn instance_layout(&self) -> Layout { + Layout::from_size_align_unchecked( + self.instance_size as usize, + ALIGNMENT, + ) + } +} + +/// A pointer to a class. +#[repr(transparent)] +#[derive(Eq, PartialEq, Copy, Clone, Debug)] +pub struct ClassPointer(*mut Class); + +impl Deref for ClassPointer { + type Target = Class; + + fn deref(&self) -> &Class { + unsafe { &*(self.0 as *const Class) } + } +} + +/// A resizable array. +#[repr(C)] +pub struct Array { + pub(crate) header: Header, + pub(crate) value: Vec<*mut u8>, +} + +impl Array { + pub(crate) unsafe fn drop(ptr: *mut Self) { + drop_in_place(ptr); + } + + pub(crate) fn alloc(class: ClassPointer, value: Vec<*mut u8>) -> *mut Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + init!(obj.value => value); + ptr + } + + pub(crate) fn alloc_permanent( + class: ClassPointer, + value: Vec<*mut u8>, + ) -> *mut Self { + let ptr = Self::alloc(class, value); + + unsafe { header_of(ptr) }.set_permanent(); + ptr + } +} + +/// A resizable array of bytes. +#[repr(C)] +pub struct ByteArray { + pub(crate) header: Header, + pub(crate) value: Vec, +} + +impl ByteArray { + pub(crate) unsafe fn drop(ptr: *mut Self) { + drop_in_place(ptr); + } + + pub(crate) fn alloc(class: ClassPointer, value: Vec) -> *mut Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + init!(obj.value => value); + ptr + } + + pub(crate) fn take_bytes(&mut self) -> Vec { + let mut bytes = Vec::new(); + + swap(&mut bytes, &mut self.value); + bytes + } +} + +/// A signed 64-bits integer. +#[repr(C)] +pub struct Int { + pub(crate) header: Header, + pub(crate) value: i64, +} + +impl Int { + pub(crate) fn new(class: ClassPointer, value: i64) -> *const Self { + if fits_in_tagged_int(value) { + tagged_int(value) + } else { + Self::boxed(class, value) + } + } + + pub(crate) fn new_permanent( + class: ClassPointer, + value: i64, + ) -> *const Self { + if fits_in_tagged_int(value) { + tagged_int(value) + } else { + Self::boxed_permanent(class, value) + } + } + + pub(crate) fn boxed(class: ClassPointer, value: i64) -> *const Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + obj.header.kind = Kind::Int; + init!(obj.value => value); + ptr as _ + } + + pub(crate) fn boxed_permanent( + class: ClassPointer, + value: i64, + ) -> *const Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + obj.header.set_permanent(); + init!(obj.value => value); + ptr as _ + } +} + +#[repr(C)] +pub struct Bool { + pub(crate) header: Header, +} + +impl Bool { + pub(crate) fn drop_and_deallocate(ptr: *const Self) { + unsafe { + drop_in_place(ptr as *mut Self); + dealloc(ptr as *mut u8, Layout::new::()); + } + } + + pub(crate) fn alloc(class: ClassPointer) -> *const Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + obj.header.set_permanent(); + ptr as _ + } +} + +#[repr(C)] +pub struct Nil { + pub(crate) header: Header, +} + +impl Nil { + pub(crate) fn drop_and_deallocate(ptr: *const Self) { + unsafe { + drop_in_place(ptr as *mut Self); + dealloc(ptr as *mut u8, Layout::new::()); + } + } + + pub(crate) fn alloc(class: ClassPointer) -> *const Self { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + obj.header.set_permanent(); + ptr as _ + } +} + +/// A heap allocated float. +#[repr(C)] +pub struct Float { + pub(crate) header: Header, + pub(crate) value: f64, +} + +impl Float { + pub(crate) fn alloc(class: ClassPointer, value: f64) -> *const Float { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init(class); + obj.header.kind = Kind::Float; + init!(obj.value => value); + ptr as _ + } + + pub(crate) fn alloc_permanent( + class: ClassPointer, + value: f64, + ) -> *const Float { + let ptr = Self::alloc(class, value); + + unsafe { header_of(ptr) }.set_permanent(); + ptr + } +} + +/// A heap allocated string. +/// +/// Strings use atomic reference counting as they are treated as value types, +/// and this removes the need for cloning the string's contents (at the cost of +/// atomic operations). +#[repr(C)] +pub struct String { + pub(crate) header: Header, + pub(crate) value: ImmutableString, +} + +impl String { + pub(crate) unsafe fn drop(ptr: *const Self) { + drop_in_place(ptr as *mut Self); + } + + pub(crate) unsafe fn drop_and_deallocate(ptr: *const Self) { + Self::drop(ptr); + free(ptr as *mut u8); + } + + pub(crate) unsafe fn read<'a>(ptr: *const String) -> &'a str { + (*ptr).value.as_slice() + } + + pub(crate) fn alloc( + class: ClassPointer, + value: RustString, + ) -> *const String { + Self::from_immutable_string(class, ImmutableString::from(value)) + } + + pub(crate) fn alloc_permanent( + class: ClassPointer, + value: RustString, + ) -> *const String { + let ptr = + Self::from_immutable_string(class, ImmutableString::from(value)); + + unsafe { header_of(ptr) }.set_permanent(); + ptr + } + + pub(crate) fn from_immutable_string( + class: ClassPointer, + value: ImmutableString, + ) -> *const String { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init_atomic(class); + init!(obj.value => value); + ptr as _ + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem::{align_of, size_of}; + use std::ptr::addr_of; + + extern "system" fn dummy() {} + + #[test] + fn test_header_field_offsets() { + let header = Header { + class: ClassPointer(0x7 as _), + kind: Kind::Owned, + references: 42, + }; + + let base = addr_of!(header) as usize; + + assert_eq!(addr_of!(header.class) as usize - base, 0); + assert_eq!(addr_of!(header.kind) as usize - base, 8); + assert_eq!(addr_of!(header.references) as usize - base, 12); + } + + #[test] + fn test_class_field_offsets() { + let class = Class::alloc("A".to_string(), 4, 8); + let base = class.0 as usize; + + assert_eq!(addr_of!(class.name) as usize - base, 0); + assert_eq!(addr_of!(class.instance_size) as usize - base, 24); + assert_eq!(addr_of!(class.method_slots) as usize - base, 28); + assert_eq!(addr_of!(class.methods) as usize - base, 32); + + unsafe { + Class::drop(class); + } + } + + #[test] + fn test_method_field_offsets() { + let method = Method { hash: 42, code: dummy }; + let base = addr_of!(method) as usize; + + assert_eq!(addr_of!(method.hash) as usize - base, 0); + assert_eq!(addr_of!(method.code) as usize - base, 8); + } + + #[test] + fn test_type_sizes() { + assert_eq!(size_of::
(), 16); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 24); + assert_eq!(size_of::(), 32); + assert_eq!(size_of::(), 40); + assert_eq!(size_of::(), 40); + assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 32); + } + + #[test] + fn test_type_alignments() { + assert_eq!(align_of::
(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + assert_eq!(align_of::(), ALIGNMENT); + } + + #[test] + fn test_with_mask() { + let ptr = with_mask(0x4 as *const u8, 0b10); + + assert_eq!(ptr as usize, 0x6); + } + + #[test] + fn test_class_new() { + let class = Class::alloc("A".to_string(), 0, 24); + + assert_eq!(class.method_slots, 0); + assert_eq!(class.instance_size, 24); + + unsafe { Class::drop(class) }; + } + + #[test] + fn test_class_new_object() { + let class = Class::object("A".to_string(), 1, 0); + + assert_eq!(class.method_slots, 0); + assert_eq!(class.instance_size, 24); + + unsafe { Class::drop(class) }; + } + + #[test] + fn test_class_new_process() { + let class = Class::process("A".to_string(), 1, 0); + + assert_eq!(class.method_slots, 0); + + unsafe { Class::drop(class) }; + } +} diff --git a/rt/src/memory_map.rs b/rt/src/memory_map.rs new file mode 100644 index 000000000..0fcf8b5b0 --- /dev/null +++ b/rt/src/memory_map.rs @@ -0,0 +1,86 @@ +use crate::page::{multiple_of_page_size, page_size}; +use libc::{ + c_int, mmap, mprotect, munmap, MAP_ANON, MAP_FAILED, MAP_PRIVATE, + PROT_NONE, PROT_READ, PROT_WRITE, +}; +use std::io::{Error, Result as IoResult}; +use std::ptr::null_mut; + +/// A chunk of memory created using `mmap` and similar functions. +pub(crate) struct MemoryMap { + pub(crate) ptr: *mut u8, + pub(crate) len: usize, +} + +fn mmap_options(_stack: bool) -> c_int { + let base = MAP_PRIVATE | MAP_ANON; + + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd" + ))] + if _stack { + return base | libc::MAP_STACK; + } + + base +} + +impl MemoryMap { + pub(crate) fn new(size: usize, stack: bool) -> Self { + let size = multiple_of_page_size(size); + let opts = mmap_options(stack); + + let ptr = unsafe { + mmap(null_mut(), size, PROT_READ | PROT_WRITE, opts, -1, 0) + }; + + if ptr == MAP_FAILED { + panic!("mmap(2) failed: {}", Error::last_os_error()); + } + + MemoryMap { ptr: ptr as *mut u8, len: size } + } + + pub(crate) fn protect(&mut self, start: usize) -> IoResult<()> { + let res = unsafe { + mprotect(self.ptr.add(start) as _, page_size(), PROT_NONE) + }; + + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } + } +} + +impl Drop for MemoryMap { + fn drop(&mut self) { + unsafe { + munmap(self.ptr as _, self.len); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new() { + let map1 = MemoryMap::new(32, false); + let map2 = MemoryMap::new(page_size() * 3, false); + + assert_eq!(map1.len, page_size()); + assert_eq!(map2.len, page_size() * 3); + } + + #[test] + fn test_protect() { + let mut map = MemoryMap::new(page_size() * 2, false); + + assert!(map.protect(0).is_ok()); + } +} diff --git a/vm/src/network_poller.rs b/rt/src/network_poller.rs similarity index 98% rename from vm/src/network_poller.rs rename to rt/src/network_poller.rs index 4d05b2ad3..e0eb95f64 100644 --- a/vm/src/network_poller.rs +++ b/rt/src/network_poller.rs @@ -92,12 +92,13 @@ impl Worker { .filter_map(|ev| { let proc = unsafe { ProcessPointer::new(ev.key as *mut _) }; let mut state = proc.state(); + let rights = state.try_reschedule_for_io(); // A process may have also been registered with the timeout // thread (e.g. when using a timeout). As such we should // only reschedule the process if the timout thread didn't // already do this for us. - match state.try_reschedule_for_io() { + match rights { RescheduleRights::Failed => None, RescheduleRights::Acquired => Some(proc), RescheduleRights::AcquiredWithTimeout => { diff --git a/rt/src/page.rs b/rt/src/page.rs new file mode 100644 index 000000000..624d765da --- /dev/null +++ b/rt/src/page.rs @@ -0,0 +1,26 @@ +use libc::{sysconf, _SC_PAGESIZE}; +use std::sync::atomic::{AtomicUsize, Ordering}; + +static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0); + +fn page_size_raw() -> usize { + unsafe { sysconf(_SC_PAGESIZE) as usize } +} + +pub(crate) fn page_size() -> usize { + match PAGE_SIZE.load(Ordering::Relaxed) { + 0 => { + let size = page_size_raw(); + + PAGE_SIZE.store(size, Ordering::Relaxed); + size + } + n => n, + } +} + +pub(crate) fn multiple_of_page_size(size: usize) -> usize { + let page = page_size(); + + (size + (page - 1)) & !(page - 1) +} diff --git a/rt/src/process.rs b/rt/src/process.rs new file mode 100644 index 000000000..3d7a35e2d --- /dev/null +++ b/rt/src/process.rs @@ -0,0 +1,1512 @@ +use crate::arc_without_weak::ArcWithoutWeak; +use crate::mem::{allocate, free, ClassPointer, Header}; +use crate::scheduler::process::Thread; +use crate::scheduler::timeouts::Timeout; +use crate::stack::Stack; +use backtrace; +use std::alloc::{alloc, dealloc, handle_alloc_error, Layout}; +use std::cell::UnsafeCell; +use std::collections::VecDeque; +use std::mem::{align_of, forget, size_of, ManuallyDrop}; +use std::ops::Drop; +use std::ops::{Deref, DerefMut}; +use std::ptr::{drop_in_place, null_mut, NonNull}; +use std::slice; +use std::sync::{Mutex, MutexGuard}; + +const INKO_SYMBOL_IDENTIFIER: &str = "_IM_"; + +/// The type signature for Inko's async methods defined in the native code. +/// +/// Native async methods only take a single argument: a `context::Context` that +/// contains all the necessary data. This makes it easier to pass multiple +/// values back to the native function without having to change the assembly +/// code used for context switching. +/// +/// The argument this function takes is a Context. We use an opague pointer here +/// because a Context contains a State, which isn't FFI safe. This however is +/// fine as the State type is exposed as an opague pointer and its fields are +/// never read directly from native code. +/// +/// While we can disable the lint on a per-function basis, this would require +/// annotating _a lot_ of functions, so instead we use an opague pointer here. +pub(crate) type NativeAsyncMethod = unsafe extern "system" fn(*mut u8); + +/// A single stack frame in a process' call stack. +#[repr(C)] +pub struct StackFrame { + pub name: String, + pub path: String, + pub line: i64, +} + +/// A message sent between two processes. +#[repr(C)] +pub struct Message { + /// The native function to run. + method: NativeAsyncMethod, + + /// The number of arguments of this message. + length: u8, + + /// The arguments of the message. + arguments: [*mut u8; 0], +} + +impl Message { + pub(crate) fn alloc(method: NativeAsyncMethod, length: u8) -> OwnedMessage { + unsafe { + let layout = Self::layout(length); + let raw_ptr = alloc(layout) as *mut Self; + + if raw_ptr.is_null() { + handle_alloc_error(layout); + } + + let msg = &mut *raw_ptr; + + init!(msg.method => method); + init!(msg.length => length); + + OwnedMessage(NonNull::new_unchecked(raw_ptr)) + } + } + + unsafe fn layout(length: u8) -> Layout { + let size = size_of::() + (length as usize * size_of::<*mut u8>()); + + // Messages are sent often, so we don't want the overhead of size and + // alignment checks. + Layout::from_size_align_unchecked(size, align_of::()) + } +} + +#[repr(C)] +pub(crate) struct OwnedMessage(NonNull); + +impl OwnedMessage { + pub(crate) unsafe fn from_raw(message: *mut Message) -> OwnedMessage { + OwnedMessage(NonNull::new_unchecked(message)) + } + + pub(crate) fn into_raw(mut self) -> *mut Message { + let ptr = unsafe { self.0.as_mut() }; + + forget(self); + ptr + } +} + +impl Deref for OwnedMessage { + type Target = Message; + + fn deref(&self) -> &Message { + unsafe { self.0.as_ref() } + } +} + +impl DerefMut for OwnedMessage { + fn deref_mut(&mut self) -> &mut Message { + unsafe { self.0.as_mut() } + } +} + +impl Drop for OwnedMessage { + fn drop(&mut self) { + unsafe { + let layout = Message::layout(self.0.as_ref().length); + + drop_in_place(self.0.as_ptr()); + dealloc(self.0.as_ptr() as *mut u8, layout); + } + } +} + +/// A collection of messages to be processed by a process. +struct Mailbox { + messages: VecDeque, +} + +impl Mailbox { + fn new() -> Self { + Mailbox { messages: VecDeque::new() } + } + + fn send(&mut self, message: OwnedMessage) { + self.messages.push_back(message); + } + + fn receive(&mut self) -> Option { + self.messages.pop_front() + } +} + +pub(crate) enum Task { + Resume, + Start(NativeAsyncMethod, Vec<*mut u8>), + Wait, +} + +/// The status of a process, represented as a set of bits. +pub(crate) struct ProcessStatus { + /// The bits used to indicate the status of the process. + /// + /// Multiple bits may be set in order to combine different statuses. + bits: u8, +} + +impl ProcessStatus { + /// A regular process. + const NORMAL: u8 = 0b00_0000; + + /// The main process. + const MAIN: u8 = 0b00_0001; + + /// The process is waiting for a message. + const WAITING_FOR_MESSAGE: u8 = 0b00_0010; + + /// The process is waiting for a channel. + const WAITING_FOR_CHANNEL: u8 = 0b00_0100; + + /// The process is waiting for an IO operation to complete. + const WAITING_FOR_IO: u8 = 0b00_1000; + + /// The process is simply sleeping for a certain amount of time. + const SLEEPING: u8 = 0b01_0000; + + /// The process was rescheduled after a timeout expired. + const TIMEOUT_EXPIRED: u8 = 0b10_0000; + + /// The process is running a message. + const RUNNING: u8 = 0b100_0000; + + /// The process is waiting for something, or suspended for a period of time. + const WAITING: u8 = + Self::WAITING_FOR_CHANNEL | Self::SLEEPING | Self::WAITING_FOR_IO; + + pub(crate) fn new() -> Self { + Self { bits: Self::NORMAL } + } + + fn set_main(&mut self) { + self.update_bits(Self::MAIN, true); + } + + fn is_running(&self) -> bool { + self.bit_is_set(Self::RUNNING) + } + + fn set_running(&mut self, enable: bool) { + self.update_bits(Self::RUNNING, enable); + } + + fn is_main(&self) -> bool { + self.bit_is_set(Self::MAIN) + } + + fn set_waiting_for_message(&mut self, enable: bool) { + self.update_bits(Self::WAITING_FOR_MESSAGE, enable); + } + + fn is_waiting_for_message(&self) -> bool { + self.bit_is_set(Self::WAITING_FOR_MESSAGE) + } + + fn set_waiting_for_channel(&mut self, enable: bool) { + self.update_bits(Self::WAITING_FOR_CHANNEL, enable); + } + + fn set_waiting_for_io(&mut self, enable: bool) { + self.update_bits(Self::WAITING_FOR_IO, enable); + } + + fn is_waiting_for_io(&self) -> bool { + self.bit_is_set(Self::WAITING_FOR_IO) + } + + fn is_waiting_for_channel(&self) -> bool { + self.bit_is_set(Self::WAITING_FOR_CHANNEL) + } + + fn is_waiting(&self) -> bool { + (self.bits & Self::WAITING) != 0 + } + + fn no_longer_waiting(&mut self) { + self.update_bits(Self::WAITING, false); + } + + fn set_timeout_expired(&mut self, enable: bool) { + self.update_bits(Self::TIMEOUT_EXPIRED, enable) + } + + fn set_sleeping(&mut self, enable: bool) { + self.update_bits(Self::SLEEPING, enable); + } + + fn timeout_expired(&self) -> bool { + self.bit_is_set(Self::TIMEOUT_EXPIRED) + } + + fn update_bits(&mut self, mask: u8, enable: bool) { + self.bits = if enable { self.bits | mask } else { self.bits & !mask }; + } + + fn bit_is_set(&self, bit: u8) -> bool { + self.bits & bit == bit + } +} + +/// An enum describing what rights a thread was given when trying to reschedule +/// a process. +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum RescheduleRights { + /// The rescheduling rights were not obtained. + Failed, + + /// The rescheduling rights were obtained. + Acquired, + + /// The rescheduling rights were obtained, and the process was using a + /// timeout. + AcquiredWithTimeout, +} + +impl RescheduleRights { + pub(crate) fn are_acquired(&self) -> bool { + !matches!(self, RescheduleRights::Failed) + } +} + +/// The shared state of a process. +/// +/// This state is shared by both the process and its clients. +pub(crate) struct ProcessState { + /// The mailbox of this process. + mailbox: Mailbox, + + /// The status of the process. + status: ProcessStatus, + + /// The timeout this process is suspended with, if any. + /// + /// If missing and the process is suspended, it means the process is + /// suspended indefinitely. + timeout: Option>, +} + +impl ProcessState { + pub(crate) fn new() -> Self { + Self { + mailbox: Mailbox::new(), + status: ProcessStatus::new(), + timeout: None, + } + } + + pub(crate) fn has_same_timeout( + &self, + timeout: &ArcWithoutWeak, + ) -> bool { + self.timeout + .as_ref() + .map(|t| t.as_ptr() == timeout.as_ptr()) + .unwrap_or(false) + } + + pub(crate) fn try_reschedule_after_timeout(&mut self) -> RescheduleRights { + if !self.status.is_waiting() { + return RescheduleRights::Failed; + } + + if self.status.is_waiting_for_channel() + || self.status.is_waiting_for_io() + { + // We may be suspended for some time without actually waiting for + // anything, in that case we don't want to update the process + // status. + self.status.set_timeout_expired(true); + } + + self.status.no_longer_waiting(); + + if self.timeout.take().is_some() { + RescheduleRights::AcquiredWithTimeout + } else { + RescheduleRights::Acquired + } + } + + pub(crate) fn waiting_for_channel( + &mut self, + timeout: Option>, + ) { + self.timeout = timeout; + + self.status.set_waiting_for_channel(true); + } + + pub(crate) fn waiting_for_io( + &mut self, + timeout: Option>, + ) { + self.timeout = timeout; + + self.status.set_waiting_for_io(true); + } + + fn try_reschedule_for_message(&mut self) -> RescheduleRights { + if !self.status.is_waiting_for_message() { + return RescheduleRights::Failed; + } + + self.status.set_waiting_for_message(false); + RescheduleRights::Acquired + } + + fn try_reschedule_for_channel(&mut self) -> RescheduleRights { + if !self.status.is_waiting_for_channel() { + return RescheduleRights::Failed; + } + + self.status.set_waiting_for_channel(false); + + if self.timeout.take().is_some() { + RescheduleRights::AcquiredWithTimeout + } else { + RescheduleRights::Acquired + } + } + + pub(crate) fn try_reschedule_for_io(&mut self) -> RescheduleRights { + if !self.status.is_waiting_for_io() { + return RescheduleRights::Failed; + } + + self.status.set_waiting_for_io(false); + + if self.timeout.take().is_some() { + RescheduleRights::AcquiredWithTimeout + } else { + RescheduleRights::Acquired + } + } +} + +/// A lightweight process. +#[repr(C)] +pub struct Process { + pub header: Header, + + /// A lock acquired when running a process. + /// + /// Processes may perform operations that result in the process being + /// rescheduled by another thread. An example is when process A sends a + /// message to process B and wants to wait for it, but B reschedules A + /// before A finishes wrapping up and suspends itself. + /// + /// The run lock is meant to prevent the process from running more than + /// once, and makes implementing various aspects (e.g. sending messages) + /// easier and safe. + /// + /// This lock _is not_ used to access the shared state of a process. + /// + /// This lock is separate from the other inner fields so that native code + /// can mutate these while the lock is held, without having to explicitly + /// acquire the lock every time. + /// + /// This value is wrapped in an UnsafeCell so we can borrow it without + /// running into borrowing conflicts with methods or other fields. An + /// alternative would be to move the process-local portion into a separate + /// type and define the necessary methods on that type, instead of defining + /// them on `Process` directly. We actually used such an approach in the + /// past, but found it to be rather clunky to work with. + pub(crate) run_lock: UnsafeCell>, + + /// The current stack pointer. + /// + /// When this pointer is set to NULL it means the process no longer has an + /// associated stack. + /// + /// When this process is suspended, this pointer is the current stack + /// pointer of this process. When the process is running it instead is the + /// stack pointer of the thread that switched to running the process. + /// + /// The stack pointer is reset every time a new message is picked up. + pub(crate) stack_pointer: *mut u8, + + /// The stack memory of this process. + /// + /// This value may be absent, in which case `stack_pointer` is set to NULL. + /// We take this approach in order to keep processes as small as possible, + /// and to remove the need for unwrapping an `Option` every time we know for + /// certain a stack is present. + pub(crate) stack: ManuallyDrop, + + /// A pointer to the thread running this process. + thread: Option>, + + /// The shared state of the process. + /// + /// Multiple processes/threads may try to access this state, such as when + /// they are sending a message to this process. Access to this data doesn't + /// one obtains the run lock first. + state: Mutex, + + /// The fields of this process. + /// + /// The length of this flexible array is derived from the number of fields + /// defined in this process' class. + pub fields: [*mut u8; 0], +} + +impl Process { + pub(crate) fn drop_and_deallocate(ptr: ProcessPointer) { + unsafe { + drop_in_place(ptr.0.as_ptr()); + free(ptr.0.as_ptr()); + } + } + + pub(crate) fn alloc(class: ClassPointer, stack: Stack) -> ProcessPointer { + let ptr = allocate(unsafe { class.instance_layout() }) as *mut Self; + let obj = unsafe { &mut *ptr }; + let mut state = ProcessState::new(); + + // Processes start without any messages, so we must ensure their status + // is set accordingly. + state.status.set_waiting_for_message(true); + + obj.header.init_atomic(class); + + init!(obj.run_lock => UnsafeCell::new(Mutex::new(()))); + init!(obj.stack_pointer => stack.stack_pointer()); + init!(obj.stack => ManuallyDrop::new(stack)); + init!(obj.thread => None); + init!(obj.state => Mutex::new(state)); + + unsafe { ProcessPointer::new(ptr) } + } + + /// Returns a new Process acting as the main process. + /// + /// This process always runs on the main thread. + pub(crate) fn main( + class: ClassPointer, + method: NativeAsyncMethod, + stack: Stack, + ) -> ProcessPointer { + let mut process = Self::alloc(class, stack); + let message = Message::alloc(method, 0); + + process.set_main(); + process.send_message(message); + process + } + + pub(crate) fn set_main(&mut self) { + self.state.lock().unwrap().status.set_main(); + } + + pub(crate) fn is_main(&self) -> bool { + self.state.lock().unwrap().status.is_main() + } + + /// Suspends this process for a period of time. + pub(crate) fn suspend(&mut self, timeout: ArcWithoutWeak) { + let mut state = self.state.lock().unwrap(); + + state.timeout = Some(timeout); + state.status.set_sleeping(true); + } + + /// Sends a synchronous message to this process. + pub(crate) fn send_message( + &mut self, + message: OwnedMessage, + ) -> RescheduleRights { + let mut state = self.state.lock().unwrap(); + + state.mailbox.send(message); + state.try_reschedule_for_message() + } + + pub(crate) fn next_task(&mut self) -> Task { + let mut state = self.state.lock().unwrap(); + + if state.status.is_running() { + return Task::Resume; + } + + let message = { + if let Some(message) = state.mailbox.receive() { + message + } else { + state.status.set_waiting_for_message(true); + return Task::Wait; + } + }; + + let func = message.method; + let len = message.length as usize; + let args = unsafe { + slice::from_raw_parts(message.arguments.as_ptr(), len).to_vec() + }; + + self.stack_pointer = self.stack.stack_pointer(); + state.status.set_running(true); + Task::Start(func, args) + } + + pub(crate) fn take_stack(&mut self) -> Option { + if self.stack_pointer.is_null() { + None + } else { + self.stack_pointer = null_mut(); + + Some(unsafe { ManuallyDrop::take(&mut self.stack) }) + } + } + + /// Finishes the exection of a message, and decides what to do next with + /// this process. + /// + /// If the return value is `true`, the process should be rescheduled. + pub(crate) fn finish_message(&mut self) -> bool { + let mut state = self.state.lock().unwrap(); + + // We must clear this status so we pick up the next message when + // rescheduling the process at some point in the future. + state.status.set_running(false); + + if state.mailbox.messages.is_empty() { + state.status.set_waiting_for_message(true); + false + } else { + true + } + } + + pub(crate) fn timeout_expired(&self) -> bool { + let mut state = self.state.lock().unwrap(); + + if state.status.timeout_expired() { + state.status.set_timeout_expired(false); + true + } else { + false + } + } + + pub(crate) fn state(&self) -> MutexGuard { + self.state.lock().unwrap() + } + + /// Acquires the run lock of this process. + /// + /// We use an explicit lifetime here so the mutex guard's lifetime isn't + /// bound to `self`, allowing us to borrow it while also borrowing other + /// parts of a process. + pub(crate) fn acquire_run_lock<'a>(&self) -> MutexGuard<'a, ()> { + // Safety: the lock itself is always present, we just use UnsafeCell so + // we can borrow _just_ the lock while still being able to borrow the + // rest of the process through methods. + unsafe { (*self.run_lock.get()).lock().unwrap() } + } + + pub(crate) fn set_thread(&mut self, thread: &mut Thread) { + self.thread = Some(NonNull::from(thread)); + } + + pub(crate) fn unset_thread(&mut self) { + self.thread = None; + } + + /// Returns a mutable reference to the thread that's running this process. + /// + /// This method is unsafe as it assumes a thread is set, and the pointer + /// points to valid data. + /// + /// The lifetime of the returned reference isn't bound to `self` as doing so + /// prevents various patterns we depend on (e.g. + /// `process.thread().schedule(process)`). In addition, the reference itself + /// remains valid even when moving the process around, as a thread always + /// outlives a process. + pub(crate) unsafe fn thread<'a>(&mut self) -> &'a mut Thread { + &mut *self.thread.unwrap_unchecked().as_ptr() + } + + pub(crate) fn stacktrace(&self) -> Vec { + let mut frames = Vec::new(); + + // We don't use backtrace::trace() so we can avoid the frames introduced + // by calling this function (and any functions it may call). + let trace = backtrace::Backtrace::new(); + + for frame in trace.frames() { + backtrace::resolve(frame.ip(), |symbol| { + let name = if let Some(sym_name) = symbol.name() { + let name = sym_name.as_str().unwrap_or(""); + + // We only want to include frames for Inko source code, not + // any additional frames introduced by the runtime library + // and its dependencies. + if let Some(name) = + name.strip_prefix(INKO_SYMBOL_IDENTIFIER) + { + name.to_string() + } else { + return; + } + } else { + String::new() + }; + + let path = symbol + .filename() + .map(|v| v.to_string_lossy().into_owned()) + .unwrap_or_else(String::new); + + let line = symbol.lineno().unwrap_or(0) as i64; + + frames.push(StackFrame { name, path, line }); + }); + } + + frames.reverse(); + frames + } +} + +/// A pointer to a process. +#[repr(transparent)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct ProcessPointer(NonNull); + +unsafe impl Sync for ProcessPointer {} +unsafe impl Send for ProcessPointer {} + +impl ProcessPointer { + pub(crate) unsafe fn new(pointer: *mut Process) -> Self { + Self(NonNull::new_unchecked(pointer)) + } + + pub(crate) fn identifier(self) -> usize { + self.0.as_ptr() as usize + } + + pub(crate) fn blocking(mut self, function: impl FnOnce() -> R) -> R { + // Safety: threads are stored in processes before running them. + unsafe { self.thread().blocking(self, function) } + } +} + +impl Drop for Process { + fn drop(&mut self) { + if !self.stack_pointer.is_null() { + unsafe { + ManuallyDrop::drop(&mut self.stack); + } + } + } +} + +impl Deref for ProcessPointer { + type Target = Process; + + fn deref(&self) -> &Process { + unsafe { &*self.0.as_ptr() } + } +} + +impl DerefMut for ProcessPointer { + fn deref_mut(&mut self) -> &mut Process { + unsafe { &mut *self.0.as_mut() } + } +} + +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum SendResult { + Sent, + Full, + Reschedule(ProcessPointer), + RescheduleWithTimeout(ProcessPointer), +} + +#[derive(Eq, PartialEq, Debug)] +pub(crate) enum ReceiveResult { + None, + Some(*mut u8), + Reschedule(*mut u8, ProcessPointer), +} + +/// The internal (synchronised) state of a channel. +pub(crate) struct ChannelState { + /// The index into the ring buffer to use for sending a new value. + send_index: usize, + + /// The index into the ring buffer to use for receiving a value. + receive_index: usize, + + /// The fixed-size ring buffer of messages. + messages: Box<[*mut u8]>, + + /// Processes waiting for a message to be sent to this channel. + waiting_for_message: Vec, + + /// Processes that tried to send a message when the channel was full. + waiting_for_space: Vec, +} + +impl ChannelState { + fn new(capacity: usize) -> ChannelState { + ChannelState { + messages: (0..capacity).map(|_| null_mut()).collect(), + send_index: 0, + receive_index: 0, + waiting_for_message: Vec::new(), + waiting_for_space: Vec::new(), + } + } + + pub(crate) fn has_messages(&self) -> bool { + !self.messages[self.receive_index].is_null() + } + + pub(crate) fn add_waiting_for_message(&mut self, process: ProcessPointer) { + self.waiting_for_message.push(process); + } + + pub(crate) fn remove_waiting_for_message( + &mut self, + process: ProcessPointer, + ) { + self.waiting_for_message.retain(|&v| v != process); + } + + fn capacity(&self) -> usize { + self.messages.len() + } + + fn is_full(&self) -> bool { + !self.messages[self.send_index].is_null() + } + + fn send(&mut self, value: *mut u8) -> bool { + if self.is_full() { + return false; + } + + let index = self.send_index; + + self.messages[index] = value; + self.send_index = self.next_index(index); + true + } + + fn receive(&mut self) -> Option<*mut u8> { + let index = self.receive_index; + let value = self.messages[index]; + + if value.is_null() { + return None; + } + + self.messages[index] = null_mut(); + self.receive_index = self.next_index(index); + Some(value) + } + + fn next_index(&self, index: usize) -> usize { + // The & operator can't be used as we don't guarantee/require message + // sizes to be a power of two. The % operator is quite expensive to use: + // a simple micro benchmark at the time of writing suggested that the % + // operator is about three times slower compared to a branch like the + // one here. + if index == self.capacity() - 1 { + 0 + } else { + index + 1 + } + } +} + +/// A multiple publisher, multiple consumer first-in-first-out channel. +/// +/// Messages are sent and received in FIFO order. However, processes waiting for +/// messages or for space to be available (in case the channel is full) aren't +/// woken up in FIFO order. Currently this uses a LIFO order, but this isn't +/// guaranteed nor should this be relied upon. +/// +/// Channels are not lock-free, and as such may perform worse compared to +/// channels found in other languages (e.g. Rust or Go). This is because in its +/// current form we favour simplicity and correctness over performance. This may +/// be improved upon in the future. +/// +/// Channels are always bounded and have a minimum capacity of 1, even if the +/// user-specified capacity is 0. When a channel is full, processes sending +/// messages are to be suspended and woken up again when space is available. +#[repr(C)] +pub struct Channel { + pub(crate) header: Header, + pub(crate) state: Mutex, +} + +impl Channel { + pub(crate) fn alloc(class: ClassPointer, capacity: usize) -> *mut Channel { + let ptr = allocate(Layout::new::()) as *mut Self; + let obj = unsafe { &mut *ptr }; + + obj.header.init_atomic(class); + init!(obj.state => Mutex::new(ChannelState::new(capacity))); + ptr as _ + } + + pub(crate) unsafe fn drop(ptr: *mut Channel) { + drop_in_place(ptr); + } + + pub(crate) fn send( + &self, + sender: ProcessPointer, + message: *mut u8, + ) -> SendResult { + let mut state = self.state.lock().unwrap(); + + if !state.send(message) { + state.waiting_for_space.push(sender); + return SendResult::Full; + } + + if let Some(receiver) = state.waiting_for_message.pop() { + // We don't need to keep the lock any longer than necessary. + drop(state); + + // The process may be waiting for more than one channel to receive a + // message. In this case it's possible that multiple different + // processes try to reschedule the same waiting process, so we have + // to acquire the rescheduling rights first. + match receiver.state().try_reschedule_for_channel() { + RescheduleRights::Failed => SendResult::Sent, + RescheduleRights::Acquired => SendResult::Reschedule(receiver), + RescheduleRights::AcquiredWithTimeout => { + SendResult::RescheduleWithTimeout(receiver) + } + } + } else { + SendResult::Sent + } + } + + pub(crate) fn receive( + &self, + receiver: ProcessPointer, + timeout: Option>, + ) -> ReceiveResult { + let mut state = self.state.lock().unwrap(); + + if let Some(msg) = state.receive() { + if let Some(proc) = state.waiting_for_space.pop() { + ReceiveResult::Reschedule(msg, proc) + } else { + ReceiveResult::Some(msg) + } + } else { + receiver.state().waiting_for_channel(timeout); + state.waiting_for_message.push(receiver); + ReceiveResult::None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mem::tagged_int; + use crate::test::{empty_class, empty_process_class, OwnedProcess}; + use std::time::Duration; + + macro_rules! offset_of { + ($value: expr, $field: ident) => {{ + (std::ptr::addr_of!($value.$field) as usize) + .saturating_sub($value.0.as_ptr() as usize) + }}; + } + + unsafe extern "system" fn method(_ctx: *mut u8) { + // This function is used for testing the sending/receiving of messages. + } + + #[test] + fn test_type_sizes() { + assert_eq!(size_of::(), 16); + assert_eq!(size_of::>(), 16); + + if cfg!(target_os = "linux") { + assert_eq!(size_of::>>(), 8); + assert_eq!(size_of::(), 112); + assert_eq!(size_of::(), 104); + } else { + assert_eq!(size_of::>>(), 16); + assert_eq!(size_of::(), 128); + assert_eq!(size_of::(), 112); + } + + assert_eq!(size_of::(), 48); + assert_eq!(size_of::>>(), 8); + assert_eq!(size_of::(), 80); + } + + #[test] + fn test_field_offsets() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let proc = OwnedProcess::new(Process::alloc(*proc_class, stack)); + + assert_eq!(offset_of!(proc, header), 0); + assert_eq!( + offset_of!(proc, fields), + if cfg!(target_os = "linux") { 112 } else { 128 } + ); + } + + #[test] + fn test_process_status_new() { + let status = ProcessStatus::new(); + + assert_eq!(status.bits, 0); + } + + #[test] + fn test_process_status_set_main() { + let mut status = ProcessStatus::new(); + + status.set_main(); + + assert!(status.is_main()); + } + + #[test] + fn test_process_status_set_waiting_for_message() { + let mut status = ProcessStatus::new(); + + status.set_waiting_for_message(true); + assert!(status.is_waiting_for_message()); + + status.set_waiting_for_message(false); + assert!(!status.is_waiting_for_message()); + } + + #[test] + fn test_process_status_set_waiting_for_channel() { + let mut status = ProcessStatus::new(); + + status.set_waiting_for_channel(true); + assert!(status.is_waiting_for_channel()); + + status.set_waiting_for_channel(false); + assert!(!status.is_waiting_for_channel()); + } + + #[test] + fn test_process_status_is_waiting() { + let mut status = ProcessStatus::new(); + + status.set_sleeping(true); + assert!(status.is_waiting()); + + status.set_sleeping(false); + status.set_waiting_for_channel(true); + assert!(status.is_waiting()); + + status.no_longer_waiting(); + + assert!(!status.is_waiting_for_channel()); + assert!(!status.is_waiting()); + } + + #[test] + fn test_process_status_timeout_expired() { + let mut status = ProcessStatus::new(); + + status.set_timeout_expired(true); + assert!(status.timeout_expired()); + + status.set_timeout_expired(false); + assert!(!status.timeout_expired()); + } + + #[test] + fn test_reschedule_rights_are_acquired() { + assert!(!RescheduleRights::Failed.are_acquired()); + assert!(RescheduleRights::Acquired.are_acquired()); + assert!(RescheduleRights::AcquiredWithTimeout.are_acquired()); + } + + #[test] + fn test_process_state_has_same_timeout() { + let mut state = ProcessState::new(); + let timeout = Timeout::with_rc(Duration::from_secs(0)); + + assert!(!state.has_same_timeout(&timeout)); + + state.timeout = Some(timeout.clone()); + + assert!(state.has_same_timeout(&timeout)); + } + + #[test] + fn test_process_state_try_reschedule_after_timeout() { + let mut state = ProcessState::new(); + + assert_eq!( + state.try_reschedule_after_timeout(), + RescheduleRights::Failed + ); + + state.waiting_for_channel(None); + + assert_eq!( + state.try_reschedule_after_timeout(), + RescheduleRights::Acquired + ); + + assert!(!state.status.is_waiting_for_channel()); + assert!(!state.status.is_waiting()); + + let timeout = Timeout::with_rc(Duration::from_secs(0)); + + state.waiting_for_channel(Some(timeout)); + + assert_eq!( + state.try_reschedule_after_timeout(), + RescheduleRights::AcquiredWithTimeout + ); + + assert!(!state.status.is_waiting_for_channel()); + assert!(!state.status.is_waiting()); + } + + #[test] + fn test_process_state_waiting_for_channel() { + let mut state = ProcessState::new(); + let timeout = Timeout::with_rc(Duration::from_secs(0)); + + state.waiting_for_channel(None); + + assert!(state.status.is_waiting_for_channel()); + assert!(state.timeout.is_none()); + + state.waiting_for_channel(Some(timeout)); + + assert!(state.status.is_waiting_for_channel()); + assert!(state.timeout.is_some()); + } + + #[test] + fn test_process_state_try_reschedule_for_message() { + let mut state = ProcessState::new(); + + assert_eq!( + state.try_reschedule_for_message(), + RescheduleRights::Failed + ); + + state.status.set_waiting_for_message(true); + + assert_eq!( + state.try_reschedule_for_message(), + RescheduleRights::Acquired + ); + assert!(!state.status.is_waiting_for_message()); + } + + #[test] + fn test_process_state_try_reschedule_for_channel() { + let mut state = ProcessState::new(); + + assert_eq!( + state.try_reschedule_for_channel(), + RescheduleRights::Failed + ); + + state.status.set_waiting_for_channel(true); + assert_eq!( + state.try_reschedule_for_channel(), + RescheduleRights::Acquired + ); + assert!(!state.status.is_waiting_for_channel()); + + state.status.set_waiting_for_channel(true); + state.timeout = Some(Timeout::with_rc(Duration::from_secs(0))); + + assert_eq!( + state.try_reschedule_for_channel(), + RescheduleRights::AcquiredWithTimeout + ); + assert!(!state.status.is_waiting_for_channel()); + } + + #[test] + fn test_process_new() { + let class = empty_process_class("A"); + let process = OwnedProcess::new(Process::alloc(*class, Stack::new(32))); + + assert_eq!(process.header.class, class.0); + } + + #[test] + fn test_process_main() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let process = + OwnedProcess::new(Process::main(*proc_class, method, stack)); + + assert!(process.is_main()); + } + + #[test] + fn test_process_set_main() { + let class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*class, stack)); + + assert!(!process.is_main()); + + process.set_main(); + assert!(process.is_main()); + } + + #[test] + fn test_process_suspend() { + let class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*class, stack)); + let timeout = Timeout::with_rc(Duration::from_secs(0)); + + process.suspend(timeout); + + assert!(process.state().timeout.is_some()); + assert!(process.state().status.is_waiting()); + } + + #[test] + fn test_process_timeout_expired() { + let class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*class, stack)); + let timeout = Timeout::with_rc(Duration::from_secs(0)); + + assert!(!process.timeout_expired()); + + process.suspend(timeout); + + assert!(!process.timeout_expired()); + assert!(!process.state().status.timeout_expired()); + } + + #[test] + fn test_process_pointer_identifier() { + let ptr = unsafe { ProcessPointer::new(0x4 as *mut _) }; + + assert_eq!(ptr.identifier(), 0x4); + } + + #[test] + fn test_channel_alloc() { + let class = empty_class("Channel"); + let chan = Channel::alloc(*class, 4); + + unsafe { + let chan = &(*chan); + let state = chan.state.lock().unwrap(); + + assert_eq!(chan.header.class, *class); + assert_eq!(state.messages.len(), 4); + } + + unsafe { + Channel::drop(chan); + free(chan); + } + } + + #[test] + fn test_channel_send_empty() { + let process_class = empty_process_class("A"); + let sender = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 4); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + assert_eq!(chan.send(*sender, msg as _), SendResult::Sent); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_send_full() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + assert_eq!(chan.send(*process, msg as _), SendResult::Sent); + assert_eq!(chan.send(*process, msg as _), SendResult::Full); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_send_with_waiting() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + chan.receive(*process, None); + + assert_eq!( + chan.send(*process, msg as _), + SendResult::Reschedule(*process) + ); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_send_with_waiting_with_timeout() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + chan.receive(*process, Some(Timeout::with_rc(Duration::from_secs(0)))); + + assert_eq!( + chan.send(*process, msg as _), + SendResult::RescheduleWithTimeout(*process) + ); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_receive_empty() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + + assert_eq!(chan.receive(*process, None), ReceiveResult::None); + assert!(process.state().status.is_waiting_for_channel()); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_receive_with_messages() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + chan.send(*process, msg as _); + + assert_eq!(chan.receive(*process, None), ReceiveResult::Some(msg as _)); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_channel_receive_with_messages_with_blocked_sender() { + let process_class = empty_process_class("A"); + let process = + OwnedProcess::new(Process::alloc(*process_class, Stack::new(32))); + let class = empty_class("Channel"); + let chan_ptr = Channel::alloc(*class, 1); + let chan = unsafe { &(*chan_ptr) }; + let msg = tagged_int(42); + + chan.send(*process, msg as _); + chan.send(*process, msg as _); + + assert_eq!( + chan.receive(*process, None), + ReceiveResult::Reschedule(msg as _, *process) + ); + + unsafe { + Channel::drop(chan_ptr); + free(chan_ptr); + } + } + + #[test] + fn test_message_new() { + let message = Message::alloc(method, 2); + + assert_eq!(message.length, 2); + } + + #[test] + fn test_mailbox_send() { + let mut mail = Mailbox::new(); + let msg = Message::alloc(method, 0); + + mail.send(msg); + assert!(mail.receive().is_some()); + } + + #[test] + fn test_process_send_message() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + let msg = Message::alloc(method, 0); + + assert_eq!(process.send_message(msg), RescheduleRights::Acquired); + assert_eq!(process.state().mailbox.messages.len(), 1); + } + + #[test] + fn test_process_next_task_without_messages() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + + assert!(matches!(process.next_task(), Task::Wait)); + } + + #[test] + fn test_process_next_task_with_new_message() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + let msg = Message::alloc(method, 0); + + process.send_message(msg); + + assert!(matches!(process.next_task(), Task::Start(_, _))); + } + + #[test] + fn test_process_next_task_with_existing_message() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + let msg1 = Message::alloc(method, 0); + let msg2 = Message::alloc(method, 0); + + process.send_message(msg1); + process.next_task(); + process.send_message(msg2); + + assert!(matches!(process.next_task(), Task::Resume)); + } + + #[test] + fn test_process_take_stack() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + + assert!(process.take_stack().is_some()); + assert!(process.stack_pointer.is_null()); + } + + #[test] + fn test_process_finish_message() { + let proc_class = empty_process_class("A"); + let stack = Stack::new(32); + let mut process = OwnedProcess::new(Process::alloc(*proc_class, stack)); + + assert!(!process.finish_message()); + assert!(process.state().status.is_waiting_for_message()); + } + + #[test] + fn test_channel_state_send() { + let mut state = ChannelState::new(2); + + assert!(!state.is_full()); + assert_eq!(state.capacity(), 2); + + assert!(state.send(0x1 as _)); + assert!(state.send(0x2 as _)); + assert!(!state.send(0x3 as _)); + assert!(!state.send(0x4 as _)); + + assert_eq!(state.messages[0], 0x1 as _); + assert_eq!(state.messages[1], 0x2 as _); + assert!(state.is_full()); + } + + #[test] + fn test_channel_state_receive() { + let mut state = ChannelState::new(2); + + assert!(state.receive().is_none()); + + state.send(0x1 as _); + state.send(0x2 as _); + + assert!(state.is_full()); + assert_eq!(state.receive(), Some(0x1 as _)); + assert!(!state.is_full()); + + assert_eq!(state.receive(), Some(0x2 as _)); + assert_eq!(state.receive(), None); + assert!(!state.is_full()); + } + + #[test] + fn test_channel_state_has_messages() { + let mut state = ChannelState::new(2); + + assert!(!state.has_messages()); + + state.send(0x1 as _); + assert!(state.has_messages()); + + state.receive(); + assert!(!state.has_messages()); + + state.send(0x1 as _); + assert!(state.has_messages()); + } +} diff --git a/rt/src/result.rs b/rt/src/result.rs new file mode 100644 index 000000000..11d5ca467 --- /dev/null +++ b/rt/src/result.rs @@ -0,0 +1,96 @@ +use crate::mem::tagged_int; +use std::io; +use std::ptr::null_mut; + +const INVALID_INPUT: i64 = 11; +const TIMED_OUT: i64 = 13; + +const OK: i64 = 0; +const NONE: i64 = 1; +const ERROR: i64 = 2; + +/// A result type that is FFI safe and wraps a pointer. +/// +/// Various functions in the runtime library need a way to signal an OK versus +/// an error value. Some of these errors are simple IO error codes, while others +/// may be strings or something else. Rust's built-in `Result` type isn't FFI +/// safe and as such we can't use it in our runtime functions. +/// +/// This type is essentially Rust's `Result` type, minus any methods as we don't +/// use it as output and not input. The layout is fixed so generated code can +/// use it as if this type were defined in the generated code directly. +/// +/// The order of this type is and must stay fixed, as rearranging the order of +/// the variants breaks generated code (unless it too is updated accordingly). +/// +/// We're using a struct here instead of an enum as this gives us more precise +/// control over the layout, and lets us test the exact field offsets. +#[repr(C)] +#[derive(Eq, PartialEq, Debug)] +pub struct Result { + pub tag: *mut u8, + pub value: *mut u8, +} + +impl Result { + pub(crate) fn ok(value: *mut u8) -> Result { + Result { tag: tagged_int(OK) as _, value } + } + + pub(crate) fn error(value: *mut u8) -> Result { + Result { tag: tagged_int(ERROR) as _, value } + } + + pub(crate) fn none() -> Result { + Result { tag: tagged_int(NONE) as _, value: null_mut() } + } + + pub(crate) fn ok_boxed(value: T) -> Result { + Result::ok(Box::into_raw(Box::new(value)) as _) + } + + pub(crate) fn io_error(error: io::Error) -> Result { + let code = match error.kind() { + io::ErrorKind::NotFound => 1, + io::ErrorKind::PermissionDenied => 2, + io::ErrorKind::ConnectionRefused => 3, + io::ErrorKind::ConnectionReset => 4, + io::ErrorKind::ConnectionAborted => 5, + io::ErrorKind::NotConnected => 6, + io::ErrorKind::AddrInUse => 7, + io::ErrorKind::AddrNotAvailable => 8, + io::ErrorKind::BrokenPipe => 9, + io::ErrorKind::AlreadyExists => 10, + io::ErrorKind::InvalidInput => INVALID_INPUT, + io::ErrorKind::InvalidData => 12, + io::ErrorKind::TimedOut => TIMED_OUT, + io::ErrorKind::WriteZero => 14, + io::ErrorKind::Interrupted => 15, + io::ErrorKind::UnexpectedEof => 16, + _ => 0, + }; + + Self::error(tagged_int(code) as _) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem::size_of; + use std::ptr::addr_of; + + #[test] + fn test_memory_layout() { + assert_eq!(size_of::(), 16); + } + + #[test] + fn test_field_offsets() { + let res = Result::ok(0x4 as _); + let base = addr_of!(res) as usize; + + assert_eq!(addr_of!(res.tag) as usize - base, 0); + assert_eq!(addr_of!(res.value) as usize - base, 8); + } +} diff --git a/rt/src/runtime.rs b/rt/src/runtime.rs new file mode 100644 index 000000000..f520714d9 --- /dev/null +++ b/rt/src/runtime.rs @@ -0,0 +1,143 @@ +mod array; +mod byte_array; +mod class; +mod env; +mod float; +mod fs; +mod general; +mod helpers; +mod int; +mod process; +mod random; +mod socket; +mod stdio; +mod string; +mod sys; +mod time; + +use crate::config::Config; +use crate::mem::ClassPointer; +use crate::network_poller::Worker as NetworkPollerWorker; +use crate::process::{NativeAsyncMethod, Process}; +use crate::scheduler::{number_of_cores, pin_thread_to_core}; +use crate::stack::Stack; +use crate::state::{MethodCounts, RcState, State}; +use std::env::args_os; +use std::io::{stdout, Write as _}; +use std::process::exit as rust_exit; +use std::thread; + +#[cfg(unix)] +fn ignore_sigpipe() { + // Broken pipe errors default to terminating the entire program, making it + // impossible to handle such errors. This is especially problematic for + // sockets, as writing to a socket closed on the other end would terminate + // the program. + // + // While Rust handles this for us when compiling an executable, it doesn't + // do so when compiling it to a static library and linking it to our + // generated code, so we must handle this ourselves. + unsafe { + libc::signal(libc::SIGPIPE, libc::SIG_IGN); + } +} + +#[cfg(not(unix))] +fn ignore_sigpipe() { + // Not needed on these platforms +} + +#[no_mangle] +pub unsafe extern "system" fn inko_runtime_new( + counts: *mut MethodCounts, +) -> *mut Runtime { + Box::into_raw(Box::new(Runtime::new(&*counts))) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_runtime_drop(runtime: *mut Runtime) { + drop(Box::from_raw(runtime)); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_runtime_start( + runtime: *mut Runtime, + class: ClassPointer, + method: NativeAsyncMethod, +) { + ignore_sigpipe(); + (*runtime).start(class, method); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_runtime_state( + runtime: *mut Runtime, +) -> *const State { + (*runtime).state.as_ptr() as _ +} + +pub(crate) fn exit(status: i32) -> ! { + // STDOUT is buffered by default, and not flushing it upon exit may result + // in parent processes not observing the output. + let _ = stdout().lock().flush(); + + rust_exit(status); +} + +/// An Inko runtime along with all its state. +#[repr(C)] +pub struct Runtime { + state: RcState, +} + +impl Runtime { + /// Returns a new `Runtime` instance. + /// + /// This method sets up the runtime and allocates the core classes, but + /// doesn't start any threads. + fn new(counts: &MethodCounts) -> Self { + let config = Config::from_env(); + let args: Vec<_> = args_os() + .skip(1) + .map(|v| v.to_string_lossy().into_owned()) + .collect(); + + Self { state: State::new(config, counts, &args) } + } + + /// Starts the runtime using the given process and method as the entry + /// point. + /// + /// This method blocks the current thread until the program terminates, + /// though this thread itself doesn't run any processes (= it just + /// waits/blocks until completion). + fn start(&self, main_class: ClassPointer, main_method: NativeAsyncMethod) { + let state = self.state.clone(); + let cores = number_of_cores(); + + thread::Builder::new() + .name("timeout".to_string()) + .spawn(move || { + pin_thread_to_core(0); + state.timeout_worker.run(&state.scheduler) + }) + .unwrap(); + + for id in 0..self.state.network_pollers.len() { + let state = self.state.clone(); + + thread::Builder::new() + .name(format!("netpoll {}", id)) + .spawn(move || { + pin_thread_to_core(1 % cores); + NetworkPollerWorker::new(id, state).run() + }) + .unwrap(); + } + + let stack = Stack::new(self.state.config.stack_size as usize); + let main_proc = Process::main(main_class, main_method, stack); + + self.state.scheduler.run(&self.state, main_proc); + } +} diff --git a/rt/src/runtime/array.rs b/rt/src/runtime/array.rs new file mode 100644 index 000000000..e9d4785f3 --- /dev/null +++ b/rt/src/runtime/array.rs @@ -0,0 +1,115 @@ +use crate::mem::{Array, Int, Nil}; +use crate::result::Result as InkoResult; +use crate::state::State; + +#[no_mangle] +pub unsafe extern "system" fn inko_array_new( + state: *const State, + capacity: i64, +) -> *mut Array { + Array::alloc((*state).array_class, Vec::with_capacity(capacity as _)) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_new_permanent( + state: *const State, + capacity: i64, +) -> *mut Array { + Array::alloc_permanent( + (*state).array_class, + Vec::with_capacity(capacity as _), + ) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_reserve( + state: *const State, + array: *mut Array, + length: i64, +) -> *const Nil { + (*array).value.reserve_exact(length as _); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_push( + state: *const State, + array: *mut Array, + value: *mut u8, +) -> *const Nil { + (*array).value.push(value); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_pop(array: *mut Array) -> InkoResult { + (*array) + .value + .pop() + .map(|v| InkoResult::ok(v as _)) + .unwrap_or_else(InkoResult::none) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_set( + array: *mut Array, + index: i64, + value: *mut u8, +) -> *mut u8 { + let array = &mut *array; + let index_ref = array.value.get_unchecked_mut(index as usize); + let old_value = *index_ref; + + *index_ref = value; + old_value +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_get( + array: *const Array, + index: i64, +) -> *mut u8 { + *(*array).value.get_unchecked(index as usize) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_remove( + array: *mut Array, + index: i64, +) -> *mut u8 { + (*array).value.remove(index as usize) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_length( + state: *const State, + array: *const Array, +) -> *const Int { + Int::new((*state).int_class, (*array).value.len() as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_capacity( + state: *const State, + array: *const Array, +) -> *const Int { + Int::new((*state).int_class, (*array).value.capacity() as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_clear( + state: *const State, + array: *mut Array, +) -> *const Nil { + (*array).value.clear(); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_array_drop( + state: *const State, + array: *mut Array, +) -> *const Nil { + Array::drop(array); + (*state).nil_singleton +} diff --git a/rt/src/runtime/byte_array.rs b/rt/src/runtime/byte_array.rs new file mode 100644 index 000000000..0be580dfd --- /dev/null +++ b/rt/src/runtime/byte_array.rs @@ -0,0 +1,188 @@ +use crate::immutable_string::ImmutableString; +use crate::mem::{tagged_int, Bool, ByteArray, Int, Nil, String as InkoString}; +use crate::state::State; +use std::cmp::min; + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_new( + state: *const State, +) -> *mut ByteArray { + ByteArray::alloc((*state).byte_array_class, Vec::new()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_push( + state: *const State, + bytes: *mut ByteArray, + value: i64, +) -> *const Nil { + (*bytes).value.push(value as u8); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_pop( + bytes: *mut ByteArray, +) -> *const Int { + if let Some(value) = (*bytes).value.pop() { + tagged_int(value as i64) + } else { + tagged_int(-1) + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_set( + bytes: *mut ByteArray, + index: i64, + value: i64, +) -> *const Int { + let bytes = &mut (*bytes).value; + let index_ref = bytes.get_unchecked_mut(index as usize); + let old_value = *index_ref; + + *index_ref = value as u8; + tagged_int(old_value as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_get( + bytes: *mut ByteArray, + index: i64, +) -> *const Int { + tagged_int(*(*bytes).value.get_unchecked(index as usize) as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_remove( + bytes: *mut ByteArray, + index: i64, +) -> *const Int { + tagged_int((*bytes).value.remove(index as usize) as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_length( + state: *const State, + bytes: *const ByteArray, +) -> *const Int { + Int::new((*state).int_class, (*bytes).value.len() as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_eq( + state: *const State, + lhs: *const ByteArray, + rhs: *const ByteArray, +) -> *const Bool { + let state = &*state; + + if (*lhs).value == (*rhs).value { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_clear( + state: *const State, + bytes: *mut ByteArray, +) -> *const Nil { + (*bytes).value.clear(); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_clone( + state: *const State, + bytes: *const ByteArray, +) -> *mut ByteArray { + ByteArray::alloc((*state).byte_array_class, (*bytes).value.clone()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_drop( + state: *const State, + array: *mut ByteArray, +) -> *const Nil { + ByteArray::drop(array); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_to_string( + state: *const State, + bytes: *const ByteArray, +) -> *const InkoString { + let bytes = &(*bytes).value; + let string = ImmutableString::from_utf8(bytes.clone()); + + InkoString::from_immutable_string((*state).string_class, string) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_drain_to_string( + state: *const State, + bytes: *mut ByteArray, +) -> *const InkoString { + let bytes = &mut (*bytes); + let string = ImmutableString::from_utf8(bytes.take_bytes()); + + InkoString::from_immutable_string((*state).string_class, string) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_slice( + state: *const State, + bytes: *const ByteArray, + start: i64, + length: i64, +) -> *mut ByteArray { + let bytes = &*bytes; + let end = min((start + length) as usize, bytes.value.len()); + + ByteArray::alloc( + (*state).byte_array_class, + bytes.value[start as usize..end].to_vec(), + ) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_append( + state: *const State, + target: *mut ByteArray, + source: *mut ByteArray, +) -> *const Nil { + (*target).value.append(&mut (*source).value); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_copy_from( + state: *const State, + target: *mut ByteArray, + source: *mut ByteArray, + start: i64, + length: i64, +) -> *const Int { + let target = &mut *target; + let source = &mut *source; + let end = min((start + length) as usize, source.value.len()); + let slice = &source.value[start as usize..end]; + let amount = slice.len() as i64; + + target.value.extend_from_slice(slice); + Int::new((*state).int_class, amount) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_byte_array_resize( + state: *const State, + bytes: *mut ByteArray, + size: i64, + filler: i64, +) -> *const Nil { + (*bytes).value.resize(size as usize, filler as u8); + (*state).nil_singleton +} diff --git a/rt/src/runtime/class.rs b/rt/src/runtime/class.rs new file mode 100644 index 000000000..228a3cda0 --- /dev/null +++ b/rt/src/runtime/class.rs @@ -0,0 +1,31 @@ +use crate::mem::{Class, ClassPointer}; +use std::{ffi::CStr, os::raw::c_char}; + +#[no_mangle] +pub unsafe extern "system" fn inko_class_object( + name: *const c_char, + fields: u8, + methods: u16, +) -> ClassPointer { + let name = + String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned(); + + Class::object(name, fields, methods) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_class_process( + name: *const c_char, + fields: u8, + methods: u16, +) -> ClassPointer { + let name = + String::from_utf8_lossy(CStr::from_ptr(name).to_bytes()).into_owned(); + + Class::process(name, fields, methods) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_class_drop(class: ClassPointer) { + Class::drop(class); +} diff --git a/rt/src/runtime/env.rs b/rt/src/runtime/env.rs new file mode 100644 index 000000000..85436f0d0 --- /dev/null +++ b/rt/src/runtime/env.rs @@ -0,0 +1,122 @@ +use crate::mem::{Array, String as InkoString}; +use crate::result::Result as InkoResult; +use crate::state::State; +use std::env; +use std::path::PathBuf; + +#[no_mangle] +pub unsafe extern "system" fn inko_env_get( + state: *const State, + name: *const InkoString, +) -> InkoResult { + let state = &(*state); + let name = InkoString::read(name); + + state + .environment + .get(name) + .cloned() + .map(|path| InkoResult::ok(path as _)) + .unwrap_or_else(InkoResult::none) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_variables( + state: *const State, +) -> *mut Array { + let state = &*state; + let names = state + .environment + .keys() + .map(|key| { + InkoString::alloc(state.string_class, key.clone()) as *mut u8 + }) + .collect(); + + Array::alloc(state.array_class, names) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_home_directory( + state: *const State, +) -> InkoResult { + let state = &*state; + + // Rather than performing all sorts of magical incantations to get the home + // directory, we're just going to require that HOME is set. + // + // If the home is explicitly set to an empty string we still ignore it, + // because there's no scenario in which Some("") is useful. + state + .environment + .get("HOME") + .cloned() + .filter(|&path| !InkoString::read(path).is_empty()) + .map(|path| InkoResult::ok(path as _)) + .unwrap_or_else(InkoResult::none) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_temp_directory( + state: *const State, +) -> *const InkoString { + let path = canonalize(env::temp_dir().to_string_lossy().into_owned()); + + InkoString::alloc((*state).string_class, path) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_get_working_directory( + state: *const State, +) -> InkoResult { + env::current_dir() + .map(|path| canonalize(path.to_string_lossy().into_owned())) + .map(|path| { + InkoResult::ok(InkoString::alloc((*state).string_class, path) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_set_working_directory( + state: *const State, + directory: *const InkoString, +) -> InkoResult { + let state = &*state; + let dir = InkoString::read(directory); + + env::set_current_dir(dir) + .map(|_| InkoResult::ok(state.nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_arguments( + state: *const State, +) -> *mut Array { + let state = &*state; + + Array::alloc( + state.array_class, + state.arguments.iter().map(|&v| v as _).collect(), + ) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_env_executable( + state: *const State, +) -> InkoResult { + env::current_exe() + .map(|path| path.to_string_lossy().into_owned()) + .map(|path| { + InkoResult::ok(InkoString::alloc((*state).string_class, path) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +fn canonalize(path: String) -> String { + PathBuf::from(&path) + .canonicalize() + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or(path) +} diff --git a/rt/src/runtime/float.rs b/rt/src/runtime/float.rs new file mode 100644 index 000000000..e615dc20c --- /dev/null +++ b/rt/src/runtime/float.rs @@ -0,0 +1,116 @@ +use crate::mem::{Bool, Float, String as InkoString}; +use crate::state::State; + +/// The maximum difference between two floats for them to be considered equal, +/// as expressed in "Units in the Last Place" (ULP). +const ULP_DIFF: i64 = 1; + +#[no_mangle] +pub unsafe extern "system" fn inko_float_boxed( + state: *const State, + value: f64, +) -> *const Float { + Float::alloc((*state).float_class, value) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_float_boxed_permanent( + state: *const State, + value: f64, +) -> *const Float { + Float::alloc_permanent((*state).float_class, value) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_float_eq( + state: *const State, + left: f64, + right: f64, +) -> *const Bool { + // For float equality we use ULPs. See + // https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ + // for more details. + let state = &*state; + + if left == right { + // Handle cases such as `-0.0 == 0.0`. + return state.true_singleton; + } + + if left.is_sign_positive() != right.is_sign_positive() { + return state.false_singleton; + } + + if left.is_nan() || right.is_nan() { + return state.false_singleton; + } + + let left_bits = left.to_bits() as i64; + let right_bits = right.to_bits() as i64; + let diff = left_bits.wrapping_sub(right_bits); + + if (-ULP_DIFF..=ULP_DIFF).contains(&diff) { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_float_clone( + state: *const State, + float: *const Float, +) -> *const Float { + let obj = &*float; + + if obj.header.is_permanent() { + float + } else { + Float::alloc((*state).float_class, obj.value) + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_float_round( + state: *const State, + float: f64, + precision: i64, +) -> *const Float { + let result = if precision == 0 { + float.round() + } else if precision <= i64::from(u32::MAX) { + let power = 10.0_f64.powi(precision as i32); + let multiplied = float * power; + + // Certain very large numbers (e.g. f64::MAX) would produce Infinity + // when multiplied with the power. In this case we just return the input + // float directly. + if multiplied.is_finite() { + multiplied.round() / power + } else { + float + } + } else { + float + }; + + Float::alloc((*state).float_class, result) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_float_to_string( + state: *const State, + value: f64, +) -> *const InkoString { + let string = if value.is_infinite() && value.is_sign_positive() { + "Infinity".to_string() + } else if value.is_infinite() { + "-Infinity".to_string() + } else if value.is_nan() { + "NaN".to_string() + } else { + format!("{:?}", value) + }; + + InkoString::alloc((*state).string_class, string) +} diff --git a/rt/src/runtime/fs.rs b/rt/src/runtime/fs.rs new file mode 100644 index 000000000..ee63b6165 --- /dev/null +++ b/rt/src/runtime/fs.rs @@ -0,0 +1,369 @@ +use crate::mem::{ + Array, Bool, ByteArray, Float, Int, Nil, String as InkoString, +}; +use crate::process::ProcessPointer; +use crate::result::Result as InkoResult; +use crate::runtime::helpers::read_into; +use crate::state::State; +use std::fs::{self, File, OpenOptions}; +use std::io::{self, Seek, SeekFrom, Write}; +use std::path::PathBuf; +use std::time::{SystemTime, UNIX_EPOCH}; + +#[no_mangle] +pub unsafe extern "system" fn inko_file_drop( + state: *const State, + file: *mut File, +) -> *const Nil { + drop(Box::from_raw(file)); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_seek( + state: *const State, + process: ProcessPointer, + file: *mut File, + offset: i64, +) -> InkoResult { + let seek = if offset < 0 { + SeekFrom::End(offset) + } else { + SeekFrom::Start(offset as u64) + }; + + process + .blocking(|| (*file).seek(seek)) + .map( + |res| InkoResult::ok(Int::new((*state).int_class, res as i64) as _), + ) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_flush( + state: *const State, + process: ProcessPointer, + file: *mut File, +) -> InkoResult { + process + .blocking(|| (*file).flush()) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_write_string( + state: *const State, + process: ProcessPointer, + file: *mut File, + input: *const InkoString, +) -> InkoResult { + process + .blocking(|| (*file).write(InkoString::read(input).as_bytes())) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_write_bytes( + state: *const State, + process: ProcessPointer, + file: *mut File, + input: *mut ByteArray, +) -> InkoResult { + process + .blocking(|| (*file).write(&(*input).value)) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_copy( + state: *const State, + process: ProcessPointer, + from: *const InkoString, + to: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::copy(InkoString::read(from), InkoString::read(to))) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_size( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::metadata(InkoString::read(path))) + .map(|meta| { + InkoResult::ok(Int::new((*state).int_class, meta.len() as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_remove( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::remove_file(InkoString::read(path))) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_created_at( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::metadata(InkoString::read(path))) + .and_then(|meta| meta.created()) + .map(system_time_to_timestamp) + .map(|time| { + InkoResult::ok(Float::alloc((*state).float_class, time) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_modified_at( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::metadata(InkoString::read(path))) + .and_then(|meta| meta.modified()) + .map(system_time_to_timestamp) + .map(|time| { + InkoResult::ok(Float::alloc((*state).float_class, time) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_accessed_at( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::metadata(InkoString::read(path))) + .and_then(|meta| meta.accessed()) + .map(system_time_to_timestamp) + .map(|time| { + InkoResult::ok(Float::alloc((*state).float_class, time) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_expand( + state: *const State, + path: *const InkoString, +) -> InkoResult { + let path = InkoString::read(path); + + PathBuf::from(path) + .canonicalize() + .map(|p| p.to_string_lossy().into_owned()) + .map(|p| { + InkoResult::ok(InkoString::alloc((*state).string_class, p) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_is_file( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> *const Bool { + let state = &*state; + let meta = process.blocking(|| fs::metadata(InkoString::read(path))); + + if meta.map(|m| m.is_file()).unwrap_or(false) { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_is_directory( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> *const Bool { + let state = &*state; + let meta = process.blocking(|| fs::metadata(InkoString::read(path))); + + if meta.map(|m| m.is_dir()).unwrap_or(false) { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_path_exists( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> *const Bool { + let state = &*state; + let meta = process.blocking(|| fs::metadata(InkoString::read(path))); + + if meta.is_ok() { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_open( + process: ProcessPointer, + path: *const InkoString, + mode: i64, +) -> InkoResult { + let mut opts = OpenOptions::new(); + + match mode { + 0 => opts.read(true), // Read-only + 1 => opts.write(true).truncate(true).create(true), // Write-only + 2 => opts.append(true).create(true), // Append-only + 3 => opts.read(true).write(true).create(true), // Read-write + _ => opts.read(true).append(true).create(true), // Read-append + }; + + open_file(process, opts, path).unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_file_read( + state: *const State, + process: ProcessPointer, + file: *mut File, + buffer: *mut ByteArray, + size: i64, +) -> InkoResult { + let file = &mut *file; + let buffer = &mut (*buffer).value; + + process + .blocking(|| read_into(file, buffer, size)) + .map(|size| InkoResult::ok(Int::new((*state).int_class, size) as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_directory_create( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::create_dir(InkoString::read(path))) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_directory_create_recursive( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::create_dir_all(InkoString::read(path))) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_directory_remove( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::remove_dir(InkoString::read(path))) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_directory_remove_all( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + process + .blocking(|| fs::remove_dir_all(InkoString::read(path))) + .map(|_| InkoResult::ok((*state).nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_directory_list( + state: *const State, + process: ProcessPointer, + path: *const InkoString, +) -> InkoResult { + let state = &*state; + let mut paths = Vec::new(); + let entries = + match process.blocking(|| fs::read_dir(InkoString::read(path))) { + Ok(entries) => entries, + Err(err) => return InkoResult::io_error(err), + }; + + for entry in entries { + let entry = match entry { + Ok(entry) => entry, + Err(err) => return InkoResult::io_error(err), + }; + + let path = entry.path().to_string_lossy().to_string(); + let pointer = InkoString::alloc(state.string_class, path); + + paths.push(pointer as *mut u8); + } + + InkoResult::ok(Array::alloc(state.array_class, paths) as _) +} + +unsafe fn open_file( + process: ProcessPointer, + options: OpenOptions, + path: *const InkoString, +) -> Result { + process + .blocking(|| options.open(InkoString::read(path))) + .map(|file| InkoResult::ok(Box::into_raw(Box::new(file)) as _)) +} + +fn system_time_to_timestamp(time: SystemTime) -> f64 { + let duration = if time < UNIX_EPOCH { + UNIX_EPOCH.duration_since(time) + } else { + time.duration_since(UNIX_EPOCH) + }; + + duration.unwrap().as_secs_f64() +} diff --git a/rt/src/runtime/general.rs b/rt/src/runtime/general.rs new file mode 100644 index 000000000..4fc8785ed --- /dev/null +++ b/rt/src/runtime/general.rs @@ -0,0 +1,74 @@ +use crate::context; +use crate::mem::{free, header_of, is_tagged_int, ClassPointer}; +use crate::process::ProcessPointer; +use crate::runtime::exit; +use crate::runtime::process::panic; +use std::alloc::alloc; + +#[no_mangle] +pub unsafe extern "system" fn inko_exit(status: i64) { + exit(status as i32); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_check_refs( + process: ProcessPointer, + pointer: *const u8, +) { + if is_tagged_int(pointer) { + return; + } + + let header = header_of(pointer); + + if header.is_permanent() { + return; + } + + let refs = header.references(); + + if refs == 0 { + return; + } + + panic( + process, + &format!( + "can't drop a value of type '{}' as it still has {} reference(s)", + &header.class.name, refs + ), + ); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_free(pointer: *mut u8) { + if is_tagged_int(pointer) || header_of(pointer).is_permanent() { + return; + } + + free(pointer); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_reduce( + mut process: ProcessPointer, + amount: u16, +) { + let thread = process.thread(); + + thread.reductions = thread.reductions.saturating_sub(amount); + + if thread.reductions == 0 { + // Safety: the current thread is holding on to the run lock + thread.schedule(process); + context::switch(process); + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_alloc(class: ClassPointer) -> *mut u8 { + let ptr = alloc(class.instance_layout()); + + header_of(ptr).init(class); + ptr +} diff --git a/rt/src/runtime/helpers.rs b/rt/src/runtime/helpers.rs new file mode 100644 index 000000000..178fcbce5 --- /dev/null +++ b/rt/src/runtime/helpers.rs @@ -0,0 +1,16 @@ +use std::io::{self, Read}; + +/// Reads a number of bytes from a buffer into a Vec. +pub(crate) fn read_into( + stream: &mut T, + output: &mut Vec, + size: i64, +) -> Result { + let read = if size > 0 { + stream.take(size as u64).read_to_end(output)? + } else { + stream.read_to_end(output)? + }; + + Ok(read as i64) +} diff --git a/rt/src/runtime/int.rs b/rt/src/runtime/int.rs new file mode 100644 index 000000000..65950e48b --- /dev/null +++ b/rt/src/runtime/int.rs @@ -0,0 +1,74 @@ +use crate::mem::{Float, Int, String as InkoString}; +use crate::process::ProcessPointer; +use crate::runtime::process::panic; +use crate::state::State; + +#[no_mangle] +pub unsafe extern "system" fn inko_int_overflow( + process: ProcessPointer, + left: i64, + right: i64, +) -> ! { + let message = format!("Int overflowed, left: {}, right: {}", left, right); + + panic(process, &message); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_boxed( + state: *const State, + value: i64, +) -> *const Int { + Int::boxed((*state).int_class, value) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_boxed_permanent( + state: *const State, + value: i64, +) -> *const Int { + Int::boxed_permanent((*state).int_class, value) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_pow( + process: ProcessPointer, + left: i64, + right: i64, +) -> i64 { + if let Some(val) = left.checked_pow(right as u32) { + val + } else { + inko_int_overflow(process, left, right); + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_clone( + state: *const State, + int: *const Int, +) -> *const Int { + let obj = &*int; + + if obj.header.is_permanent() { + int + } else { + Int::boxed((*state).int_class, obj.value) + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_to_float( + state: *const State, + int: i64, +) -> *const Float { + Float::alloc((*state).float_class, int as f64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_int_to_string( + state: *const State, + int: i64, +) -> *const InkoString { + InkoString::alloc((*state).string_class, int.to_string()) +} diff --git a/rt/src/runtime/process.rs b/rt/src/runtime/process.rs new file mode 100644 index 000000000..8e9ff98f1 --- /dev/null +++ b/rt/src/runtime/process.rs @@ -0,0 +1,377 @@ +use crate::context; +use crate::mem::{Array, ClassPointer, Int, Nil, String as InkoString}; +use crate::process::{ + Channel, Message, NativeAsyncMethod, OwnedMessage, Process, ProcessPointer, + ReceiveResult, RescheduleRights, SendResult, StackFrame, +}; +use crate::result::Result as InkoResult; +use crate::runtime::exit; +use crate::scheduler::process::Action; +use crate::scheduler::timeouts::Timeout; +use crate::state::State; +use std::cmp::max; +use std::fmt::Write as _; +use std::str; +use std::time::Duration; + +const SEND_ERROR: &str = "Processes can't send messages to themselves, \ + as this could result in deadlocks"; + +/// Terminates the current program with an Inko panic (opposed to a panic +/// triggered using the `panic!` macro). +/// +/// This function is marked as cold as we expect it to be called rarely, if ever +/// (in a correct program). This should also ensure any branches leading to this +/// function are treated as unlikely. +#[inline(never)] +#[cold] +pub(crate) fn panic(process: ProcessPointer, message: &str) -> ! { + let mut buffer = String::new(); + + buffer.push_str("Stack trace (the most recent call comes last):"); + + for frame in process.stacktrace() { + let _ = if !frame.path.is_empty() && frame.line > 0 { + write!( + buffer, + "\n {}:{} in {}", + frame.path, frame.line, frame.name, + ) + } else { + write!(buffer, "\n ?? in {}", frame.name) + }; + } + + let _ = write!( + buffer, + "\nProcess '{}' ({:#x}) panicked: {}", + process.header.class.name, + process.identifier(), + message + ); + + eprintln!("{}", buffer); + + // There's no real standard across programs for exit codes. Rust uses 101 so + // for the sake of "we don't know a better value", we also use 101. + exit(101); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_panic( + process: ProcessPointer, + message: *const InkoString, +) { + let msg = &(*message).value; + + panic(process, msg); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_new( + mut process: ProcessPointer, + class: ClassPointer, +) -> ProcessPointer { + let stack = process.thread().stacks.alloc(); + + Process::alloc(class, stack) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_message_new( + method: NativeAsyncMethod, + length: u8, +) -> *mut Message { + Message::alloc(method, length).into_raw() +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_send_message( + state: *const State, + mut sender: ProcessPointer, + mut receiver: ProcessPointer, + message: *mut Message, +) { + if sender == receiver { + panic(sender, SEND_ERROR); + } + + let message = OwnedMessage::from_raw(message); + let state = &*state; + let reschedule = match receiver.send_message(message) { + RescheduleRights::AcquiredWithTimeout => { + state.timeout_worker.increase_expired_timeouts(); + true + } + RescheduleRights::Acquired => true, + RescheduleRights::Failed => false, + }; + + if reschedule { + sender.thread().schedule(receiver); + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_finish_message( + mut process: ProcessPointer, + terminate: bool, +) { + let resched = process.finish_message(); + + if terminate { + // Safety: we can't terminate the process here as that would result in + // us corrupting the current stack (= the process' stack), so instead we + // defer this until we switch back to the thread's stack. + process.thread().action = Action::Terminate; + } else if resched { + process.thread().schedule(process); + } + + context::switch(process); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_yield(mut process: ProcessPointer) { + // Safety: the current thread is holding on to the run lock + process.thread().schedule(process); + context::switch(process); +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_suspend( + state: *const State, + mut process: ProcessPointer, + nanos: i64, +) -> *const Nil { + let timeout = Timeout::with_rc(Duration::from_nanos(nanos as _)); + let state = &*state; + + // Safety: the current thread is holding on to the run lock + process.suspend(timeout.clone()); + state.timeout_worker.suspend(process, timeout); + context::switch(process); + state.nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stacktrace( + process: ProcessPointer, +) -> *mut Vec { + Box::into_raw(Box::new(process.stacktrace())) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stack_frame_name( + state: *const State, + trace: *const Vec, + index: i64, +) -> *const InkoString { + let val = &(*trace).get_unchecked(index as usize).name; + + InkoString::alloc((*state).string_class, val.clone()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stack_frame_path( + state: *const State, + trace: *const Vec, + index: i64, +) -> *const InkoString { + let val = &(*trace).get_unchecked(index as usize).path; + + InkoString::alloc((*state).string_class, val.clone()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stack_frame_line( + state: *const State, + trace: *const Vec, + index: i64, +) -> *const Int { + let val = (*trace).get_unchecked(index as usize).line; + + Int::new((*state).int_class, val) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stacktrace_length( + state: *const State, + trace: *const Vec, +) -> *const Int { + Int::new((*state).int_class, (*trace).len() as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_process_stacktrace_drop( + state: *const State, + trace: *mut Vec, +) -> *const Nil { + drop(Box::from_raw(trace)); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_new( + state: *const State, + capacity: i64, +) -> *mut Channel { + Channel::alloc((*state).channel_class, max(capacity, 1) as usize) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_send( + state: *const State, + mut process: ProcessPointer, + channel: *const Channel, + message: *mut u8, +) -> *const Nil { + let state = &*state; + + loop { + match (*channel).send(process, message) { + SendResult::Sent => break, + SendResult::Full => context::switch(process), + SendResult::Reschedule(receiver) => { + process.thread().schedule_global(receiver); + break; + } + SendResult::RescheduleWithTimeout(receiver) => { + state.timeout_worker.increase_expired_timeouts(); + process.thread().schedule_global(receiver); + break; + } + } + } + + state.nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_receive( + mut process: ProcessPointer, + channel: *const Channel, +) -> *const u8 { + loop { + match (*channel).receive(process, None) { + ReceiveResult::None => context::switch(process), + ReceiveResult::Some(msg) => return msg, + ReceiveResult::Reschedule(msg, sender) => { + // We schedule onto the global queue because the current process + // wants to do something with the message. + process.thread().schedule_global(sender); + return msg; + } + } + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_try_receive( + mut process: ProcessPointer, + channel: *const Channel, +) -> InkoResult { + match (*channel).receive(process, None) { + ReceiveResult::None => InkoResult::none(), + ReceiveResult::Some(msg) => InkoResult::ok(msg as _), + ReceiveResult::Reschedule(msg, sender) => { + // We schedule onto the global queue because the current process + // wants to do something with the message. + process.thread().schedule_global(sender); + InkoResult::ok(msg as _) + } + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_receive_until( + state: *const State, + mut process: ProcessPointer, + channel: *const Channel, + nanos: u64, +) -> InkoResult { + let state = &(*state); + let deadline = Timeout::from_nanos_deadline(state, nanos); + + loop { + match (*channel).receive(process, Some(deadline.clone())) { + ReceiveResult::None => { + // Safety: the current thread is holding on to the run lock + state.timeout_worker.suspend(process, deadline.clone()); + context::switch(process); + + if process.timeout_expired() { + state.timeout_worker.increase_expired_timeouts(); + return InkoResult::none(); + } + + // It's possible another process received all messages before we + // got a chance to try again. In this case we continue waiting + // for a message. + } + ReceiveResult::Some(msg) => return InkoResult::ok(msg as _), + ReceiveResult::Reschedule(msg, sender) => { + // We schedule onto the global queue because the current process + // wants to do something with the message. + process.thread().schedule_global(sender); + return InkoResult::ok(msg as _); + } + } + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_drop( + state: *const State, + channel: *mut Channel, +) -> *const Nil { + Channel::drop(channel); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_channel_wait( + state: *const State, + process: ProcessPointer, + channels: *mut Array, +) -> *const Nil { + let channels = &mut *channels; + let mut guards = Vec::with_capacity(channels.value.len()); + + for &ptr in &channels.value { + let chan = &(*(ptr as *const Channel)); + let guard = chan.state.lock().unwrap(); + + if guard.has_messages() { + return (*state).nil_singleton; + } + + guards.push(guard); + } + + // We have to hold on to the process state lock until all channels are + // updated. If we don't do this, a process may write to a channel before + // observing that we want to wait for messages, thus never rescheduling our + // process. + let mut proc_state = process.state(); + + for mut guard in guards { + guard.add_waiting_for_message(process); + } + + proc_state.waiting_for_channel(None); + drop(proc_state); + + // Safety: the current thread is holding on to the run lock, so a process + // writing to one of the above channels can't reschedule us until the thread + // releases the lock. + context::switch(process); + + for &ptr in &channels.value { + let chan = &(*(ptr as *const Channel)); + + chan.state.lock().unwrap().remove_waiting_for_message(process); + } + + (*state).nil_singleton +} diff --git a/rt/src/runtime/random.rs b/rt/src/runtime/random.rs new file mode 100644 index 000000000..bc7d5774c --- /dev/null +++ b/rt/src/runtime/random.rs @@ -0,0 +1,86 @@ +use crate::mem::{ByteArray, Float, Int, Nil}; +use crate::process::ProcessPointer; +use crate::runtime::process::panic; +use crate::state::State; +use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; + +#[no_mangle] +pub unsafe extern "system" fn inko_random_int( + state: *const State, + rng: *mut StdRng, +) -> *const Int { + Int::new((*state).int_class, (*rng).gen()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_float( + state: *const State, + rng: *mut StdRng, +) -> *const Float { + Float::alloc((*state).float_class, (*rng).gen()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_int_range( + state: *const State, + rng: *mut StdRng, + min: i64, + max: i64, +) -> *const Int { + let val = if min < max { (*rng).gen_range(min..max) } else { 0 }; + + Int::new((*state).int_class, val) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_float_range( + state: *const State, + rng: *mut StdRng, + min: f64, + max: f64, +) -> *const Float { + let val = if min < max { (*rng).gen_range(min..max) } else { 0.0 }; + + Float::alloc((*state).float_class, val) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_bytes( + state: *const State, + process: ProcessPointer, + rng: *mut StdRng, + size: i64, +) -> *mut ByteArray { + let mut bytes = vec![0; size as usize]; + + if let Err(err) = (*rng).try_fill(&mut bytes[..]) { + panic(process, &err.to_string()); + } + + ByteArray::alloc((*state).byte_array_class, bytes) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_new( + mut process: ProcessPointer, +) -> *mut StdRng { + let mut seed: ::Seed = Default::default(); + + process.thread().rng.fill(&mut seed); + Box::into_raw(Box::new(StdRng::from_seed(seed))) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_from_int(seed: i64) -> *mut StdRng { + Box::into_raw(Box::new(StdRng::seed_from_u64(seed as _))) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_random_drop( + state: *const State, + rng: *mut StdRng, +) -> *const Nil { + drop(Box::from_raw(rng)); + (*state).nil_singleton +} diff --git a/rt/src/runtime/socket.rs b/rt/src/runtime/socket.rs new file mode 100644 index 000000000..fd23b60e9 --- /dev/null +++ b/rt/src/runtime/socket.rs @@ -0,0 +1,478 @@ +use crate::context; +use crate::mem::{Bool, ByteArray, Int, Nil, String as InkoString}; +use crate::network_poller::Interest; +use crate::process::ProcessPointer; +use crate::result::Result; +use crate::scheduler::timeouts::Timeout; +use crate::socket::Socket; +use crate::state::State; +use std::io::{self, Write}; + +fn blocking( + state: &State, + mut process: ProcessPointer, + socket: &mut Socket, + interest: Interest, + deadline: i64, + mut func: impl FnMut(&mut Socket) -> io::Result, +) -> io::Result { + match func(socket) { + Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} + val => return val, + } + + let poll_id = unsafe { process.thread() }.network_poller; + + // We must keep the process' state lock open until everything is registered, + // otherwise a timeout thread may reschedule the process (i.e. the timeout + // is very short) before we finish registering the socket with a poller. + { + let mut proc_state = process.state(); + + // A deadline of -1 signals that we should wait indefinitely. + if deadline >= 0 { + let time = Timeout::from_nanos_deadline(state, deadline as u64); + + proc_state.waiting_for_io(Some(time.clone())); + state.timeout_worker.suspend(process, time); + } else { + proc_state.waiting_for_io(None); + } + + socket.register(state, process, poll_id, interest)?; + } + + // Safety: the current thread is holding on to the process' run lock, so if + // the process gets rescheduled onto a different thread, said thread won't + // be able to use it until we finish this context switch. + unsafe { context::switch(process) }; + + if process.timeout_expired() { + // The socket is still registered at this point, so we have to + // deregister first. If we don't and suspend for another IO operation, + // the poller could end up rescheduling the process multiple times (as + // there are multiple events still in flight for the process). + socket.deregister(state); + return Err(io::Error::from(io::ErrorKind::TimedOut)); + } + + func(socket) +} + +fn new_address_pair(state: &State, addr: String, port: i64) -> Result { + let addr = InkoString::alloc(state.string_class, addr); + let port = Int::new(state.int_class, port); + + Result::ok_boxed((addr, port)) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_socket_new( + proto: i64, + kind: i64, +) -> Result { + let sock = match proto { + 0 => Socket::ipv4(kind), + 1 => Socket::ipv6(kind), + _ => Socket::unix(kind), + }; + + sock.map(Result::ok_boxed).unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_socket_write_string( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + input: *const InkoString, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Write, deadline, |sock| { + sock.write(InkoString::read(input).as_bytes()) + }) + .map(|v| Result::ok(Int::new(state.int_class, v as i64) as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_socket_write_bytes( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + input: *mut ByteArray, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Write, deadline, |sock| { + sock.write(&(*input).value) + }) + .map(|v| Result::ok(Int::new(state.int_class, v as i64) as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_read( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + buffer: *mut ByteArray, + amount: i64, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Read, deadline, |sock| { + sock.read(&mut (*buffer).value, amount as usize) + }) + .map(|size| Result::ok(Int::new(state.int_class, size as i64) as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_listen( + state: *const State, + socket: *mut Socket, + value: i64, +) -> Result { + (*socket) + .listen(value as i32) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_bind( + state: *const State, + socket: *mut Socket, + address: *const InkoString, + port: i64, +) -> Result { + // POSX states that bind(2) _can_ produce EINPROGRESS, but in practise it + // seems no system out there actually does this. + (*socket) + .bind(InkoString::read(address), port as u16) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_connect( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + address: *const InkoString, + port: i64, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Write, deadline, |sock| { + sock.connect(InkoString::read(address), port as u16) + }) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_accept( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Read, deadline, |sock| { + sock.accept() + }) + .map(Result::ok_boxed) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_receive_from( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + buffer: *mut ByteArray, + amount: i64, + deadline: i64, +) -> Result { + let state = &*state; + + blocking(state, process, &mut *socket, Interest::Read, deadline, |sock| { + sock.recv_from(&mut (*buffer).value, amount as _) + }) + .map(|(addr, port)| new_address_pair(state, addr, port)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_send_bytes_to( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + buffer: *mut ByteArray, + address: *const InkoString, + port: i64, + deadline: i64, +) -> Result { + let state = &*state; + let addr = InkoString::read(address); + + blocking(state, process, &mut *socket, Interest::Write, deadline, |sock| { + sock.send_to(&(*buffer).value, addr, port as _) + }) + .map(|size| Result::ok(Int::new(state.int_class, size as i64) as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_send_string_to( + state: *const State, + process: ProcessPointer, + socket: *mut Socket, + buffer: *const InkoString, + address: *const InkoString, + port: i64, + deadline: i64, +) -> Result { + let state = &*state; + let addr = InkoString::read(address); + + blocking(state, process, &mut *socket, Interest::Write, deadline, |sock| { + sock.send_to(InkoString::read(buffer).as_bytes(), addr, port as _) + }) + .map(|size| Result::ok(Int::new(state.int_class, size as i64) as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_shutdown_read( + state: *const State, + socket: *mut Socket, +) -> Result { + (*socket) + .shutdown_read() + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_shutdown_write( + state: *const State, + socket: *mut Socket, +) -> Result { + (*socket) + .shutdown_write() + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_shutdown_read_write( + state: *const State, + socket: *mut Socket, +) -> Result { + (*socket) + .shutdown_read_write() + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_local_address( + state: *const State, + socket: *mut Socket, +) -> Result { + (*socket) + .local_address() + .map(|(addr, port)| new_address_pair(&*state, addr, port)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_peer_address( + state: *const State, + socket: *mut Socket, +) -> Result { + (*socket) + .peer_address() + .map(|(addr, port)| new_address_pair(&*state, addr, port)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_ttl( + state: *const State, + socket: *mut Socket, + value: i64, +) -> Result { + (*socket) + .set_ttl(value as _) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_only_v6( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_only_v6(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_nodelay( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_nodelay(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_broadcast( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_broadcast(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_linger( + state: *const State, + socket: *mut Socket, + value: i64, +) -> Result { + (*socket) + .set_linger(value as _) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_recv_size( + state: *const State, + socket: *mut Socket, + value: i64, +) -> Result { + (*socket) + .set_recv_buffer_size(value as _) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_send_size( + state: *const State, + socket: *mut Socket, + value: i64, +) -> Result { + (*socket) + .set_send_buffer_size(value as _) + .map(|_| Result::ok((*state).nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_keepalive( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_keepalive(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_reuse_address( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_reuse_address(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_set_reuse_port( + state: *const State, + socket: *mut Socket, + value: *const Bool, +) -> Result { + let state = &*state; + + (*socket) + .set_reuse_port(value == state.true_singleton) + .map(|_| Result::ok(state.nil_singleton as _)) + .unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_try_clone( + socket: *mut Socket, +) -> Result { + (*socket).try_clone().map(Result::ok_boxed).unwrap_or_else(Result::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_drop( + state: *const State, + socket: *mut Socket, +) -> *const Nil { + drop(Box::from_raw(socket)); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_address_pair_address( + pair: *const (*const InkoString, *const Int), +) -> *const InkoString { + (*pair).0 +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_address_pair_port( + pair: *const (*const InkoString, *const Int), +) -> *const Int { + (*pair).1 +} + +#[no_mangle] +pub unsafe extern "system" fn inko_socket_address_pair_drop( + state: *const State, + pair: *mut (*const InkoString, *const Int), +) -> *const Nil { + drop(Box::from_raw(pair)); + (*state).nil_singleton +} diff --git a/rt/src/runtime/stdio.rs b/rt/src/runtime/stdio.rs new file mode 100644 index 000000000..8585bcd5c --- /dev/null +++ b/rt/src/runtime/stdio.rs @@ -0,0 +1,106 @@ +use crate::mem::{ByteArray, Int, Nil, String as InkoString}; +use crate::process::ProcessPointer; +use crate::result::Result as InkoResult; +use crate::runtime::helpers::read_into; +use crate::state::State; +use std::io::Write; +use std::io::{stderr, stdin, stdout}; + +#[no_mangle] +pub unsafe extern "system" fn inko_stdout_write_string( + state: *const State, + process: ProcessPointer, + input: *const InkoString, +) -> InkoResult { + let input = InkoString::read(input).as_bytes(); + + process + .blocking(|| stdout().write(input)) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stdout_write_bytes( + state: *const State, + process: ProcessPointer, + input: *mut ByteArray, +) -> InkoResult { + let input = &(*input).value; + + process + .blocking(|| stdout().write(input)) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stderr_write_string( + state: *const State, + process: ProcessPointer, + input: *const InkoString, +) -> InkoResult { + let input = InkoString::read(input).as_bytes(); + + process + .blocking(|| stderr().write(input)) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stderr_write_bytes( + state: *const State, + process: ProcessPointer, + input: *mut ByteArray, +) -> InkoResult { + let input = &(*input).value; + + process + .blocking(|| stderr().write(input)) + .map(|size| { + InkoResult::ok(Int::new((*state).int_class, size as i64) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stdout_flush( + state: *const State, + process: ProcessPointer, +) -> *const Nil { + let _ = process.blocking(|| stdout().flush()); + + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stderr_flush( + state: *const State, + process: ProcessPointer, +) -> *const Nil { + let _ = process.blocking(|| stderr().flush()); + + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_stdin_read( + state: *const State, + process: ProcessPointer, + buffer: *mut ByteArray, + size: i64, +) -> InkoResult { + let buffer = &mut (*buffer).value; + + process + .blocking(|| read_into(&mut stdin(), buffer, size)) + .map(|size| InkoResult::ok(Int::new((*state).int_class, size) as _)) + .unwrap_or_else(InkoResult::io_error) +} diff --git a/rt/src/runtime/string.rs b/rt/src/runtime/string.rs new file mode 100644 index 000000000..ec6cbc08b --- /dev/null +++ b/rt/src/runtime/string.rs @@ -0,0 +1,257 @@ +use crate::mem::{ + tagged_int, Array, Bool, ByteArray, Float, Int, Nil, String as InkoString, +}; +use crate::process::ProcessPointer; +use crate::result::Result as InkoResult; +use crate::runtime::process::panic; +use crate::state::State; +use std::cmp::min; +use std::slice; +use unicode_segmentation::{Graphemes, UnicodeSegmentation}; + +#[no_mangle] +pub unsafe extern "system" fn inko_string_new_permanent( + state: *const State, + bytes: *const u8, + length: usize, +) -> *const InkoString { + let bytes = slice::from_raw_parts(bytes, length).to_vec(); + let string = String::from_utf8_unchecked(bytes); + + InkoString::alloc_permanent((*state).string_class, string) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_equals( + state: *const State, + left: *const InkoString, + right: *const InkoString, +) -> *const Bool { + let state = &*state; + + if InkoString::read(left) == InkoString::read(right) { + state.true_singleton + } else { + state.false_singleton + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_size( + state: *const State, + string: *const InkoString, +) -> *const Int { + let state = &*state; + + Int::new(state.int_class, InkoString::read(string).len() as i64) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_concat( + state: *const State, + strings: *const *const InkoString, + length: i64, +) -> *const InkoString { + let slice = slice::from_raw_parts(strings, length as usize); + let mut buffer = String::new(); + + for &val in slice { + buffer.push_str(InkoString::read(val)); + } + + InkoString::alloc((*state).string_class, buffer) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_byte( + string: *const InkoString, + index: i64, +) -> *const Int { + let byte = i64::from( + *InkoString::read(string).as_bytes().get_unchecked(index as usize), + ); + + tagged_int(byte) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_drop( + state: *const State, + pointer: *const InkoString, +) -> *const Nil { + InkoString::drop(pointer); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_lower( + state: *const State, + string: *const InkoString, +) -> *const InkoString { + InkoString::alloc( + (*state).string_class, + InkoString::read(string).to_lowercase(), + ) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_upper( + state: *const State, + string: *const InkoString, +) -> *const InkoString { + InkoString::alloc( + (*state).string_class, + InkoString::read(string).to_uppercase(), + ) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_byte_array( + state: *const State, + string: *const InkoString, +) -> *mut ByteArray { + let bytes = InkoString::read(string).as_bytes().to_vec(); + + ByteArray::alloc((*state).byte_array_class, bytes) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_float( + state: *const State, + string: *const InkoString, + start: i64, + end: i64, +) -> InkoResult { + let string = InkoString::read(string); + let slice = if start >= 0 && end >= 0 { + &string[start as usize..end as usize] + } else { + string + }; + + let parsed = match slice { + "Infinity" => Ok(f64::INFINITY), + "-Infinity" => Ok(f64::NEG_INFINITY), + _ => slice.parse::(), + }; + + parsed + .map(|v| InkoResult::ok(Float::alloc((*state).float_class, v) as _)) + .unwrap_or_else(|_| InkoResult::none()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_to_int( + state: *const State, + process: ProcessPointer, + string: *const InkoString, + radix: i64, + start: i64, + end: i64, +) -> InkoResult { + let string = InkoString::read(string); + + if !(2..=36).contains(&radix) { + panic(process, &format!("The radix '{}' is invalid", radix)); + } + + let slice = if start >= 0 && end >= 0 { + &string[start as usize..end as usize] + } else { + string + }; + + // Rust doesn't handle parsing strings like "-0x4a3f043013b2c4d1" out of the + // box. + let parsed = if radix == 16 { + if let Some(tail) = string.strip_prefix("-0x") { + i64::from_str_radix(tail, 16).map(|v| 0_i64.wrapping_sub(v)) + } else { + i64::from_str_radix(slice, 16) + } + } else { + i64::from_str_radix(slice, radix as u32) + }; + + parsed + .map(|v| InkoResult::ok(Int::new((*state).int_class, v) as _)) + .unwrap_or_else(|_| InkoResult::none()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_characters( + string: *const InkoString, +) -> *mut u8 { + let string = InkoString::read(string); + + // Safety: a Graphemes takes a reference to a slice of bytes. The standard + // library implements a wrapper around this native type that holds on to the + // string we're iterating over, preventing the slice from being invalidated + // while this iterator still exists. + // + // Graphemes isn't FFI safe, so we have to work around this by casting it to + // a regular raw pointer. + Box::into_raw(Box::new(string.graphemes(true))) as _ +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_characters_next( + state: *const State, + iter: *mut u8, +) -> InkoResult { + let iter = &mut *(iter as *mut Graphemes); + + iter.next() + .map(|v| { + let string = + InkoString::alloc((*state).string_class, v.to_string()); + + InkoResult::ok(string as _) + }) + .unwrap_or_else(InkoResult::none) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_characters_drop( + state: *const State, + iter: *mut u8, +) -> *const Nil { + drop(Box::from_raw(iter as *mut Graphemes)); + (*state).nil_singleton +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_concat_array( + state: *const State, + array: *const Array, +) -> *const InkoString { + let array = &*array; + let mut buffer = String::new(); + + for &ptr in &array.value { + let ptr = ptr as *const InkoString; + + buffer.push_str(InkoString::read(ptr)); + } + + InkoString::alloc((*state).string_class, buffer) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_string_slice_bytes( + state: *const State, + string: *const InkoString, + start: i64, + length: i64, +) -> *const InkoString { + let string = InkoString::read(string); + let end = min((start + length) as usize, string.len()); + let new_string = if start < 0 || length <= 0 || start as usize >= end { + String::new() + } else { + String::from_utf8_lossy(&string.as_bytes()[start as usize..end]) + .into_owned() + }; + + InkoString::alloc((*state).string_class, new_string) +} diff --git a/rt/src/runtime/sys.rs b/rt/src/runtime/sys.rs new file mode 100644 index 000000000..9a032be86 --- /dev/null +++ b/rt/src/runtime/sys.rs @@ -0,0 +1,229 @@ +use crate::mem::{ + tagged_int, Array, ByteArray, Int, Nil, String as InkoString, +}; +use crate::process::ProcessPointer; +use crate::result::Result as InkoResult; +use crate::runtime::helpers::read_into; +use crate::scheduler::number_of_cores; +use crate::state::State; +use std::io::Write; +use std::process::{Child, Command, Stdio}; + +fn stdio_for(value: i64) -> Stdio { + match value { + 1 => Stdio::inherit(), + 2 => Stdio::piped(), + _ => Stdio::null(), + } +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_spawn( + process: ProcessPointer, + program: *const InkoString, + args: *const Array, + env: *const Array, + stdin: i64, + stdout: i64, + stderr: i64, + directory: *const InkoString, +) -> InkoResult { + let program = InkoString::read(program); + let args = &(*args).value; + let env = &(*env).value; + let directory = InkoString::read(directory); + let mut cmd = Command::new(program); + + for &ptr in args { + cmd.arg(InkoString::read(ptr as _)); + } + + for pair in env.chunks(2) { + let key = InkoString::read(pair[0] as _); + let val = InkoString::read(pair[1] as _); + + cmd.env(key, val); + } + + cmd.stdin(stdio_for(stdin)); + cmd.stdout(stdio_for(stdout)); + cmd.stderr(stdio_for(stderr)); + + if !directory.is_empty() { + cmd.current_dir(directory); + } + + process + .blocking(|| cmd.spawn()) + .map(InkoResult::ok_boxed) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_wait( + process: ProcessPointer, + child: *mut Child, +) -> InkoResult { + process + .blocking(|| (*child).wait()) + .map(|status| tagged_int(status.code().unwrap_or(0) as i64)) + .map(|status| InkoResult::ok(status as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_try_wait( + child: *mut Child, +) -> InkoResult { + let child = &mut *child; + + child + .try_wait() + .map(|status| { + InkoResult::ok(tagged_int( + status.map(|s| s.code().unwrap_or(0)).unwrap_or(-1) as i64, + ) as _) + }) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdout_read( + state: *const State, + process: ProcessPointer, + child: *mut Child, + buffer: *mut ByteArray, + size: i64, +) -> InkoResult { + let state = &*state; + let child = &mut *child; + let buff = &mut (*buffer).value; + + child + .stdout + .as_mut() + .map(|stream| process.blocking(|| read_into(stream, buff, size))) + .unwrap_or(Ok(0)) + .map(|size| InkoResult::ok(Int::new(state.int_class, size) as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stderr_read( + state: *const State, + process: ProcessPointer, + child: *mut Child, + buffer: *mut ByteArray, + size: i64, +) -> InkoResult { + let state = &*state; + let child = &mut *child; + let buff = &mut (*buffer).value; + + child + .stderr + .as_mut() + .map(|stream| process.blocking(|| read_into(stream, buff, size))) + .unwrap_or(Ok(0)) + .map(|size| InkoResult::ok(Int::new(state.int_class, size) as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdin_write_bytes( + state: *const State, + process: ProcessPointer, + child: *mut Child, + input: *mut ByteArray, +) -> InkoResult { + let state = &*state; + let child = &mut *child; + let input = &(*input).value; + + child + .stdin + .as_mut() + .map(|stream| process.blocking(|| stream.write(input))) + .unwrap_or(Ok(0)) + .map(|size| InkoResult::ok(Int::new(state.int_class, size as i64) as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdin_write_string( + state: *const State, + process: ProcessPointer, + child: *mut Child, + input: *mut InkoString, +) -> InkoResult { + let state = &*state; + let child = &mut *child; + let input = InkoString::read(input); + + child + .stdin + .as_mut() + .map(|stream| process.blocking(|| stream.write(input.as_bytes()))) + .unwrap_or(Ok(0)) + .map(|size| InkoResult::ok(Int::new(state.int_class, size as i64) as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdin_flush( + state: *const State, + process: ProcessPointer, + child: *mut Child, +) -> InkoResult { + let state = &*state; + let child = &mut *child; + + child + .stdin + .as_mut() + .map(|stream| process.blocking(|| stream.flush())) + .unwrap_or(Ok(())) + .map(|_| InkoResult::ok(state.nil_singleton as _)) + .unwrap_or_else(InkoResult::io_error) +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdout_close( + state: *const State, + child: *mut Child, +) -> *const Nil { + (*child).stdout.take(); + (*state).nil_singleton +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stderr_close( + state: *const State, + child: *mut Child, +) -> *const Nil { + (*child).stderr.take(); + (*state).nil_singleton +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_stdin_close( + state: *const State, + child: *mut Child, +) -> *const Nil { + (*child).stdin.take(); + (*state).nil_singleton +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_child_process_drop( + state: *const State, + child: *mut Child, +) -> *const Nil { + drop(Box::from_raw(child)); + (*state).nil_singleton +} + +#[no_mangle] +pub(crate) unsafe extern "system" fn inko_cpu_cores() -> *const Int { + tagged_int(number_of_cores() as i64) +} diff --git a/rt/src/runtime/time.rs b/rt/src/runtime/time.rs new file mode 100644 index 000000000..4f548d024 --- /dev/null +++ b/rt/src/runtime/time.rs @@ -0,0 +1,96 @@ +use crate::mem::{Float, Int}; +use crate::state::State; +use std::mem::MaybeUninit; + +fn utc() -> f64 { + unsafe { + let mut ts = MaybeUninit::uninit(); + + if libc::clock_gettime(libc::CLOCK_REALTIME, ts.as_mut_ptr()) != 0 { + panic!("clock_gettime() failed"); + } + + let ts = ts.assume_init(); + + ts.tv_sec as f64 + (ts.tv_nsec as f64 / 1_000_000_000.0) + } +} + +fn offset() -> i64 { + unsafe { + extern "C" { + fn tzset(); + } + + let ts = { + let mut ts = MaybeUninit::uninit(); + + if libc::clock_gettime(libc::CLOCK_REALTIME, ts.as_mut_ptr()) != 0 { + panic!("clock_gettime() failed"); + } + + ts.assume_init() + }; + + let mut tm = MaybeUninit::uninit(); + + // localtime_r() doesn't necessarily call tzset() for us. + tzset(); + + // While localtime_r() may call setenv() internally, this is not a + // problem as Inko caches environment variables upon startup. If an FFI + // call ends up racing with the setenv() call, that's a problem for the + // FFI code. + if libc::localtime_r(&ts.tv_sec, tm.as_mut_ptr()).is_null() { + panic!("localtime_r() failed"); + } + + tm.assume_init().tm_gmtoff + } +} + +#[no_mangle] +pub unsafe extern "system" fn inko_time_monotonic( + state: *const State, +) -> *const Int { + // An i64 gives us roughly 292 years of time. That should be more than + // enough for a monotonic clock, as an Inko program is unlikely to run for + // that long. + let state = &*state; + let nanos = state.start_time.elapsed().as_nanos() as i64; + + Int::new(state.int_class, nanos) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_time_system( + state: *const State, +) -> *const Float { + Float::alloc((*state).float_class, utc()) +} + +#[no_mangle] +pub unsafe extern "system" fn inko_time_system_offset( + state: *const State, +) -> *const Int { + Int::new((*state).int_class, offset()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::{SystemTime, UNIX_EPOCH}; + + #[test] + fn test_utc() { + let expected = + SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64(); + let given = utc(); + + // We can't assert equality, for there may be time between the two + // function calls. We also can't assert the utc() time is greater in the + // event of clock changes. Instead we just assert the two times are + // within 5 seconds of each other, which should be sufficient. + assert!((given - expected).abs() < 5.0); + } +} diff --git a/rt/src/scheduler/mod.rs b/rt/src/scheduler/mod.rs new file mode 100644 index 000000000..a625f22cd --- /dev/null +++ b/rt/src/scheduler/mod.rs @@ -0,0 +1,30 @@ +pub mod process; +pub mod timeout_worker; +pub mod timeouts; + +use std::thread::available_parallelism; + +#[cfg(target_os = "linux")] +use { + libc::{cpu_set_t, sched_setaffinity, CPU_SET}, + std::mem::{size_of, zeroed}, +}; + +#[cfg(target_os = "linux")] +pub(crate) fn pin_thread_to_core(core: usize) { + unsafe { + let mut set: cpu_set_t = zeroed(); + + CPU_SET(core, &mut set); + sched_setaffinity(0, size_of::(), &set); + } +} + +#[cfg(not(target_os = "linux"))] +pub(crate) fn pin_thread_to_core(_core: usize) { + // Pinning is only implemented for Linux at this time. +} + +pub(crate) fn number_of_cores() -> usize { + available_parallelism().map(|v| v.into()).unwrap_or(1) +} diff --git a/vm/src/scheduler/process.rs b/rt/src/scheduler/process.rs similarity index 76% rename from vm/src/scheduler/process.rs rename to rt/src/scheduler/process.rs index 8843e4922..7b9fcfde6 100644 --- a/vm/src/scheduler/process.rs +++ b/rt/src/scheduler/process.rs @@ -1,8 +1,9 @@ //! Scheduling and execution of lightweight Inko processes. use crate::arc_without_weak::ArcWithoutWeak; -use crate::machine::Machine; -use crate::mem::{ClassPointer, MethodPointer}; -use crate::process::{Process, ProcessPointer}; +use crate::context; +use crate::process::{Process, ProcessPointer, Task}; +use crate::scheduler::{number_of_cores, pin_thread_to_core}; +use crate::stack::StackPool; use crate::state::State; use crossbeam_queue::ArrayQueue; use crossbeam_utils::atomic::AtomicCell; @@ -11,12 +12,51 @@ use rand::rngs::ThreadRng; use rand::thread_rng; use std::cmp::min; use std::collections::VecDeque; -use std::mem::size_of; +use std::mem::{size_of, swap}; use std::ops::Drop; use std::sync::atomic::{AtomicBool, AtomicU16, AtomicU64, Ordering}; use std::sync::{Condvar, Mutex}; use std::time::{Duration, Instant}; +/// The number of reductions a process can perform before it needs to suspend +/// itself. +/// +/// The number here is derived as follows: +/// +/// - We assume a static or virtual method call takes <= 5 nsec +/// - We want to restrict processes to time slices of roughly 10 µsec +const REDUCTIONS: u16 = 2000; + +/// A type describing what a thread should do in response to a process yielding +/// back control. +/// +/// This type exists as processes may need to perform certain operations that +/// aren't safe to perform while the process is still running. For example, a +/// process may want to send a message to a receiver and wait for the result. If +/// the receiver is still running, it may end up trying to reschedule the +/// sender. If this happens while the sender is still wrapping up, all sorts of +/// things can go wrong. +/// +/// To prevent such problems, processes yield control back to the thread and let +/// the thread perform such operations using its own stack. +#[derive(Debug)] +pub(crate) enum Action { + /// The thread shouldn't do anything with the process it was running. + Ignore, + + /// The thread should terminate the process. + Terminate, +} + +impl Action { + fn take(&mut self) -> Self { + let mut old_val = Action::Ignore; + + swap(self, &mut old_val); + old_val + } +} + /// The starting capacity of the global queue. /// /// The global queue grows if needed, this capacity simply exists to reduce the @@ -88,7 +128,7 @@ struct Shared { /// The private half of a thread, used only by the OS thread this state belongs /// to. -pub(crate) struct Thread<'a> { +pub struct Thread { /// The unique ID of this thread. /// /// This is used to prevent a thread from trying to steal work from itself, @@ -100,18 +140,7 @@ pub(crate) struct Thread<'a> { work: ArcWithoutWeak>, /// The pool this thread belongs to. - pool: &'a Pool, - - /// A process to run before trying to consume any other work. - /// - /// Sometimes we reschedule a process and want to run it immediately, - /// instead of processing jobs in the local queue. For example, when sending - /// a message to a process not doing anything, running it immediately - /// reduces the latency of producing a response to the message. - /// - /// Other threads can't steal from this slot, because that would defeat its - /// purpose. - priority: Option, + pool: ArcWithoutWeak, /// A flag indicating this thread is or will become a backup thread. pub(crate) backup: bool, @@ -133,34 +162,54 @@ pub(crate) struct Thread<'a> { /// A random number generator to use for the current thread. pub(crate) rng: ThreadRng, + + /// The pool of stacks to use. + pub(crate) stacks: StackPool, + + /// A value indicating what to do with a process when it yields back to us. + /// + /// The default is to not do anything with a process after it yields back to + /// the thread. + pub(crate) action: Action, + + /// The amount of reductions left before a process needs to be suspended. + pub(crate) reductions: u16, } -impl<'a> Thread<'a> { - fn new(id: usize, network_poller: usize, pool: &'a Pool) -> Thread { +impl Thread { + fn new( + id: usize, + network_poller: usize, + pool: ArcWithoutWeak, + ) -> Thread { Self { id, work: pool.threads[id].queue.clone(), - priority: None, - pool, backup: false, blocked_at: NOT_BLOCKING, network_poller, rng: thread_rng(), + stacks: StackPool::new(pool.stack_size), + action: Action::Ignore, + reductions: REDUCTIONS, + pool, } } - fn backup(network_poller: usize, pool: &'a Pool) -> Thread { + fn backup(network_poller: usize, pool: ArcWithoutWeak) -> Thread { Self { // For backup threads the ID/queue doesn't matter, because we won't // use them until we're turned into a regular thread. id: 0, work: pool.threads[0].queue.clone(), - priority: None, - pool, backup: true, blocked_at: NOT_BLOCKING, network_poller, rng: thread_rng(), + stacks: StackPool::new(pool.stack_size), + action: Action::Ignore, + reductions: REDUCTIONS, + pool, } } @@ -180,32 +229,21 @@ impl<'a> Thread<'a> { } } - /// Schedules a process in the priority slot. - /// - /// This method shouldn't be used when the thread is to transition to a - /// backup thread, as the work might never get picked up again. - pub(crate) fn schedule_priority(&mut self, process: ProcessPointer) { - // Outside of any bugs in the VM, we should never reach this point and - // still have a value in the priority slot, so we can just set the value - // as-is. - self.priority = Some(process); - } - /// Schedules a process onto the global queue. pub(crate) fn schedule_global(&self, process: ProcessPointer) { self.pool.schedule(process); } pub(crate) fn start_blocking(&mut self) { - let epoch = self.pool.current_epoch(); - let shared = &self.pool.threads[self.id]; - // We have to push our work away before entering the blocking operation. // If we do this after returning from the blocking operation, another // thread may already be using our queue, at which point we'd slow it // down by moving its work to the global queue. self.move_work_to_global_queue(); + let epoch = self.pool.current_epoch(); + let shared = &self.pool.threads[self.id]; + self.blocked_at = epoch; shared.blocked_at.store(epoch, Ordering::Release); @@ -249,7 +287,11 @@ impl<'a> Thread<'a> { self.blocked_at = NOT_BLOCKING; } - pub(crate) fn blocking(&mut self, function: F) -> R + pub(crate) fn blocking( + &mut self, + process: ProcessPointer, + function: F, + ) -> R where F: FnOnce() -> R, { @@ -258,11 +300,29 @@ impl<'a> Thread<'a> { let res = function(); self.finish_blocking(); + + // If the closure took too long to run (e.g. an IO operation took too + // long), we have to give up running the process. If we continue running + // we could mess up whatever thread has taken over our queue/work, and + // we'd be using the OS thread even longer than we already have. + // + // We schedule onto the global queue because if another thread took over + // but found no other work, it may have gone to sleep. In that case + // scheduling onto the local queue may result in the work never getting + // picked up (e.g. if all other threads are also sleeping). + if self.backup { + // Safety: the current thread is holding on to the run lock, so + // another thread can't run the process until we finish the context + // switch. + self.schedule_global(process); + unsafe { context::switch(process) }; + } + res } fn move_work_to_global_queue(&mut self) { - let len = self.work.len() + if self.priority.is_some() { 1 } else { 0 }; + let len = self.work.len(); if len == 0 { return; @@ -270,10 +330,6 @@ impl<'a> Thread<'a> { let mut work = Vec::with_capacity(len); - if let Some(process) = self.priority.take() { - work.push(process); - } - while let Some(process) = self.work.pop() { work.push(process); } @@ -313,6 +369,13 @@ impl<'a> Thread<'a> { continue; } + // Now that we ran out of local work, we can try to shrink the stack + // if really necessary. We do this _before_ stealing global work to + // prevent the stack pool from ballooning in size. If we did this + // before going to sleep then in an active system we may never end + // up shrinking the stack pool. + self.stacks.shrink(); + if let Some(process) = self.steal_from_global() { self.run_process(state, process); continue; @@ -323,7 +386,7 @@ impl<'a> Thread<'a> { } fn next_local_process(&mut self) -> Option { - self.priority.take().or_else(|| self.work.pop()) + self.work.pop() } fn steal_from_thread(&mut self) -> Option { @@ -413,20 +476,71 @@ impl<'a> Thread<'a> { self.pool.sleeping.fetch_sub(1, Ordering::AcqRel); } - fn run_process(&mut self, state: &State, process: ProcessPointer) { - Machine::new(state).run(self, process); + /// Runs a process by calling back into the native code. + fn run_process(&mut self, state: &State, mut process: ProcessPointer) { + { + // We must acquire the run lock first to prevent running a process + // that's still wrapping up/suspending in another thread. + // + // An example of such a scenario is when process A sends a message + // to process B, wants to wait for it, but B produces the result and + // tries to reschedule A _before_ A gets a chance to finish yielding + // back to the scheduler. + // + // This is done in a sub scope such that the lock is unlocked + // automatically when we decide what action to take in response to + // the yield. + let _lock = process.acquire_run_lock(); + + match process.next_task() { + Task::Resume => { + process.set_thread(self); + unsafe { context::switch(process) } + } + Task::Start(func, args) => { + process.set_thread(self); + unsafe { context::start(state, process, func, args) } + } + Task::Wait => return, + } + + process.unset_thread(); + } + + self.reductions = REDUCTIONS; + + match self.action.take() { + Action::Terminate => { + // Process termination can't be safely done on the process' + // stack, because its memory would be dropped while we're still + // using it, hence we do that here. + if process.is_main() { + state.terminate(); + } + + if let Some(stack) = process.take_stack() { + self.stacks.add(stack); + } + + // Processes drop/free themselves as this must be deferred until + // all messages (including any destructors) have finished + // running. If we did this in a destructor we'd end up releasing + // memory of a process while still using it. + Process::drop_and_deallocate(process); + } + Action::Ignore => { + // In this case it's up to the process (or another process) to + // reschedule the process we just finished running. + } + } } } -impl<'a> Drop for Thread<'a> { +impl Drop for Thread { fn drop(&mut self) { while let Some(process) = self.work.pop() { Process::drop_and_deallocate(process); } - - if let Some(process) = self.priority.take() { - Process::drop_and_deallocate(process); - } } } @@ -666,6 +780,9 @@ struct Pool { /// The state of the process monitor thread. monitor: MonitorState, + + /// The size of each stack to allocate for a process. + stack_size: usize, } impl Pool { @@ -729,7 +846,11 @@ pub(crate) struct Scheduler { } impl Scheduler { - pub(crate) fn new(size: usize, backup: usize) -> Scheduler { + pub(crate) fn new( + size: usize, + backup: usize, + stack_size: usize, + ) -> Scheduler { let mut shared = Vec::with_capacity(size); for _ in 0..size { @@ -753,6 +874,7 @@ impl Scheduler { lock: Mutex::new(()), cvar: Condvar::new(), }, + stack_size, }); Self { primary: size, backup, pool: shared } @@ -767,7 +889,7 @@ impl Scheduler { } pub(crate) fn terminate(&self) { - let _gloabl = self.pool.global.lock().unwrap(); + let _global = self.pool.global.lock().unwrap(); let _blocked = self.pool.blocked_threads.lock().unwrap(); let _monitor = self.pool.monitor.lock.lock().unwrap(); @@ -779,18 +901,19 @@ impl Scheduler { self.pool.monitor.cvar.notify_one(); } - pub(crate) fn run( - &self, - state: &State, - class: ClassPointer, - method: MethodPointer, - ) { - let process = Process::main(class, method); + pub(crate) fn run(&self, state: &State, process: ProcessPointer) { let pollers = state.network_pollers.len(); + let cores = number_of_cores(); let _ = scope(move |s| { s.builder() .name("proc monitor".to_string()) - .spawn(move |_| Monitor::new(&*self.pool).run()) + .spawn(move |_| { + // Cores 0 and 1 are used for the timeout and network poller + // threads. Since we may be running quite often we'll pin + // this thread to a different core. + pin_thread_to_core(2 % cores); + Monitor::new(&self.pool).run() + }) .unwrap(); for id in 0..self.primary { @@ -799,7 +922,8 @@ impl Scheduler { s.builder() .name(format!("proc {}", id)) .spawn(move |_| { - Thread::new(id, poll_id, &*self.pool).run(state) + pin_thread_to_core(id % cores); + Thread::new(id, poll_id, self.pool.clone()).run(state) }) .unwrap(); } @@ -810,7 +934,8 @@ impl Scheduler { s.builder() .name(format!("backup {}", id)) .spawn(move |_| { - Thread::backup(poll_id, &*self.pool).run(state) + pin_thread_to_core(id % cores); + Thread::backup(poll_id, self.pool.clone()).run(state) }) .unwrap(); } @@ -823,19 +948,25 @@ impl Scheduler { #[cfg(test)] mod tests { use super::*; - use crate::mem::Method; + use crate::context::Context; use crate::test::{ - empty_async_method, empty_process_class, new_main_process, new_process, - setup, + empty_process_class, new_main_process, new_process, setup, }; use std::thread::sleep; + unsafe extern "system" fn method(ctx: *mut u8) { + let ctx = &mut *(ctx as *mut Context); + + ctx.process.thread().action = Action::Terminate; + context::switch(ctx.process); + } + #[test] fn test_thread_schedule() { let class = empty_process_class("A"); let process = new_process(*class).take_and_forget(); - let scheduler = Scheduler::new(1, 1); - let mut thread = Thread::new(0, 0, &scheduler.pool); + let scheduler = Scheduler::new(1, 1, 32); + let mut thread = Thread::new(0, 0, scheduler.pool.clone()); thread.schedule(process); @@ -847,8 +978,8 @@ mod tests { fn test_thread_schedule_with_overflow() { let class = empty_process_class("A"); let process = new_process(*class).take_and_forget(); - let scheduler = Scheduler::new(1, 1); - let mut thread = Thread::new(0, 0, &scheduler.pool); + let scheduler = Scheduler::new(1, 1, 32); + let mut thread = Thread::new(0, 0, scheduler.pool.clone()); scheduler.pool.sleeping.fetch_add(1, Ordering::AcqRel); @@ -867,93 +998,54 @@ mod tests { } } - #[test] - fn test_thread_schedule_priority() { - let class = empty_process_class("A"); - let process = new_process(*class).take_and_forget(); - let scheduler = Scheduler::new(1, 1); - let mut thread = Thread::new(0, 0, &scheduler.pool); - - thread.schedule_priority(process); - - assert_eq!(thread.priority, Some(process)); - } - #[test] fn test_thread_run_with_local_job() { let class = empty_process_class("A"); - let main_method = empty_async_method(); - let process = new_main_process(*class, main_method).take_and_forget(); + let process = new_main_process(*class, method).take_and_forget(); let state = setup(); - let mut thread = Thread::new(0, 0, &state.scheduler.pool); + let mut thread = Thread::new(0, 0, state.scheduler.pool.clone()); thread.schedule(process); thread.run(&state); assert_eq!(thread.work.len(), 0); - - Method::drop_and_deallocate(main_method); - } - - #[test] - fn test_thread_run_with_priority_job() { - let class = empty_process_class("A"); - let main_method = empty_async_method(); - let process = new_main_process(*class, main_method).take_and_forget(); - let state = setup(); - let mut thread = Thread::new(0, 0, &state.scheduler.pool); - - thread.schedule_priority(process); - thread.run(&state); - - assert_eq!(thread.work.len(), 0); - assert!(thread.priority.is_none()); - - Method::drop_and_deallocate(main_method); } #[test] fn test_thread_run_with_stolen_job() { let class = empty_process_class("A"); - let main_method = empty_async_method(); - let process = new_main_process(*class, main_method).take_and_forget(); + let process = new_main_process(*class, method).take_and_forget(); let state = setup(); - let mut thread0 = Thread::new(0, 0, &state.scheduler.pool); - let mut thread1 = Thread::new(1, 0, &state.scheduler.pool); + let mut thread0 = Thread::new(0, 0, state.scheduler.pool.clone()); + let mut thread1 = Thread::new(1, 0, state.scheduler.pool.clone()); thread1.schedule(process); thread0.run(&state); assert_eq!(thread0.work.len(), 0); assert_eq!(thread1.work.len(), 0); - - Method::drop_and_deallocate(main_method); } #[test] fn test_thread_run_with_global_job() { let class = empty_process_class("A"); - let main_method = empty_async_method(); - let process = new_main_process(*class, main_method).take_and_forget(); + let process = new_main_process(*class, method).take_and_forget(); let state = setup(); - let mut thread = Thread::new(0, 0, &state.scheduler.pool); + let mut thread = Thread::new(0, 0, state.scheduler.pool.clone()); state.scheduler.pool.schedule(process); thread.run(&state); assert_eq!(thread.work.len(), 0); assert!(state.scheduler.pool.global.lock().unwrap().is_empty()); - - Method::drop_and_deallocate(main_method); } #[test] fn test_thread_run_as_backup() { let class = empty_process_class("A"); - let main_method = empty_async_method(); - let process = new_main_process(*class, main_method).take_and_forget(); + let process = new_main_process(*class, method).take_and_forget(); let state = setup(); - let mut thread = Thread::new(0, 0, &state.scheduler.pool); + let mut thread = Thread::new(0, 0, state.scheduler.pool.clone()); thread.backup = true; @@ -968,38 +1060,33 @@ mod tests { thread.work.as_ptr(), state.scheduler.pool.threads[1].queue.as_ptr() ); - - Method::drop_and_deallocate(main_method); } #[test] fn test_thread_start_blocking() { let class = empty_process_class("A"); - let proc1 = new_process(*class).take_and_forget(); - let proc2 = new_process(*class).take_and_forget(); + let proc = new_process(*class).take_and_forget(); let state = setup(); let pool = &state.scheduler.pool; - let mut thread = Thread::new(0, 0, pool); + let mut thread = Thread::new(0, 0, pool.clone()); pool.epoch.store(4, Ordering::Release); pool.monitor.status.store(MonitorStatus::Sleeping); - thread.schedule_priority(proc1); - thread.schedule(proc2); + thread.schedule(proc); thread.start_blocking(); assert_eq!(thread.blocked_at, 4); assert_eq!(pool.threads[0].blocked_at.load(Ordering::Acquire), 4); assert_eq!(pool.monitor.status.load(), MonitorStatus::Notified); assert!(thread.work.is_empty()); - assert!(thread.priority.is_none()); } #[test] fn test_thread_finish_blocking() { let state = setup(); let pool = &state.scheduler.pool; - let mut thread = Thread::new(0, 0, pool); + let mut thread = Thread::new(0, 0, pool.clone()); thread.start_blocking(); thread.finish_blocking(); @@ -1014,42 +1101,26 @@ mod tests { assert_eq!(thread.blocked_at, NOT_BLOCKING); } - #[test] - fn test_thread_blocking() { - let state = setup(); - let pool = &state.scheduler.pool; - let mut thread = Thread::new(0, 0, pool); - - thread.blocking(|| { - pool.threads[0].blocked_at.store(NOT_BLOCKING, Ordering::Release) - }); - - assert!(thread.backup); - } - #[test] fn test_thread_move_work_to_global_queue() { let class = empty_process_class("A"); - let proc1 = new_process(*class).take_and_forget(); - let proc2 = new_process(*class).take_and_forget(); + let proc = new_process(*class).take_and_forget(); let state = setup(); let pool = &state.scheduler.pool; - let mut thread = Thread::new(0, 0, pool); + let mut thread = Thread::new(0, 0, pool.clone()); - thread.schedule(proc1); - thread.schedule_priority(proc2); + thread.schedule(proc); thread.move_work_to_global_queue(); assert!(thread.work.is_empty()); - assert!(thread.priority.is_none()); - assert_eq!(pool.global.lock().unwrap().len(), 2); + assert_eq!(pool.global.lock().unwrap().len(), 1); } #[test] fn test_pool_schedule_with_sleeping_thread() { let class = empty_process_class("A"); let process = new_process(*class).take_and_forget(); - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); scheduler.pool.sleeping.fetch_add(1, Ordering::Release); scheduler.pool.schedule(process); @@ -1059,8 +1130,8 @@ mod tests { #[test] fn test_scheduler_terminate() { - let scheduler = Scheduler::new(1, 1); - let thread = Thread::new(0, 0, &scheduler.pool); + let scheduler = Scheduler::new(1, 1, 32); + let thread = Thread::new(0, 0, scheduler.pool.clone()); scheduler.pool.sleeping.fetch_add(1, Ordering::Release); scheduler.terminate(); @@ -1076,7 +1147,7 @@ mod tests { #[test] fn test_monitor_check_threads() { - let scheduler = Scheduler::new(2, 2); + let scheduler = Scheduler::new(2, 2, 32); let mut monitor = Monitor::new(&*scheduler.pool); assert!(!monitor.check_threads()); @@ -1104,7 +1175,7 @@ mod tests { #[test] fn test_monitor_update_epoch() { - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); let mut monitor = Monitor::new(&*scheduler.pool); assert_eq!(monitor.epoch, START_EPOCH); @@ -1118,7 +1189,7 @@ mod tests { #[test] fn test_monitor_sleep() { - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); let monitor = Monitor::new(&*scheduler.pool); let start = Instant::now(); @@ -1131,7 +1202,7 @@ mod tests { #[test] fn test_monitor_deep_sleep_with_termination() { - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); let monitor = Monitor::new(&*scheduler.pool); scheduler.terminate(); @@ -1142,7 +1213,7 @@ mod tests { #[test] fn test_monitor_deep_sleep_with_notification() { - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); let monitor = Monitor::new(&*scheduler.pool); let _ = scope(|s| { s.spawn(|_| monitor.deep_sleep()); @@ -1164,7 +1235,7 @@ mod tests { #[test] fn test_monitor_deep_sleep_with_blocked_threads() { - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 32); let monitor = Monitor::new(&*scheduler.pool); scheduler.pool.threads[0].blocked_at.store(1, Ordering::Release); diff --git a/vm/src/scheduler/timeout_worker.rs b/rt/src/scheduler/timeout_worker.rs similarity index 91% rename from vm/src/scheduler/timeout_worker.rs rename to rt/src/scheduler/timeout_worker.rs index fb19a9b2d..2bcc6fa13 100644 --- a/vm/src/scheduler/timeout_worker.rs +++ b/rt/src/scheduler/timeout_worker.rs @@ -191,6 +191,7 @@ mod tests { use super::*; use crate::process::Process; use crate::scheduler::process::Scheduler; + use crate::stack::Stack; use crate::test::{empty_process_class, new_process}; #[test] @@ -224,14 +225,14 @@ mod tests { #[test] fn test_run_with_fragmented_heap() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let worker = TimeoutWorker::new(); - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 1024); for time in &[10_u64, 5_u64] { let timeout = Timeout::with_rc(Duration::from_secs(*time)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); worker.suspend(process, timeout); } @@ -250,12 +251,12 @@ mod tests { #[test] fn test_run_with_message() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let worker = TimeoutWorker::new(); - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 1024); let timeout = Timeout::with_rc(Duration::from_secs(10)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); worker.suspend(process, timeout); worker.run_iteration(&scheduler); @@ -265,12 +266,12 @@ mod tests { #[test] fn test_run_with_reschedule() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let worker = TimeoutWorker::new(); - let scheduler = Scheduler::new(1, 1); + let scheduler = Scheduler::new(1, 1, 1024); let timeout = Timeout::with_rc(Duration::from_secs(0)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); worker.suspend(process, timeout); worker.run_iteration(&scheduler); @@ -280,11 +281,11 @@ mod tests { #[test] fn test_defragment_heap_without_fragmentation() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let worker = TimeoutWorker::new(); let timeout = Timeout::with_rc(Duration::from_secs(1)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); worker.suspend(process, timeout); worker.move_messages(); worker.handle_pending_messages(); @@ -297,13 +298,13 @@ mod tests { #[test] fn test_defragment_heap_with_fragmentation() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let worker = TimeoutWorker::new(); for time in &[1_u64, 1_u64] { let timeout = Timeout::with_rc(Duration::from_secs(*time)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); worker.suspend(process, timeout); } diff --git a/vm/src/scheduler/timeouts.rs b/rt/src/scheduler/timeouts.rs similarity index 96% rename from vm/src/scheduler/timeouts.rs rename to rt/src/scheduler/timeouts.rs index c3a6c2afe..8cac32a25 100644 --- a/vm/src/scheduler/timeouts.rs +++ b/rt/src/scheduler/timeouts.rs @@ -23,7 +23,7 @@ impl Timeout { // Our own monotonic clock is the time since the runtime epoch, which is // roughly when the program first started running, so we can safely fit // this in a Duration. - let dur = Duration::from_nanos(nanos as u64); + let dur = Duration::from_nanos(nanos); // This calculates the difference between our own monotonic clock, and // the clock of `std::time::Instant`. We can then turn that into a @@ -197,6 +197,7 @@ impl Drop for Timeouts { #[cfg(test)] mod tests { use super::*; + use crate::stack::Stack; use crate::test::{empty_process_class, new_process}; mod timeout { @@ -335,11 +336,11 @@ mod tests { #[test] fn test_remove_invalid_entries_with_valid_entries() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let mut timeouts = Timeouts::new(); let timeout = Timeout::with_rc(Duration::from_secs(10)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); timeouts.insert(process, timeout); assert_eq!(timeouts.remove_invalid_entries(), 0); @@ -377,11 +378,11 @@ mod tests { #[test] fn test_processes_to_reschedule_with_remaining_time() { let class = empty_process_class("A"); - let process = Process::alloc(*class); + let process = Process::alloc(*class, Stack::new(1024)); let mut timeouts = Timeouts::new(); let timeout = Timeout::with_rc(Duration::from_secs(10)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); timeouts.insert(process, timeout); let (reschedule, expiration) = timeouts.processes_to_reschedule(); @@ -398,7 +399,7 @@ mod tests { let mut timeouts = Timeouts::new(); let timeout = Timeout::with_rc(Duration::from_secs(0)); - process.state().waiting_for_future(Some(timeout.clone())); + process.state().waiting_for_channel(Some(timeout.clone())); timeouts.insert(*process, timeout); let (reschedule, expiration) = timeouts.processes_to_reschedule(); diff --git a/vm/src/socket.rs b/rt/src/socket.rs similarity index 61% rename from vm/src/socket.rs rename to rt/src/socket.rs index 6236e0b27..22178c0d8 100644 --- a/vm/src/socket.rs +++ b/rt/src/socket.rs @@ -2,12 +2,11 @@ pub mod socket_address; use crate::network_poller::Interest; use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; use crate::socket::socket_address::SocketAddress; use crate::state::State; +use libc::{EINPROGRESS, EISCONN}; use socket2::{Domain, SockAddr, Socket as RawSocket, Type}; -use std::io; -use std::io::Read; +use std::io::{self, Read}; use std::mem::transmute; use std::net::Shutdown; use std::net::{IpAddr, SocketAddr}; @@ -19,57 +18,18 @@ use std::time::Duration; /// network poller. const NOT_REGISTERED: i8 = -1; -#[cfg(unix)] -use libc::{EINPROGRESS, EISCONN}; - -#[cfg(windows)] -use windows_sys::Win32::Networking::WinSock::{ - WSAEINPROGRESS as EINPROGRESS, WSAEISCONN as EISCONN, -}; - macro_rules! socket_setter { ($setter:ident, $type:ty) => { - pub(crate) fn $setter(&self, value: $type) -> Result<(), RuntimeError> { - self.inner.$setter(value)?; - - Ok(()) - } - }; -} - -macro_rules! socket_getter { - ($getter:ident, $type:ty) => { - pub(crate) fn $getter(&self) -> Result<$type, RuntimeError> { - Ok(self.inner.$getter()?) - } - }; -} - -macro_rules! socket_u32_getter { - ($getter:ident) => { - pub(crate) fn $getter(&self) -> Result { - Ok(self.inner.$getter()? as usize) + pub(crate) fn $setter(&self, value: $type) -> io::Result<()> { + self.inner.$setter(value) } }; } macro_rules! socket_duration_setter { ($setter:ident) => { - pub(crate) fn $setter(&self, value: u64) -> Result<(), RuntimeError> { - let dur = Duration::from_nanos(value); - - self.inner.$setter(Some(dur))?; - Ok(()) - } - }; -} - -macro_rules! socket_duration_getter { - ($getter:ident) => { - pub(crate) fn $getter(&self) -> Result { - let dur = self.inner.$getter()?; - - Ok(dur.map(|v| v.as_nanos() as i64).unwrap_or(0)) + pub(crate) fn $setter(&self, value: u64) -> io::Result<()> { + self.inner.$setter(Some(Duration::from_nanos(value))) } }; } @@ -78,14 +38,12 @@ macro_rules! socket_duration_getter { fn decode_sockaddr( sockaddr: SockAddr, unix: bool, -) -> Result<(String, i64), RuntimeError> { - let peer_result = if unix { +) -> Result<(String, i64), String> { + if unix { SocketAddress::Unix(sockaddr).address() } else { SocketAddress::Other(sockaddr).address() - }; - - Ok(peer_result?) + } } #[cfg(unix)] @@ -93,14 +51,15 @@ fn encode_sockaddr( address: &str, port: u16, unix: bool, -) -> Result { +) -> io::Result { if unix { - return Ok(SockAddr::unix(address)?); + return SockAddr::unix(address); } - let ip = address.parse::()?; - - Ok(SockAddr::from(SocketAddr::new(ip, port))) + address + .parse::() + .map(|ip| SockAddr::from(SocketAddr::new(ip, port))) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) } #[cfg(not(unix))] @@ -108,10 +67,11 @@ fn encode_sockaddr( address: &str, port: u16, _unix: bool, -) -> Result { - let ip = address.parse::()?; - - Ok(SockAddr::from(SocketAddr::new(ip, port))) +) -> io::Result { + address + .parse::() + .map(|ip| SockAddr::from(SocketAddr::new(ip, port))) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) } /// Returns a slice of the input buffer that a socket operation can write to. @@ -145,25 +105,21 @@ fn update_buffer_length_and_capacity(buffer: &mut Vec, read: usize) { buffer.shrink_to_fit(); } -fn socket_type(kind: i64) -> Result { - let kind = match kind { - 0 => Type::STREAM, - 1 => Type::DGRAM, - 2 => Type::SEQPACKET, - 3 => Type::RAW, - _ => { - return Err(RuntimeError::Panic(format!( - "{} is not a valid socket type", - kind - ))) - } - }; - - Ok(kind) +fn socket_type(kind: i64) -> io::Result { + match kind { + 0 => Ok(Type::STREAM), + 1 => Ok(Type::DGRAM), + 2 => Ok(Type::SEQPACKET), + 3 => Ok(Type::RAW), + _ => Err(io::Error::new( + io::ErrorKind::Other, + format!("{} is not a valid socket type", kind), + )), + } } /// A nonblocking socket that can be registered with a `NetworkPoller`. -pub(crate) struct Socket { +pub struct Socket { /// The raw socket. inner: RawSocket, @@ -187,7 +143,7 @@ impl Socket { domain: Domain, kind: Type, unix: bool, - ) -> Result { + ) -> io::Result { let socket = RawSocket::new(domain, kind, None)?; socket.set_nonblocking(true)?; @@ -199,80 +155,62 @@ impl Socket { }) } - pub(crate) fn ipv4(kind_int: i64) -> Result { + pub(crate) fn ipv4(kind_int: i64) -> io::Result { Self::new(Domain::IPV4, socket_type(kind_int)?, false) } - pub(crate) fn ipv6(kind_int: i64) -> Result { + pub(crate) fn ipv6(kind_int: i64) -> io::Result { Self::new(Domain::IPV6, socket_type(kind_int)?, false) } #[cfg(unix)] - pub(crate) fn unix(kind_int: i64) -> Result { + pub(crate) fn unix(kind_int: i64) -> io::Result { Self::new(Domain::UNIX, socket_type(kind_int)?, true) } #[cfg(not(unix))] - pub(crate) fn unix(_: i64) -> Result { - Err(RuntimeError::from( + pub(crate) fn unix(_: i64) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Other, "UNIX sockets aren't supported on this platform", )) } - pub(crate) fn bind( - &self, - address: &str, - port: u16, - ) -> Result<(), RuntimeError> { + pub(crate) fn bind(&self, address: &str, port: u16) -> io::Result<()> { let sockaddr = encode_sockaddr(address, port, self.unix)?; - self.inner.bind(&sockaddr)?; - - Ok(()) + self.inner.bind(&sockaddr) } - pub(crate) fn listen(&self, backlog: i32) -> Result<(), RuntimeError> { - self.inner.listen(backlog)?; - - Ok(()) + pub(crate) fn listen(&self, backlog: i32) -> io::Result<()> { + self.inner.listen(backlog) } - pub(crate) fn connect( - &self, - address: &str, - port: u16, - ) -> Result<(), RuntimeError> { + pub(crate) fn connect(&self, address: &str, port: u16) -> io::Result<()> { let sockaddr = encode_sockaddr(address, port, self.unix)?; match self.inner.connect(&sockaddr) { - Ok(_) => {} + Ok(_) => Ok(()), Err(ref e) if e.kind() == io::ErrorKind::WouldBlock - || e.raw_os_error() == Some(EINPROGRESS as i32) => + || e.raw_os_error() == Some(EINPROGRESS) => { if let Ok(Some(err)) = self.inner.take_error() { // When performing a connect(), the error returned may be // WouldBlock, with the actual error being stored in - // SO_ERROR on the socket. Windows in particular seems to - // take this approach. - return Err(err.into()); + // SO_ERROR on the socket. + return Err(err); } - // On Windows a connect(2) might throw WSAEWOULDBLOCK, the - // Windows equivalent of EAGAIN/EWOULDBLOCK. Other platforms may - // also produce some error that Rust will report as WouldBlock. - return Err(RuntimeError::WouldBlock); + Err(io::Error::from(io::ErrorKind::WouldBlock)) } - Err(ref e) if e.raw_os_error() == Some(EISCONN as i32) => { + Err(ref e) if e.raw_os_error() == Some(EISCONN) => { // We may run into an EISCONN if a previous connect(2) attempt // would block. In this case we can just continue. + Ok(()) } - Err(e) => { - return Err(e.into()); - } + Err(e) => Err(e), } - - Ok(()) } pub(crate) fn register( @@ -281,7 +219,7 @@ impl Socket { process: ProcessPointer, thread_poller_id: usize, interest: Interest, - ) -> Result<(), RuntimeError> { + ) -> io::Result<()> { let existing_id = self.registered.load(Ordering::Acquire); // Once registered, the process might be rescheduled immediately if @@ -292,7 +230,7 @@ impl Socket { // // 1. Set "registered" _first_ (if necessary) // 2. Add the socket to the poller - let result = if existing_id == NOT_REGISTERED { + if existing_id == NOT_REGISTERED { let poller = &state.network_pollers[thread_poller_id]; self.registered.store(thread_poller_id as i8, Ordering::Release); @@ -302,11 +240,9 @@ impl Socket { let poller = &state.network_pollers[existing_id as usize]; poller.modify(process, &self.inner, interest) - }; - + } // *DO NOT* use "self" from here on, as the socket/process may already // be running on a different thread. - result.map_err(|e| e.into()) } pub(crate) fn deregister(&mut self, state: &State) { @@ -314,7 +250,7 @@ impl Socket { let _ = state.network_pollers[poller_id].delete(&self.inner); } - pub(crate) fn accept(&self) -> Result { + pub(crate) fn accept(&self) -> io::Result { let (socket, _) = self.inner.accept()?; // Accepted sockets don't inherit the non-blocking status of the @@ -332,7 +268,7 @@ impl Socket { &self, buffer: &mut Vec, amount: usize, - ) -> Result { + ) -> io::Result { if amount > 0 { // We don't use take(), because that only terminates if: // @@ -355,7 +291,7 @@ impl Socket { &self, buffer: &mut Vec, bytes: usize, - ) -> Result<(String, i64), RuntimeError> { + ) -> io::Result<(String, i64)> { let slice = socket_output_slice(buffer, bytes); let (read, sockaddr) = self.inner.recv_from(unsafe { transmute(slice) })?; @@ -363,6 +299,7 @@ impl Socket { update_buffer_length_and_capacity(buffer, read); decode_sockaddr(sockaddr, self.unix) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) } pub(crate) fn send_to( @@ -370,34 +307,36 @@ impl Socket { buffer: &[u8], address: &str, port: u16, - ) -> Result { + ) -> io::Result { let sockaddr = encode_sockaddr(address, port, self.unix)?; - Ok(self.inner.send_to(buffer, &sockaddr)?) + self.inner.send_to(buffer, &sockaddr) } - pub(crate) fn local_address(&self) -> Result<(String, i64), RuntimeError> { + pub(crate) fn local_address(&self) -> io::Result<(String, i64)> { let sockaddr = self.inner.local_addr()?; decode_sockaddr(sockaddr, self.unix) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) } - pub(crate) fn peer_address(&self) -> Result<(String, i64), RuntimeError> { + pub(crate) fn peer_address(&self) -> io::Result<(String, i64)> { let sockaddr = self.inner.peer_addr()?; decode_sockaddr(sockaddr, self.unix) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) } - pub(crate) fn shutdown_read(&self) -> Result<(), RuntimeError> { - self.inner.shutdown(Shutdown::Read).map_err(|e| e.into()) + pub(crate) fn shutdown_read(&self) -> io::Result<()> { + self.inner.shutdown(Shutdown::Read) } - pub(crate) fn shutdown_write(&self) -> Result<(), RuntimeError> { - self.inner.shutdown(Shutdown::Write).map_err(|e| e.into()) + pub(crate) fn shutdown_write(&self) -> io::Result<()> { + self.inner.shutdown(Shutdown::Write) } - pub(crate) fn shutdown_read_write(&self) -> Result<(), RuntimeError> { - self.inner.shutdown(Shutdown::Both).map_err(|e| e.into()) + pub(crate) fn shutdown_read_write(&self) -> io::Result<()> { + self.inner.shutdown(Shutdown::Both) } socket_setter!(set_ttl, u32); @@ -412,46 +351,17 @@ impl Socket { socket_duration_setter!(set_linger); - socket_getter!(only_v6, bool); - socket_getter!(nodelay, bool); - socket_getter!(broadcast, bool); - socket_getter!(reuse_address, bool); - socket_getter!(keepalive, bool); - - socket_getter!(recv_buffer_size, usize); - socket_getter!(send_buffer_size, usize); - - socket_u32_getter!(ttl); - - socket_duration_getter!(linger); - #[cfg(unix)] - pub(crate) fn set_reuse_port( - &self, - reuse: bool, - ) -> Result<(), RuntimeError> { - Ok(self.inner.set_reuse_port(reuse)?) + pub(crate) fn set_reuse_port(&self, reuse: bool) -> io::Result<()> { + self.inner.set_reuse_port(reuse) } #[cfg(not(unix))] - pub(crate) fn set_reuse_port( - &self, - _reuse: bool, - ) -> Result<(), RuntimeError> { + pub(crate) fn set_reuse_port(&self, _reuse: bool) -> io::Result<()> { Ok(()) } - #[cfg(unix)] - pub(crate) fn reuse_port(&self) -> Result { - Ok(self.inner.reuse_port()?) - } - - #[cfg(not(unix))] - pub(crate) fn reuse_port(&self) -> Result { - Ok(false) - } - - pub(crate) fn try_clone(&self) -> Result { + pub(crate) fn try_clone(&self) -> io::Result { let sock = Socket { inner: self.inner.try_clone()?, registered: AtomicI8::new(NOT_REGISTERED), @@ -497,17 +407,6 @@ mod tests { #[test] fn test_type_size() { - let socket_size = if cfg!(windows) { - // Windows uses a HANDLE type, which is a pointer. This means the - // total size is 16 bytes due to the extra flags we store in the - // socket. - 16 - } else { - // On Unix a file descriptor is a 32-bits int, so the total size is - // 8 bytes. - 8 - }; - - assert_eq!(size_of::(), socket_size); + assert_eq!(size_of::(), 8); } } diff --git a/vm/src/socket/socket_address.rs b/rt/src/socket/socket_address.rs similarity index 69% rename from vm/src/socket/socket_address.rs rename to rt/src/socket/socket_address.rs index 44aa9e6b8..037c7881d 100644 --- a/vm/src/socket/socket_address.rs +++ b/rt/src/socket/socket_address.rs @@ -4,27 +4,24 @@ use socket2::SockAddr; use { libc::sockaddr_un, std::ffi::OsStr, + std::mem::transmute, std::os::{raw::c_char, unix::ffi::OsStrExt}, }; #[cfg(unix)] #[cfg_attr(feature = "cargo-clippy", allow(clippy::uninit_assumed_init))] -fn sun_path_offset() -> usize { - use std::mem::MaybeUninit; - - let addr: sockaddr_un = unsafe { MaybeUninit::uninit().assume_init() }; - let base = &addr as *const _ as usize; - let path = &addr.sun_path as *const _ as usize; +fn sun_path_offset(addr: &sockaddr_un) -> usize { + let base = addr as *const sockaddr_un as usize; + let path = &addr.sun_path as *const c_char as usize; path - base } #[cfg(unix)] fn unix_socket_path(sockaddr: &SockAddr) -> String { - let len = sockaddr.len() as usize - sun_path_offset(); - let raw_addr = unsafe { &*(sockaddr.as_ptr() as *mut sockaddr_un) }; - let path = - unsafe { &*(&raw_addr.sun_path as *const [c_char] as *const [u8]) }; + let raw_addr = unsafe { &*(sockaddr.as_ptr() as *const sockaddr_un) }; + let len = sockaddr.len() as usize - sun_path_offset(raw_addr); + let path = unsafe { transmute::<&[c_char], &[u8]>(&raw_addr.sun_path) }; if len == 0 || (cfg!(not(target_os = "linux")) && raw_addr.sun_path[0] == 0) { @@ -73,3 +70,20 @@ impl SocketAddress { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(unix)] + fn test_unix_socket_path() { + let path1 = unix_socket_path(&SockAddr::unix("foo.sock").unwrap()); + let path2 = unix_socket_path(&SockAddr::unix("").unwrap()); + let path3 = unix_socket_path(&SockAddr::unix("\0").unwrap()); + + assert_eq!(path1, "foo.sock".to_string()); + assert_eq!(path2, String::new()); + assert_eq!(path3, String::new()); + } +} diff --git a/rt/src/stack.rs b/rt/src/stack.rs new file mode 100644 index 000000000..e2310c0eb --- /dev/null +++ b/rt/src/stack.rs @@ -0,0 +1,219 @@ +use crate::memory_map::MemoryMap; +use crate::page::page_size; +use std::collections::VecDeque; + +/// The age of a reusable stack after which we deem it too old to keep around. +/// +/// The value here is arbitrary, and chosen under the assumption it's good +/// enough to prevent excessive shrinking. +const SHRINK_AGE: u16 = 10; + +/// The minimum number of stacks we need before we'll even consider shrinking +/// a stack pool. +/// +/// The value here is arbitrary and mostly meant to avoid the shrinking overhead +/// for cases where it's pretty much just a waste of time. +const MIN_STACKS: usize = 4; + +/// A pool of `Stack` objects to reuse. +/// +/// Stacks all share the same size and can't grow beyond this size. The decision +/// to not support growable stack is for the following reasons: +/// +/// - It can mask/hide runaway recursion. +/// - It requires a prologue of sorts in every function to check if there's +/// enough stack space remaining. +/// - Depending on the backend used we may not have enough information to +/// accurately determine the amount of stack space a function needs. +/// - It's not clear when/if we would also shrink the stack, and doing this at +/// the right time is tricky. +/// - Foreign function calls would require that we either grow the stack to a +/// reasonable size, or swap the stack with a temporary stack of a larger +/// size. Both add complexity and incur a runtime cost we're not happy with. +/// - Even with a 1 MiB stack per process one can easily run millions of +/// processes and not run out of virtual memory. +/// - Processes aren't likely to use even close to the full amount of stack +/// space, so all the trouble of resizing stacks likely just isn't worth it. +/// - Fixed size stacks make it easier to reuse stacks, as we don't need to +/// divide them into size classes and pick the appropriate one based on the +/// size we need. +/// +/// The amount of reusable stacks is reduced every now and then to prevent a +/// pool from using excessive amounts of memory. +pub(crate) struct StackPool { + /// The amount of bytes to allocate for every stack, excluding guard pages. + /// + /// The actual size used may be slightly larger depending on the OS' page + /// size. + size: usize, + + /// Any stacks that can be reused. + stacks: VecDeque, + + /// The current epoch. + /// + /// This value is used to determine if reusable stacks have been sitting + /// around for too long and should be discarded. + epoch: u16, + + /// The check-in epoch for every stack. + /// + /// These values are stored separate from the stacks as we only need them + /// when the stacks are reusable. + epochs: VecDeque, +} + +impl StackPool { + pub fn new(size: usize) -> Self { + Self { + size, + stacks: VecDeque::new(), + epoch: 0, + epochs: VecDeque::new(), + } + } + + pub(crate) fn alloc(&mut self) -> Stack { + if let Some(stack) = self.stacks.pop_back() { + self.epoch = self.epoch.wrapping_add(1); + self.epochs.pop_back(); + stack + } else { + Stack::new(self.size) + } + } + + pub(crate) fn add(&mut self, stack: Stack) { + self.stacks.push_back(stack); + self.epochs.push_back(self.epoch); + } + + /// Shrinks the list of reusable stacks to at most half the current number + /// of stacks. + /// + /// Using this method we can keep the number of unused stacks under control. + /// For example, if we suddenly need many stacks but then never reuse most + /// of them, this is a waste of memory. + pub(crate) fn shrink(&mut self) { + if self.stacks.len() < MIN_STACKS { + return; + } + + let trim_size = self + .epochs + .iter() + .filter(|&&epoch| self.epoch.abs_diff(epoch) >= SHRINK_AGE) + .count(); + + // We want to shrink to at most half the size in an attempt to not + // remove too many stacks we may need later. + let max = std::cmp::min(self.stacks.len() / 2, trim_size); + + self.epochs.drain(0..max); + self.stacks.drain(0..max); + + // Update the epochs of the remaining stacks so we don't shrink too soon + // again. + self.epochs = VecDeque::from(vec![self.epoch; self.epochs.len()]); + } +} + +/// A type that represents stack memory. +/// +/// A `Stack` represents a chunk of memory of a certain size that can be used +/// as a process' stack memory. The exact implementation differs per platform. +#[repr(C)] +pub struct Stack { + mem: MemoryMap, +} + +impl Stack { + pub(crate) fn new(size: usize) -> Self { + let page = page_size(); + let mut mem = MemoryMap::new(page + size + page, true); + + // There's nothing we can do at runtime in response to the guard pages + // not being set up, so we just terminate if this ever happens. + mem.protect(0) + .and_then(|_| mem.protect(mem.len - page)) + .expect("Failed to set up the stack's guard pages"); + + Self { mem } + } + + pub(crate) fn stack_pointer(&self) -> *mut u8 { + unsafe { self.mem.ptr.add(self.mem.len - page_size()) } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_stack_pool_alloc() { + let mut pool = StackPool::new(page_size()); + let size = page_size(); + let stack = pool.alloc(); + + assert_eq!(stack.mem.len, size * 3); + } + + #[test] + fn test_stack_pool_alloc_with_reuse() { + let size = page_size(); + let mut pool = StackPool::new(size); + let stack = pool.alloc(); + + pool.add(stack); + assert_eq!(pool.stacks.len(), 1); + assert_eq!(pool.epochs, vec![0]); + + pool.alloc(); + assert!(pool.stacks.is_empty()); + assert!(pool.epochs.is_empty()); + + pool.add(Stack::new(size)); + pool.add(Stack::new(size)); + pool.alloc(); + pool.alloc(); + + assert_eq!(pool.epoch, 3); + } + + #[test] + fn test_stack_pool_shrink() { + let size = page_size(); + let mut pool = StackPool::new(size); + + pool.epoch = 14; + + pool.add(Stack::new(size)); + pool.add(Stack::new(size)); + pool.epochs[0] = 1; + pool.epochs[1] = 2; + + // Not enough stacks, so no shrinking is performed. + pool.shrink(); + + pool.add(Stack::new(size)); + pool.add(Stack::new(size)); + pool.add(Stack::new(size)); + pool.add(Stack::new(size)); + + pool.epochs[2] = 3; + pool.epochs[3] = 4; + pool.epochs[4] = 11; + pool.epochs[5] = 12; + + // This shrinks the pool. + pool.shrink(); + + // This doesn't shrink the pool because we updated the epochs to prevent + // excessive shrinking. + pool.shrink(); + + assert_eq!(pool.stacks.len(), 3); + assert_eq!(&pool.epochs, &[14, 14, 14]); + } +} diff --git a/rt/src/state.rs b/rt/src/state.rs new file mode 100644 index 000000000..897fbd85b --- /dev/null +++ b/rt/src/state.rs @@ -0,0 +1,229 @@ +use crate::arc_without_weak::ArcWithoutWeak; +use crate::config::Config; +use crate::mem::{ + Array, Bool, ByteArray, Class, ClassPointer, Float, Int, Nil, + String as InkoString, +}; +use crate::network_poller::NetworkPoller; +use crate::process::Channel; +use crate::scheduler::process::Scheduler; +use crate::scheduler::timeout_worker::TimeoutWorker; +use rand::{thread_rng, Rng}; +use std::collections::HashMap; +use std::env; +use std::mem::size_of; +use std::panic::RefUnwindSafe; +use std::time; + +/// Allocates a new class, returning a tuple containing the owned pointer and a +/// permanent reference pointer. +macro_rules! class { + ($name: expr, $methods: expr, $size_source: ident) => {{ + Class::alloc( + $name.to_string(), + $methods, + size_of::<$size_source>() as u32, + ) + }}; +} + +/// A reference counted State. +pub(crate) type RcState = ArcWithoutWeak; + +/// The number of methods used for the various built-in classes. +/// +/// These counts are used to determine how much memory is needed for allocating +/// the various built-in classes. +#[derive(Default, Debug)] +#[repr(C)] +pub struct MethodCounts { + pub(crate) int_class: u16, + pub(crate) float_class: u16, + pub(crate) string_class: u16, + pub(crate) array_class: u16, + pub(crate) boolean_class: u16, + pub(crate) nil_class: u16, + pub(crate) byte_array_class: u16, + pub(crate) channel_class: u16, +} + +/// The state of the Inko runtime. +#[repr(C)] +pub struct State { + // These fields are exposed to the generated code directly, hence they're + // marked as public. These fields must also come first so their offsets are + // more reliable/stable. + pub true_singleton: *const Bool, + pub false_singleton: *const Bool, + pub nil_singleton: *const Nil, + pub int_class: ClassPointer, + pub float_class: ClassPointer, + pub string_class: ClassPointer, + pub array_class: ClassPointer, + pub bool_class: ClassPointer, + pub nil_class: ClassPointer, + pub byte_array_class: ClassPointer, + pub channel_class: ClassPointer, + + /// The first randomly generated key to use for hashers. + pub hash_key0: *const Int, + + /// The second randomly generated key to use for hashers. + pub hash_key1: *const Int, + + /// The runtime's configuration. + pub(crate) config: Config, + + /// The start time of the VM (more or less). + pub(crate) start_time: time::Instant, + + /// The commandline arguments passed to an Inko program. + pub(crate) arguments: Vec<*const InkoString>, + + /// The environment variables defined when the VM started. + /// + /// We cache environment variables because C functions used through the FFI + /// (or through libraries) may call `setenv()` concurrently with `getenv()` + /// calls, which is unsound. Caching the variables also means we can safely + /// use `localtime_r()` (which internally may call `setenv()`). + pub(crate) environment: HashMap, + + /// The scheduler to use for executing Inko processes. + pub(crate) scheduler: Scheduler, + + /// A task used for handling timeouts, such as message and IO timeouts. + pub(crate) timeout_worker: TimeoutWorker, + + /// The network pollers to use for process threads. + pub(crate) network_pollers: Vec, +} + +unsafe impl Sync for State {} +impl RefUnwindSafe for State {} + +impl State { + pub(crate) fn new( + config: Config, + counts: &MethodCounts, + args: &[String], + ) -> RcState { + let int_class = class!("Int", counts.int_class, Int); + let float_class = class!("Float", counts.float_class, Float); + let string_class = class!("String", counts.string_class, InkoString); + let array_class = class!("Array", counts.array_class, Array); + let bool_class = class!("Bool", counts.boolean_class, Bool); + let nil_class = class!("Nil", counts.nil_class, Nil); + let byte_array_class = + class!("ByteArray", counts.byte_array_class, ByteArray); + let channel_class = class!("Channel", counts.channel_class, Channel); + let true_singleton = Bool::alloc(bool_class); + let false_singleton = Bool::alloc(bool_class); + let nil_singleton = Nil::alloc(nil_class); + let arguments = args + .iter() + .map(|arg| InkoString::alloc_permanent(string_class, arg.clone())) + .collect(); + + let mut rng = thread_rng(); + let hash_key0 = Int::new_permanent(int_class, rng.gen()); + let hash_key1 = Int::new_permanent(int_class, rng.gen()); + let environment = env::vars_os() + .map(|(k, v)| { + ( + k.to_string_lossy().into_owned(), + InkoString::alloc_permanent( + string_class, + v.to_string_lossy().into_owned(), + ), + ) + }) + .collect::>(); + + let scheduler = Scheduler::new( + config.process_threads as usize, + config.backup_threads as usize, + config.stack_size as usize, + ); + + let network_pollers = + (0..config.netpoll_threads).map(|_| NetworkPoller::new()).collect(); + + let state = State { + hash_key0, + hash_key1, + scheduler, + environment, + config, + start_time: time::Instant::now(), + timeout_worker: TimeoutWorker::new(), + arguments, + network_pollers, + int_class, + float_class, + string_class, + array_class, + bool_class, + nil_class, + byte_array_class, + channel_class, + true_singleton, + false_singleton, + nil_singleton, + }; + + ArcWithoutWeak::new(state) + } + + pub(crate) fn terminate(&self) { + self.scheduler.terminate(); + } +} + +impl Drop for State { + fn drop(&mut self) { + unsafe { + for &val in &self.arguments { + InkoString::drop_and_deallocate(val) + } + + for &val in self.environment.values() { + InkoString::drop_and_deallocate(val); + } + + Bool::drop_and_deallocate(self.true_singleton); + Bool::drop_and_deallocate(self.false_singleton); + Nil::drop_and_deallocate(self.nil_singleton); + + Class::drop(self.int_class); + Class::drop(self.float_class); + Class::drop(self.string_class); + Class::drop(self.array_class); + Class::drop(self.bool_class); + Class::drop(self.nil_class); + Class::drop(self.byte_array_class); + Class::drop(self.channel_class); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! offset_of { + ($value: expr, $field: ident) => {{ + (std::ptr::addr_of!($value.$field) as usize) + - (&*$value as *const _ as usize) + }}; + } + + #[test] + fn test_field_offsets() { + let config = Config::new(); + let state = State::new(config, &MethodCounts::default(), &[]); + + assert_eq!(offset_of!(state, true_singleton), 0); + assert_eq!(offset_of!(state, false_singleton), 8); + assert_eq!(offset_of!(state, nil_singleton), 16); + } +} diff --git a/vm/src/test.rs b/rt/src/test.rs similarity index 53% rename from vm/src/test.rs rename to rt/src/test.rs index 39c5ab892..53a2e1a98 100644 --- a/vm/src/test.rs +++ b/rt/src/test.rs @@ -1,13 +1,9 @@ //! Helper functions for writing unit tests. use crate::config::Config; -use crate::location_table::LocationTable; -use crate::mem::{ - Class, ClassPointer, Method, MethodPointer, Module, ModulePointer, -}; -use crate::permanent_space::{MethodCounts, PermanentSpace}; -use crate::process::{Process, ProcessPointer}; -use crate::state::{RcState, State}; -use bytecode::{Instruction, Opcode}; +use crate::mem::{Class, ClassPointer}; +use crate::process::{NativeAsyncMethod, Process, ProcessPointer}; +use crate::stack::Stack; +use crate::state::{MethodCounts, RcState, State}; use std::mem::forget; use std::ops::{Deref, DerefMut, Drop}; @@ -55,7 +51,7 @@ impl Drop for OwnedProcess { /// A class that is dropped when this pointer is dropped. #[repr(transparent)] -pub(crate) struct OwnedClass(ClassPointer); +pub(crate) struct OwnedClass(pub(crate) ClassPointer); impl OwnedClass { pub(crate) fn new(ptr: ClassPointer) -> Self { @@ -73,31 +69,9 @@ impl Deref for OwnedClass { impl Drop for OwnedClass { fn drop(&mut self) { - Class::drop(self.0); - } -} - -/// A module that is dropped when this pointer is dropped. -#[repr(transparent)] -pub(crate) struct OwnedModule(ModulePointer); - -impl OwnedModule { - pub(crate) fn new(ptr: ModulePointer) -> Self { - Self(ptr) - } -} - -impl Deref for OwnedModule { - type Target = ModulePointer; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Drop for OwnedModule { - fn drop(&mut self) { - Module::drop_and_deallocate(self.0); + unsafe { + Class::drop(self.0); + } } } @@ -109,48 +83,18 @@ pub(crate) fn setup() -> RcState { // different platforms. config.process_threads = 2; - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - - State::new(config, perm, &[]) + State::new(config, &MethodCounts::default(), &[]) } pub(crate) fn new_process(class: ClassPointer) -> OwnedProcess { - OwnedProcess::new(Process::alloc(class)) + OwnedProcess::new(Process::alloc(class, Stack::new(1024))) } pub(crate) fn new_main_process( class: ClassPointer, - method: MethodPointer, + method: NativeAsyncMethod, ) -> OwnedProcess { - OwnedProcess::new(Process::main(class, method)) -} - -pub(crate) fn empty_module(class: ClassPointer) -> OwnedModule { - OwnedModule::new(Module::alloc(class)) -} - -pub(crate) fn empty_method() -> MethodPointer { - // We use Int values for the name so we don't have to worry about also - // dropping strings when dropping this Method. - Method::alloc( - 123, - 2, - vec![Instruction::new(Opcode::Return, [0; 5])], - LocationTable::new(), - Vec::new(), - ) -} - -pub(crate) fn empty_async_method() -> MethodPointer { - // We use Int values for the name so we don't have to worry about also - // dropping strings when dropping this Method. - Method::alloc( - 123, - 2, - vec![Instruction::one(Opcode::ProcessFinishTask, 1)], - LocationTable::new(), - Vec::new(), - ) + OwnedProcess::new(Process::main(class, method, Stack::new(1024))) } pub(crate) fn empty_class(name: &str) -> OwnedClass { diff --git a/scripts/llvm.sh b/scripts/llvm.sh new file mode 100755 index 000000000..ce7281dd1 --- /dev/null +++ b/scripts/llvm.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +set -e + +if [ "$RUNNER_OS" = "Linux" ] +then + # libclang-common is needed because of + # https://gitlab.com/taricorp/llvm-sys.rs/-/issues/16. + sudo apt-get install --yes llvm-15 llvm-15-dev libstdc++-11-dev \ + libclang-common-15-dev zlib1g-dev +elif [ "$RUNNER_OS" = "macOS" ] +then + brew install llvm@15 + echo "$(brew --prefix llvm@15)/bin" >> $GITHUB_PATH +else + echo 'RUNNER_OS must be set to a supported value' + exit 1 +fi diff --git a/libstd/src/std/array.inko b/std/src/std/array.inko similarity index 78% rename from libstd/src/std/array.inko rename to std/src/std/array.inko index d869d2b1a..7d0719c30 100644 --- a/libstd/src/std/array.inko +++ b/std/src/std/array.inko @@ -4,11 +4,21 @@ import std::cmp::(Contains, Equal) import std::drop::Drop import std::fmt::(Format, Formatter) import std::hash::(Hash, Hasher) -import std::index::(bounds_check, Index, IndexMut, SetIndex) import std::iter::(Enum, Iter) import std::option::Option import std::rand::Shuffle +# Checks if `index` is in the range of zero up to (but excluding) `length`. +# +# # Panics +# +# This method panics if the index is out of bounds. +fn pub bounds_check(index: Int, length: Int) { + if index >= 0 and index < length { return } + + _INKO.panic("The index {index} is out of bounds (length: {length})") +} + # An ordered, integer-indexed generic collection of values. # # Accessing values in an `Array` is a constant-time operation. @@ -43,12 +53,13 @@ class builtin Array[T] { # # Examples # # Array.filled(with: 0, times: 4) # => [0, 0, 0, 0] - fn pub static filled[V: Clone](with: V, times: Int) -> Array[V] { - let array = with_capacity(times) + fn pub static filled[V: Clone[V]](with: V, times: Int) -> Array[V] { + if times == 0 { return [] } - if times == 0 { return array } + let array = with_capacity(times) + let temp = ref with - (times - 1).times fn (_) { array.push(with.clone) } + (times - 1).times fn (_) { array.push(temp.clone) } array.push(with) array } @@ -106,9 +117,10 @@ class builtin Array[T] { # # array.pop # => Option.None fn pub mut pop -> Option[T] { - let val = _INKO.array_pop(self) as T - - if _INKO.is_undefined(val) { Option.None } else { Option.Some(val) } + match _INKO.array_pop(self) { + case { @tag = 0, @value = v } -> Option.Some(v as T) + case _ -> Option.None + } } # Removes the value at the given index, returning the removed value. @@ -131,7 +143,7 @@ class builtin Array[T] { _INKO.array_remove(self, index) as T } - # Returns an immutable reference to the value at the given index. + # Returns an optional immutable reference to the value at the given index. # # # Examples # @@ -139,46 +151,56 @@ class builtin Array[T] { # # let numbers = [10, 20] # - # numbers.get(0) # => Option.Some(ref 10) + # numbers.opt(0) # => Option.Some(ref 10) # # Retrieving a value from a non-existing index: # # let numbers = [10, 20] # - # numbers.get(5) # => Option.None - fn pub get(index: Int) -> Option[ref T] { + # numbers.opt(5) # => Option.None + fn pub opt(index: Int) -> Option[ref T] { if index < 0 or index >= length { return Option.None } - let raw = _INKO.array_get(self, index) as T - let res = ref raw - - _INKO.moved(raw) - Option.Some(res) + Option.Some((ref _INKO.array_get(self, index)) as ref T) } - # Returns a mutable reference to the value at the given index. + # Returns an immutable reference to the value at the given index. # - # # Examples + # # Panics # - # Retrieving an existing value: + # This method panics if the index is out of bounds. + # + # # Examples # # let numbers = [10, 20] # - # numbers.get_mut(0) # => Option.Some(mut 10) + # numbers.get(0) # => 10 + fn pub get(index: Int) -> ref T { + bounds_check(index, length) + (ref _INKO.array_get(self, index)) as ref T + } + + # Stores a value at the given index. # - # Retrieving a value from a non-existing index: + # If a value is already present at the given index, it's dropped before the + # new value overwrites it. # - # let numbers = [10, 20] + # # Panics # - # numbers.get_mut(5) # => Option.None - fn pub mut get_mut(index: Int) -> Option[mut T] { - if index < 0 or index >= length { return Option.None } - - let raw = _INKO.array_get(self, index) as T - let res = mut raw - - _INKO.moved(raw) - Option.Some(res) + # This method panics if the index is out of bounds. + # + # # Examples + # + # Setting an index to a value: + # + # let array = [] + # + # array.set(0, 10) + # array # => [10] + fn pub mut set(index: Int, value: T) { + bounds_check(index, length) + _INKO.array_set(self, index, value) as T + _INKO.moved(value) } # Inserts the value at the given index, returning the old value. @@ -204,13 +226,8 @@ class builtin Array[T] { # Returns an iterator that yields immutable references to the values in # `self`. - fn pub iter -> Iter[ref T, Never] { - Enum.indexed(length) fn (index) { self[index] } - } - - # Returns an iterator that yields mutable references to the values in `self`. - fn pub mut iter_mut -> Iter[mut T, Never] { - Enum.indexed(length) fn (index) { self[index] } + fn pub iter -> Iter[ref T] { + Enum.indexed(length) fn (index) { get(index) } } # Returns an `Iter` that iterates over all values in `self`, returning them @@ -243,12 +260,12 @@ class builtin Array[T] { # iter.next # => Option.Some(20) # iter.next # => Option.Some(10) # iter.next # => Option.None - fn pub reverse_iter -> Iter[ref T, Never] { + fn pub reverse_iter -> Iter[ref T] { let mut index = length - 1 Enum.new fn move { if index > -1 { - Option.Some(self[index := index - 1]) + Option.Some(get(index := index - 1)) } else { Option.None } @@ -266,7 +283,7 @@ class builtin Array[T] { # numbers.append([40, 50]) # # numbers.length # => 5 - fn pub mut append(other: Self) { + fn pub mut append(other: Array[T]) { other.into_iter.each fn (v) { push(v) } } @@ -341,91 +358,86 @@ class builtin Array[T] { } } -impl Drop for Array { - fn mut drop { - let mut index = 0 - - while index < length { _INKO.array_get(self, index := index + 1) as T } +impl Array if T: mut { + # Returns an optional mutable reference to the value at the given index. + # + # # Examples + # + # Retrieving an existing value: + # + # let numbers = [10, 20] + # + # numbers.opt_mut(0) # => Option.Some(mut 10) + # + # Retrieving a value from a non-existing index: + # + # let numbers = [10, 20] + # + # numbers.opt_mut(5) # => Option.None + fn pub mut opt_mut(index: Int) -> Option[mut T] { + if index < 0 or index >= length { return Option.None } - _INKO.array_drop(self) + Option.Some((mut _INKO.array_get(self, index)) as mut T) } -} -impl Contains[T] for Array if T: Equal { - # Returns `true` if `self` contains the given value. + # Returns a mutable reference to the value at the given index. + # + # # Panics + # + # This method panics if the index is out of bounds. # # # Examples # - # Checking if an `Array` contains a value: + # let numbers = [10, 20] # - # [10, 20, 30].contains?(10) # => true - fn pub contains?(value: ref T) -> Bool { - iter.any? fn (ours) { ours == value } - } -} - -impl Index[Int, ref T] for Array { - fn pub index(index: Int) -> ref T { + # numbers.get_mut(0) # => 10 + fn pub mut get_mut(index: Int) -> mut T { bounds_check(index, length) + (mut _INKO.array_get(self, index)) as mut T + } - let raw = _INKO.array_get(self, index) as T - let res = ref raw - - _INKO.moved(raw) - res + # Returns an iterator that yields mutable references to the values in `self`. + fn pub mut iter_mut -> Iter[mut T] { + Enum.indexed(length) fn (index) { get_mut(index) } } } -impl IndexMut[Int, mut T] for Array { - fn pub mut index_mut(index: Int) -> mut T { - bounds_check(index, length) +impl Drop for Array { + fn mut drop { + let mut index = 0 - let raw = _INKO.array_get(self, index) as T - let res = mut raw + while index < length { _INKO.array_get(self, index := index + 1) as T } - _INKO.moved(raw) - res + _INKO.array_drop(self) } } -impl SetIndex[Int, T] for Array { - # Stores a value at the given index. - # - # If a value is already present at the given index, it's dropped before the - # new value overwrites it. +impl Contains[T] for Array if T: Equal[T] { + # Returns `true` if `self` contains the given value. # # # Examples # - # Setting an index to a value: - # - # let array = [] - # - # array[0] = 10 # => 10 - # array # => [10] - # - # # Panics + # Checking if an `Array` contains a value: # - # This method panics if the index is out of bounds. - fn pub mut set_index(index: Int, value: T) { - bounds_check(index, length) - _INKO.array_set(self, index, value) as T - _INKO.moved(value) + # [10, 20, 30].contains?(10) # => true + fn pub contains?(value: ref T) -> Bool { + iter.any? fn (ours) { ours == value } } } -impl Clone for Array if T: Clone { - fn pub clone -> Self { +impl Clone[Array[T]] for Array if T: Clone[T] { + fn pub clone -> Array[T] { let copy = [] let mut index = 0 let max = length - while index < max { copy.push(self[index := index + 1].clone) } + while index < max { copy.push(get(index := index + 1).clone) } copy } } -impl Equal for Array if T: Equal { +impl Equal[Array[T]] for Array if T: Equal[T] { # Returns `true` if `self` and the given `Array` are identical. # # # Examples @@ -441,15 +453,15 @@ impl Equal for Array if T: Equal { # Comparing two arrays with the same length but with different values: # # [10, 20] == [20, 10] # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Array[T]) -> Bool { if length != other.length { return false } let mut index = 0 let max = length while index < max { - let ours = self[index] - let theirs = other[index] + let ours = get(index) + let theirs = other.get(index) if ours != theirs { return false } @@ -465,7 +477,7 @@ impl Hash for Array if T: Hash { let mut index = 0 let max = length - while index < max { self[index := index + 1].hash(hasher) } + while index < max { get(index := index + 1).hash(hasher) } } } @@ -510,7 +522,7 @@ impl Drop for IntoIter { } } -impl Iter[T, Never] for IntoIter { +impl Iter[T] for IntoIter { fn pub mut next -> Option[T] { if @index < length { Option.Some(take_next) } else { Option.None } } diff --git a/libstd/src/std/bool.inko b/std/src/std/bool.inko similarity index 86% rename from libstd/src/std/bool.inko rename to std/src/std/bool.inko index bdf0ce7c4..80442bd52 100644 --- a/libstd/src/std/bool.inko +++ b/std/src/std/bool.inko @@ -41,20 +41,20 @@ impl ToString for Bool { } } -impl Clone for Bool { - fn pub clone -> Self { +impl Clone[Bool] for Bool { + fn pub clone -> Bool { if self { true } else { false } } } -impl Equal for Bool { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Bool] for Bool { + fn pub ==(other: ref Bool) -> Bool { _INKO.object_eq(self, other) } } -impl Compare for Bool { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Bool] for Bool { + fn pub cmp(other: ref Bool) -> Ordering { to_int.cmp(other.to_int) } } diff --git a/libstd/src/std/byte_array.inko b/std/src/std/byte_array.inko similarity index 91% rename from libstd/src/std/byte_array.inko rename to std/src/std/byte_array.inko index 800ac4159..467329afb 100644 --- a/libstd/src/std/byte_array.inko +++ b/std/src/std/byte_array.inko @@ -1,10 +1,10 @@ # Arrays of bytes +import std::array::(bounds_check) import std::clone::Clone import std::cmp::(Contains, Equal) import std::drop::Drop import std::fmt::(Format, Formatter) import std::hash::(Hash, Hasher) -import std::index::(bounds_check, Index, SetIndex) import std::io::Read import std::iter::(Bytes as BytesTrait, EOF, Enum, Iter) import std::option::Option @@ -31,12 +31,12 @@ trait pub IntoByteArray { # you're better off using the `Array` type. class builtin ByteArray { # Returns a new empty `ByteArray`. - fn pub static new -> Self { - _INKO.byte_array_allocate + fn pub static new -> ByteArray { + _INKO.byte_array_new } # Returns a new `ByteArray` created from the given `Array`. - fn pub static from_array(array: ref Array[Int]) -> Self { + fn pub static from_array(array: ref Array[Int]) -> ByteArray { let bytes = ByteArray.new array.iter.each fn (v) { bytes.push(v) } @@ -86,7 +86,7 @@ class builtin ByteArray { # # a.append(b) # a # => ByteArray.from_array([10, 20]) - fn pub mut append(other: Self) { + fn pub mut append(other: ByteArray) { _INKO.byte_array_append(self, other) } @@ -125,9 +125,10 @@ class builtin ByteArray { # # bytes.pop # => Option.None fn pub mut pop -> Option[Int] { - let val = _INKO.byte_array_pop(self) - - if _INKO.is_undefined(val) { Option.None } else { Option.Some(val) } + match _INKO.byte_array_pop(self) { + case -1 -> Option.None + case val -> Option.Some(val) + } } # Removes the value at the given index, returning the removed value. @@ -197,19 +198,57 @@ class builtin ByteArray { # # let bytes = ByteArray.from_array([10, 20]) # - # bytes.get(0) # => Option.Some(10) + # bytes.opt(0) # => Option.Some(10) # # Retrieving a non-existing byte: # # let bytes = ByteArray.from_array([10, 20]) # - # bytes.get(5) # => Option.None - fn pub get(index: Int) -> Option[Int] { + # bytes.opt(5) # => Option.None + fn pub opt(index: Int) -> Option[Int] { if index < 0 or index >= length { return Option.None } Option.Some(_INKO.byte_array_get(self, index)) } + # Returns the byte at the given index. + # + # # Panics + # + # This method panics if the index is out of bounds. + # + # # Examples + # + # Retrieving an existing byte: + # + # let bytes = ByteArray.from_array([10, 20]) + # + # bytes[0] # => 10 + fn pub get(index: Int) -> Int { + bounds_check(index, length) + _INKO.byte_array_get(self, index) + } + + # Stores a byte at the given index, then returns it. + # + # # Panics + # + # This method panics if the index is out of bounds. + # + # # Examples + # + # Setting the value of an existing index: + # + # let bytes = ByteArray.from_array([10, 20]) + # + # bytes[0] = 30 # => 30 + # bytes[0] # => 30 + fn pub mut set(index: Int, value: Int) { + bounds_check(index, length) + _INKO.byte_array_set(self, index, value) + _INKO.moved(value) + } + # Returns the number of bytes in this `ByteArray`. # # # Examples @@ -326,56 +365,14 @@ impl Drop for ByteArray { } } -impl Index[Int, Int] for ByteArray { - # Returns the byte at the given index. - # - # # Examples - # - # Retrieving an existing byte: - # - # let bytes = ByteArray.from_array([10, 20]) - # - # bytes[0] # => 10 - # - # # Panics - # - # This method panics if the index is out of bounds. - fn pub index(index: Int) -> Int { - bounds_check(index, length) - _INKO.byte_array_get(self, index) - } -} - -impl SetIndex[Int, Int] for ByteArray { - # Stores a byte at the given index, then returns it. - # - # # Examples - # - # Setting the value of an existing index: - # - # let bytes = ByteArray.from_array([10, 20]) - # - # bytes[0] = 30 # => 30 - # bytes[0] # => 30 - # - # # Panics - # - # This method panics if the index is out of bounds. - fn pub mut set_index(index: Int, value: Int) { - bounds_check(index, length) - _INKO.byte_array_set(self, index, value) - _INKO.moved(value) - } -} - impl ToByteArray for ByteArray { - fn pub to_byte_array -> Self { + fn pub to_byte_array -> ByteArray { clone } } impl IntoByteArray for ByteArray { - fn pub move into_byte_array -> Self { + fn pub move into_byte_array -> ByteArray { self } } @@ -404,7 +401,7 @@ impl IntoString for ByteArray { } } -impl Equal for ByteArray { +impl Equal[ByteArray] for ByteArray { # Returns `True` if two `ByteArray` objects are equal to each other. # # Two `ByteArray` objects are considered equal if they have the exact same @@ -417,12 +414,12 @@ impl Equal for ByteArray { # ByteArray.from_array([10]) == ByteArray.from_array([10]) # => True # ByteArray.from_array([10]) == ByteArray.from_array([20]) # => False fn pub ==(other: ref ByteArray) -> Bool { - _INKO.byte_array_equals(self, other) + _INKO.byte_array_eq(self, other) } } -impl Clone for ByteArray { - fn pub clone -> Self { +impl Clone[ByteArray] for ByteArray { + fn pub clone -> ByteArray { _INKO.byte_array_clone(self) } } @@ -469,7 +466,7 @@ class pub Bytes { let @index: Int } -impl Iter[Int, Never] for Bytes { +impl Iter[Int] for Bytes { fn pub mut next -> Option[Int] { match next_byte { case EOF -> Option.None @@ -478,7 +475,7 @@ impl Iter[Int, Never] for Bytes { } } -impl BytesTrait[Never] for Bytes { +impl BytesTrait for Bytes { fn pub mut next_byte -> Int { if @index < @bytes.length { _INKO.byte_array_get(@bytes, @index := @index + 1) @@ -489,7 +486,7 @@ impl BytesTrait[Never] for Bytes { } impl Read for Bytes { - fn pub mut read(into: mut ByteArray, size: Int) -> Int { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Never] { let mut read = 0 while read < size { @@ -502,10 +499,10 @@ impl Read for Bytes { } } - read + Result.Ok(read) } - fn pub mut read_all(bytes: mut ByteArray) -> Int { + fn pub mut read_all(bytes: mut ByteArray) -> Result[Int, Never] { read(into: bytes, size: @bytes.length - @index) } } diff --git a/std/src/std/channel.inko b/std/src/std/channel.inko new file mode 100644 index 000000000..9dece085f --- /dev/null +++ b/std/src/std/channel.inko @@ -0,0 +1,153 @@ +# A multi-producer, multi-consumer FIFO queue. +import std::clone::Clone +import std::drop::Drop +import std::time::Instant + +# Blocks the current process until one or more of the given channels have a +# message. +# +# This method simply returns once one or more channels have a message sent to +# them. As a channel can have multiple consumers, it's possible that once this +# method returns all the specified channels are empty again. As such, it's best +# to use this method in a loop like so: +# +# loop { +# channels.iter.each fn (chan) { +# match chan.try_receive { +# case Some(val) -> ... +# case _ -> {} +# } +# } +# +# wait(channels) +# } +# +# This method is intended to be used when you have a small number of channels, +# you want to wait until one or more have a message (without busy polling), and +# you don't care which channels receive a message. +# +# # Performance +# +# This method may need to iterate over the list of channels multiple times. As +# such it's best to only use a small number of channels at a time. If you find +# yourself in need of having to wait for many (e.g. hundreds) of channels at a +# time, you should probably refactor your code in some way (e.g. receive from an +# auxillary channel instead). +# +# # Examples +# +# import std::channel::(wait) +# +# let chan1 = Channel.new(1) +# let chan2 = Channel.new(2) +# +# chan2.send(1) +# wait([chan1, chan2]) +fn pub wait[T](channels: ref Array[Channel[T]]) { + _INKO.channel_wait(channels) +} + +# A multi-producer, multi-consumer FIFO queue. +# +# Channels allow for multiple producers and consumers, uses FIFO ordering, and +# are bounded. When sending a message to a channel that's full, the sending +# process is blocked until space becomes available. +# +# Channels use atomic reference counting and are dropped (along with any pending +# messages) when the last reference to the channel is dropped. Channels are +# treated as value types and are sendable to other processes without the need +# for the `recover` expression. +class builtin Channel[T] { + # Returns a new channel that can store the given number of messages. + # + # If you specify a value less than 1, the capacity is set to 1. + fn pub static new(capacity: Int) -> Channel[uni T] { + _INKO.channel_new(capacity) as Channel[uni T] + } + + # Sends a message to the channel. + # + # If the channel is full, the current process is blocked until space is + # available in the channel. + # + # # Examples + # + # let chan = Channel.new(4) + # + # chan.send(1) + # chan.send(2) + fn pub send(value: uni T) { + _INKO.channel_send(self, value) + _INKO.moved(value) + } + + # Receives a message from the channel. + # + # This method blocks the current process until a message is delivered. + # + # # Examples + # + # let chan = Channel.new(1) + # + # chan.send(1) + # chan.receive # => 1 + fn pub receive -> uni T { + _INKO.channel_receive(self) as uni T + } + + # Receives a message from the channel without blocking the sender. + # + # If a message is availabe, it's returned as a `Some`, otherwise a `None` is + # returned. + # + # # Examples + # + # let chan = Channel.new(1) + # + # chan.try_receive # => Option.None + # chan.send(1) + # chan.try_receive # => Option.Some(1) + fn pub try_receive -> Option[uni T] { + match _INKO.channel_try_receive(self) { + case { @tag = 0, @value = v } -> Option.Some(v as uni T) + case _ -> Option.None + } + } + + # Receives a message, returning a `None` if no message is received when the + # deadline is met. + # + # import std::time::Instant + # import std::time::Duration + # + # let duration = Duration.from_secs(1) + # let chan = Channel.new(1) + # + # chan.receive_until(deadline: Instant.new + duration) # => Option.None + # chan.send(1) + # chan.receive_until(deadline: Instant.new + duration) # => Option.Some(1) + fn pub receive_until(deadline: ref Instant) -> Option[uni T] { + match _INKO.channel_receive_until(self, deadline.to_int) { + case { @tag = 0, @value = v } -> Option.Some(v as uni T) + case _ -> Option.None + } + } +} + +impl Clone[Channel[T]] for Channel { + fn pub clone -> Channel[T] { + self + } +} + +impl Drop for Channel { + fn mut drop { + loop { + match _INKO.channel_try_receive(self) { + # The value is dropped at the end of this scope. + case { @tag = 0, @value = v } -> v as T + case _ -> break + } + } + } +} diff --git a/std/src/std/clone.inko b/std/src/std/clone.inko new file mode 100644 index 000000000..dffaf13de --- /dev/null +++ b/std/src/std/clone.inko @@ -0,0 +1,7 @@ +# Cloning of objects. + +# A type that can be cloned into a new type. +trait pub Clone[T] { + # Creates a clone of `self`. + fn pub clone -> T +} diff --git a/libstd/src/std/cmp.inko b/std/src/std/cmp.inko similarity index 85% rename from libstd/src/std/cmp.inko rename to std/src/std/cmp.inko index 80bfe516c..c81619c00 100644 --- a/libstd/src/std/cmp.inko +++ b/std/src/std/cmp.inko @@ -9,8 +9,8 @@ class pub enum Ordering { case Greater } -impl Equal for Ordering { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Ordering] for Ordering { + fn pub ==(other: ref Ordering) -> Bool { match self { case Less -> match other { case Less -> true @@ -39,7 +39,7 @@ impl Format for Ordering { } # A type that can be compared to another type for a sort-order. -trait pub Compare { +trait pub Compare[T] { # Returns the ordering between `self` and the given argument. # # The returned value should be as follows: @@ -49,10 +49,10 @@ trait pub Compare { # | `a == b` | `Ordering.Equal` # | `a > b` | `Ordering.Greater` # | `a < b` | `Ordering.Less` - fn pub cmp(other: ref Self) -> Ordering + fn pub cmp(other: ref T) -> Ordering # Returns `true` if `self` is lower than the given argument. - fn pub <(other: ref Self) -> Bool { + fn pub <(other: ref T) -> Bool { match cmp(other) { case Less -> true case _ -> false @@ -60,7 +60,7 @@ trait pub Compare { } # Returns `true` if `self` is lower than or equal to the given argument. - fn pub <=(other: ref Self) -> Bool { + fn pub <=(other: ref T) -> Bool { match cmp(other) { case Less or Equal -> true case _ -> false @@ -68,7 +68,7 @@ trait pub Compare { } # Returns `true` if `self` is greater than the given argument. - fn pub >(other: ref Self) -> Bool { + fn pub >(other: ref T) -> Bool { match cmp(other) { case Greater -> true case _ -> false @@ -76,7 +76,7 @@ trait pub Compare { } # Returns `true` if `self` is equal to or greater than the given argument. - fn pub >=(other: ref Self) -> Bool { + fn pub >=(other: ref T) -> Bool { match cmp(other) { case Greater or Equal -> true case _ -> false @@ -85,7 +85,7 @@ trait pub Compare { } # A type that can be compared for equality. -trait pub Equal { +trait pub Equal[T] { # Returns `True` if `self` and the given object are equal to each other. # # This operator is used to perform structural equality. This means two objects @@ -93,10 +93,10 @@ trait pub Equal { # their structure is equal. For example, two different arrays may be # considered to have structural equality if they contain the exact same # values. - fn pub ==(other: ref Self) -> Bool + fn pub ==(other: ref T) -> Bool # Returns `True` if `self` and the given object are not equal to each other. - fn pub !=(other: ref Self) -> Bool { + fn pub !=(other: ref T) -> Bool { (self == other).false? } } @@ -114,7 +114,7 @@ trait pub Contains[T] { # import std::cmp::(min) # # min(10, 5) # => 5 -fn pub min[T: Compare](a: T, b: T) -> T { +fn pub min[T: Compare[T]](a: T, b: T) -> T { if a <= b { a } else { b } } @@ -125,6 +125,6 @@ fn pub min[T: Compare](a: T, b: T) -> T { # import std::cmp::(max) # # max(10, 5) # => 10 -fn pub max[T: Compare](a: T, b: T) -> T { +fn pub max[T: Compare[T]](a: T, b: T) -> T { if a >= b { a } else { b } } diff --git a/libstd/src/std/crypto/chacha.inko b/std/src/std/crypto/chacha.inko similarity index 87% rename from libstd/src/std/crypto/chacha.inko rename to std/src/std/crypto/chacha.inko index 169216073..526a88eb2 100644 --- a/libstd/src/std/crypto/chacha.inko +++ b/std/src/std/crypto/chacha.inko @@ -9,7 +9,6 @@ import std::crypto::cipher::Cipher import std::crypto::hash::(Hash, Hasher) import std::crypto::math::(rotate_left_u32, to_u32) import std::endian::little -import std::index::(Index, SetIndex) # The ChaCha key size in bytes. let KEY_SIZE = 256 / 8 @@ -83,14 +82,14 @@ fn pub hchacha20(key: ref ByteArray, nonce: ref ByteArray) -> ByteArray { } matrix.perform_rounds - little.write_u32(matrix.words[0], into: out, at: 0) - little.write_u32(matrix.words[1], into: out, at: 4) - little.write_u32(matrix.words[2], into: out, at: 8) - little.write_u32(matrix.words[3], into: out, at: 12) - little.write_u32(matrix.words[12], into: out, at: 16) - little.write_u32(matrix.words[13], into: out, at: 20) - little.write_u32(matrix.words[14], into: out, at: 24) - little.write_u32(matrix.words[15], into: out, at: 28) + little.write_u32(matrix.words.get(0), into: out, at: 0) + little.write_u32(matrix.words.get(1), into: out, at: 4) + little.write_u32(matrix.words.get(2), into: out, at: 8) + little.write_u32(matrix.words.get(3), into: out, at: 12) + little.write_u32(matrix.words.get(12), into: out, at: 16) + little.write_u32(matrix.words.get(13), into: out, at: 20) + little.write_u32(matrix.words.get(14), into: out, at: 24) + little.write_u32(matrix.words.get(15), into: out, at: 28) out } @@ -100,17 +99,17 @@ class Matrix { fn mut quarter_round(a: Int, b: Int, c: Int, d: Int) { let words = @words - words[a] = to_u32(words[a].wrapping_add(words[b])) - words[d] = rotate_left_u32(words[d] ^ words[a], 16) + words.set(a, to_u32(words.get(a).wrapping_add(words.get(b)))) + words.set(d, rotate_left_u32(words.get(d) ^ words.get(a), 16)) - words[c] = to_u32(words[c].wrapping_add(words[d])) - words[b] = rotate_left_u32(words[b] ^ words[c], 12) + words.set(c, to_u32(words.get(c).wrapping_add(words.get(d)))) + words.set(b, rotate_left_u32(words.get(b) ^ words.get(c), 12)) - words[a] = to_u32(words[a].wrapping_add(words[b])) - words[d] = rotate_left_u32(words[d] ^ words[a], 8) + words.set(a, to_u32(words.get(a).wrapping_add(words.get(b)))) + words.set(d, rotate_left_u32(words.get(d) ^ words.get(a), 8)) - words[c] = to_u32(words[c].wrapping_add(words[d])) - words[b] = rotate_left_u32(words[b] ^ words[c], 7) + words.set(c, to_u32(words.get(c).wrapping_add(words.get(d)))) + words.set(b, rotate_left_u32(words.get(b) ^ words.get(c), 7)) } fn mut perform_rounds { @@ -132,17 +131,13 @@ class Matrix { fn length -> Int { @words.length } -} -impl Index[Int, Int] for Matrix { - fn pub index(index: Int) -> Int { - @words[index] + fn get(index: Int) -> Int { + @words.get(index) } -} -impl SetIndex[Int, Int] for Matrix { - fn pub mut set_index(index: Int, value: Int) { - @words[index] = value + fn mut set(index: Int, value: Int) { + @words.set(index, value) } } @@ -237,7 +232,7 @@ class pub ChaCha20 { # environments you must ensure that the nonce is never reused for the same # key. Simply generating a random nonce isn't enough, as given enough time and # bad luck the same nonce may be produced. - fn pub static new(key: ref ByteArray, nonce: ref ByteArray) -> Self { + fn pub static new(key: ref ByteArray, nonce: ref ByteArray) -> ChaCha20 { if key.length != KEY_SIZE { panic("ChaCha20 key sizes must be exactly {KEY_SIZE} bytes") } @@ -246,7 +241,7 @@ class pub ChaCha20 { panic("ChaCha20 nonce sizes must be exactly {CHACHA_NONCE_SIZE} bytes") } - Self { + ChaCha20 { @matrix = Matrix { @words = [ 0x61707865, @@ -280,7 +275,7 @@ class pub ChaCha20 { if value < 0 or value > MAX_COUNTER { panic("ChaCha block counters must be between 0 and {MAX_COUNTER}") } else { - @matrix[12] = value + @matrix.set(12, value) } } @@ -292,17 +287,17 @@ class pub ChaCha20 { let tmp = Matrix { @words = Array.filled(with: 0, times: MATRIX_SIZE) } loop { - MATRIX_SIZE.times fn (i) { tmp[i] = @matrix[i] } + MATRIX_SIZE.times fn (i) { tmp.set(i, @matrix.get(i)) } tmp.perform_rounds MATRIX_SIZE.times fn (i) { - tmp[i] = to_u32(tmp[i].wrapping_add(@matrix[i])) - little.write_u32(tmp[i], into: buf, at: i * 4) + tmp.set(i, to_u32(tmp.get(i).wrapping_add(@matrix.get(i)))) + little.write_u32(tmp.get(i), into: buf, at: i * 4) } # This in itself can't overflow, as the Int type is a 64-bits signed # integer, and below we limit it to the range that fits in a 32-bits # unsigned integer. - let new_length = @matrix[12] + 1 + let new_length = @matrix.get(12) + 1 # The original implementation makes no attempt at protecting the user from # overflowing the counter, as it's unlikely to happen in the first place. @@ -315,14 +310,18 @@ class pub ChaCha20 { panic("The block counter overflowed after {MAX_COUNTER} blocks") } - @matrix[12] = new_length + @matrix.set(12, new_length) if len <= BLOCK_SIZE { - len.times fn (i) { out[offset + i] = input[offset + i] ^ buf[i] } + len.times fn (i) { + out.set(offset + i, input.get(offset + i) ^ buf.get(i)) + } return out } - BLOCK_SIZE.times fn (i) { out[offset + i] = input[offset + i] ^ buf[i] } + BLOCK_SIZE.times fn (i) { + out.set(offset + i, input.get(offset + i) ^ buf.get(i)) + } len -= BLOCK_SIZE offset += BLOCK_SIZE @@ -378,7 +377,7 @@ class pub XChaCha20 { # let nonce = rand.bytes(size: 24) # # XChaCha20.new(key, nonce) - fn pub static new(key: ref ByteArray, nonce: ref ByteArray) -> Self { + fn pub static new(key: ref ByteArray, nonce: ref ByteArray) -> XChaCha20 { if key.length != KEY_SIZE { panic("XChaCha20 key sizes must be exactly {KEY_SIZE} bytes") } @@ -389,7 +388,7 @@ class pub XChaCha20 { let sub_key = hchacha20(key, nonce.slice(start: 0, length: 16)) - Self { + XChaCha20 { @chacha = ChaCha20 { @matrix = Matrix { @words = [ diff --git a/libstd/src/std/crypto/cipher.inko b/std/src/std/crypto/cipher.inko similarity index 100% rename from libstd/src/std/crypto/cipher.inko rename to std/src/std/crypto/cipher.inko diff --git a/libstd/src/std/crypto/hash.inko b/std/src/std/crypto/hash.inko similarity index 76% rename from libstd/src/std/crypto/hash.inko rename to std/src/std/crypto/hash.inko index c937b8517..9c70bea76 100644 --- a/libstd/src/std/crypto/hash.inko +++ b/std/src/std/crypto/hash.inko @@ -3,7 +3,6 @@ import std::cmp::Equal import std::endian::big import std::endian::little import std::fmt::(Format, Formatter) -import std::index::(Index, SetIndex) import std::string::(StringBuffer, ToString) # The digits to use when converting a digest to a hexadecimal string. @@ -17,8 +16,8 @@ class pub Block { let @index: Int # Returns a new `Block` with the given size in bytes. - fn pub static new(size: Int) -> Self { - Self { @bytes = ByteArray.filled(with: 0, times: size), @index = 0 } + fn pub static new(size: Int) -> Block { + Block { @bytes = ByteArray.filled(with: 0, times: size), @index = 0 } } # Reads an unsigned 32-bits little-endian integer from the given index. @@ -42,7 +41,7 @@ class pub Block { let mut index = 0 while length > 0 { - @bytes[@index := @index + 1] = bytes[index] + @bytes.set(@index := @index + 1, bytes.get(index)) if @index == @bytes.length { transform.call @@ -62,18 +61,18 @@ class pub Block { let pad_to = @bytes.length - length_bytes if @index >= pad_to { - @bytes[@index := @index + 1] = 0x80 + @bytes.set(@index := @index + 1, 0x80) - while @index < @bytes.length { @bytes[@index := @index + 1] = 0 } + while @index < @bytes.length { @bytes.set(@index := @index + 1, 0) } transform.call @index = 0 - while @index < pad_to { @bytes[@index := @index + 1] = 0 } + while @index < pad_to { @bytes.set(@index := @index + 1, 0) } } else { - @bytes[@index := @index + 1] = 0x80 + @bytes.set(@index := @index + 1, 0x80) - while @index < pad_to { @bytes[@index := @index + 1] = 0 } + while @index < pad_to { @bytes.set(@index := @index + 1, 0) } } } @@ -91,17 +90,23 @@ class pub Block { fn pub block_index -> Int { @index } -} -impl Index[Int, Int] for Block { - fn pub index(index: Int) -> Int { - @bytes[index] + # Returns the byte at the given index. + # + # # Panics + # + # This method panics if the index is out of bounds. + fn pub get(index: Int) -> Int { + @bytes.get(index) } -} -impl SetIndex[Int, Int] for Block { - fn pub mut set_index(index: Int, value: Int) { - @bytes[index] = value + # Sets the byte at the given index. + # + # # Panics + # + # This method panics if the index is out of bounds. + fn pub mut set(index: Int, value: Int) { + @bytes.set(index, value) } } @@ -117,8 +122,8 @@ class pub Hash { let pub @bytes: ByteArray # Returns a new empty `Digest`. - fn pub static new(bytes: ByteArray) -> Self { - Self { @bytes = bytes} + fn pub static new(bytes: ByteArray) -> Hash { + Hash { @bytes = bytes} } } @@ -132,16 +137,16 @@ impl ToString for Hash { @bytes.iter.each_with_index fn (index, byte) { let hex_index = index * 2 - hex[hex_index] = HEX_DIGITS.byte(byte >> 4) - hex[hex_index + 1] = HEX_DIGITS.byte(byte & 0x0F) + hex.set(hex_index, HEX_DIGITS.byte(byte >> 4)) + hex.set(hex_index + 1, HEX_DIGITS.byte(byte & 0x0F)) } hex.into_string } } -impl Equal for Hash { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Hash] for Hash { + fn pub ==(other: ref Hash) -> Bool { @bytes == other.bytes } } @@ -163,5 +168,5 @@ trait pub Hasher { fn pub mut write(bytes: ref ByteArray) # Generate a hash based on the current state. - fn pub move finalize -> Hash + fn pub move finish -> Hash } diff --git a/libstd/src/std/crypto/math.inko b/std/src/std/crypto/math.inko similarity index 71% rename from libstd/src/std/crypto/math.inko rename to std/src/std/crypto/math.inko index ff9ebe667..60d31936c 100644 --- a/libstd/src/std/crypto/math.inko +++ b/std/src/std/crypto/math.inko @@ -6,7 +6,16 @@ # # This method panics if `amount` is greater than 32 or less than zero. fn pub rotate_left_u32(value: Int, amount: Int) -> Int { - to_u32((value << amount) | (value >> (32 - amount))) + to_u32((value << amount) | (value >>> (32 - amount))) +} + +# Rotates a 64-bits unsigned integer to the left. +# +# # Panics +# +# This method panics if `amount` is greater than 64 or less than zero. +fn pub rotate_left_u64(value: Int, amount: Int) -> Int { + (value << amount) | (value >>> (64 - amount)) } # Rotates a 32-bits unsigned integer to the right. @@ -24,7 +33,7 @@ fn pub rotate_right_u32(value: Int, amount: Int) -> Int { # # This method panics if `amount` is greater than 32 or less than zero. fn pub shift_right_u32(value: Int, amount: Int) -> Int { - to_u32(value >> amount) + to_u32(value >>> amount) } # Converts an `Int` to an unsigned 32-bits `Int`. diff --git a/libstd/src/std/crypto/md5.inko b/std/src/std/crypto/md5.inko similarity index 92% rename from libstd/src/std/crypto/md5.inko rename to std/src/std/crypto/md5.inko index 50ed930ef..6eb41f4d3 100644 --- a/libstd/src/std/crypto/md5.inko +++ b/std/src/std/crypto/md5.inko @@ -13,7 +13,7 @@ # let hasher = Md5.new # # hasher.write('hello'.to_byte_array) -# hasher.finalize.to_string # => '5d41402abc4b2a76b9719d911017c592' +# hasher.finish.to_string # => '5d41402abc4b2a76b9719d911017c592' # # You can also use `Md5.hash`: # @@ -104,12 +104,12 @@ class pub Md5 { let hasher = new hasher.write(bytes) - hasher.finalize + hasher.finish } # Returns a new instance of the hasher. - fn pub static new -> Self { - Self { + fn pub static new -> Md5 { + Md5 { @block = Block.new(BLOCK_SIZE), @words = Array.filled(with: 0, times: 16), @size = 0, @@ -123,7 +123,7 @@ class pub Md5 { fn mut compress { let words = @words - 16.times fn (i) { words[i] = @block.read_u32_le(i * 4) } + 16.times fn (i) { words.set(i, @block.read_u32_le(i * 4)) } let mut a = @a let mut b = @b @@ -152,10 +152,13 @@ class pub Md5 { } f = to_u32( - f.wrapping_add(a).wrapping_add(words[word]).wrapping_add(TABLE[i]) + f + .wrapping_add(a) + .wrapping_add(words.get(word)) + .wrapping_add(TABLE.get(i)) ) - let temp = to_u32(b.wrapping_add(rotate_left_u32(f, SHIFTS[i]))) + let temp = to_u32(b.wrapping_add(rotate_left_u32(f, SHIFTS.get(i)))) a = d d = c @@ -177,7 +180,7 @@ impl Hasher for Md5 { @block.write_bytes(bytes) fn { compress } } - fn pub move finalize -> Hash { + fn pub move finish -> Hash { @block.add_padding(8) fn { compress } @block.write_length_le(@size * 8, at: 56) compress diff --git a/libstd/src/std/crypto/poly1305.inko b/std/src/std/crypto/poly1305.inko similarity index 96% rename from libstd/src/std/crypto/poly1305.inko rename to std/src/std/crypto/poly1305.inko index 160ca302d..41ca58c32 100644 --- a/libstd/src/std/crypto/poly1305.inko +++ b/std/src/std/crypto/poly1305.inko @@ -69,7 +69,7 @@ class pub Poly1305 { let hasher = new(key) hasher.write(bytes) - hasher.finalize + hasher.finish } # Returns a new Poly1305 hasher. @@ -80,12 +80,12 @@ class pub Poly1305 { # # Panics # # This method panics if the key isn't exactly 32 bytes long. - fn pub static new(key: ref ByteArray) -> Self { + fn pub static new(key: ref ByteArray) -> Poly1305 { if key.length != KEY_SIZE { panic("Poly1305 keys must be exactly {KEY_SIZE} bytes") } - Self { + Poly1305 { @block = Block.new(BLOCK_SIZE), @r0 = little.read_u32(from: key, at: 0) & 0x0FFFFFFF, @r1 = little.read_u32(from: key, at: 4) & 0x0FFFFFFC, @@ -164,13 +164,13 @@ impl Hasher for Poly1305 { @block.write_bytes(bytes) fn { compress(final: false) } } - fn pub move finalize -> Hash { + fn pub move finish -> Hash { if @block.block_index > 0 { let mut i = @block.block_index - @block[i := i + 1] = 1 + @block.set(i := i + 1, 1) - while i < BLOCK_SIZE { @block[i := i + 1] = 0 } + while i < BLOCK_SIZE { @block.set(i := i + 1, 0) } compress(final: true) } diff --git a/libstd/src/std/crypto/sha1.inko b/std/src/std/crypto/sha1.inko similarity index 85% rename from libstd/src/std/crypto/sha1.inko rename to std/src/std/crypto/sha1.inko index 37593600e..ad6bf9910 100644 --- a/libstd/src/std/crypto/sha1.inko +++ b/std/src/std/crypto/sha1.inko @@ -10,7 +10,7 @@ # let hasher = Sha1.new # # hasher.write('hello'.to_byte_array) -# hasher.finalize.to_string # => 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d' +# hasher.finish.to_string # => 'aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d' # # You can also use `Sha1.hash`: # @@ -50,12 +50,12 @@ class pub Sha1 { let hasher = new hasher.write(bytes) - hasher.finalize + hasher.finish } # Returns a new instance of the hasher. - fn pub static new -> Self { - Self { + fn pub static new -> Sha1 { + Sha1 { @block = Block.new(BLOCK_SIZE), @words = Array.filled(with: 0, times: 80), @size = 0, @@ -70,12 +70,18 @@ class pub Sha1 { fn mut compress { let words = @words - 0.until(16).iter.each fn (i) { words[i] = @block.read_u32_be(i * 4) } + 0.until(16).iter.each fn (i) { words.set(i, @block.read_u32_be(i * 4)) } 16.until(80).iter.each fn (i) { - words[i] = rotate_left_u32( - words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16], - 1 + words.set( + i, + rotate_left_u32( + words.get(i - 3) + ^ words.get(i - 8) + ^ words.get(i - 14) + ^ words.get(i - 16), + 1 + ) ) } @@ -91,7 +97,7 @@ class pub Sha1 { rotate_left_u32(a, 5) .wrapping_add((b & c) | (b.not & d)) .wrapping_add(e) - .wrapping_add(words[i]) + .wrapping_add(words.get(i)) .wrapping_add(0x5A827999) ) @@ -108,7 +114,7 @@ class pub Sha1 { rotate_left_u32(a, 5) .wrapping_add(b ^ c ^ d) .wrapping_add(e) - .wrapping_add(words[i]) + .wrapping_add(words.get(i)) .wrapping_add(0x6ED9EBA1) ) @@ -125,7 +131,7 @@ class pub Sha1 { rotate_left_u32(a, 5) .wrapping_add((b & c) | (b & d) | (c & d)) .wrapping_add(e) - .wrapping_add(words[i]) + .wrapping_add(words.get(i)) .wrapping_add(0x8F1BBCDC) ) @@ -142,7 +148,7 @@ class pub Sha1 { rotate_left_u32(a, 5) .wrapping_add(b ^ c ^ d) .wrapping_add(e) - .wrapping_add(words[i]) + .wrapping_add(words.get(i)) .wrapping_add(0xCA62C1D6) ) @@ -168,7 +174,7 @@ impl Hasher for Sha1 { @block.write_bytes(bytes) fn { compress } } - fn pub move finalize -> Hash { + fn pub move finish -> Hash { @block.add_padding(8) fn { compress } @block.write_length_be(@size * 8, at: 56) compress diff --git a/libstd/src/std/crypto/sha2.inko b/std/src/std/crypto/sha2.inko similarity index 90% rename from libstd/src/std/crypto/sha2.inko rename to std/src/std/crypto/sha2.inko index 0bfd84a48..e5610bc08 100644 --- a/libstd/src/std/crypto/sha2.inko +++ b/std/src/std/crypto/sha2.inko @@ -7,7 +7,7 @@ # let hasher = Sha256.new # # hasher.write('hello'.to_byte_array) -# hasher.finalize.to_string # => '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824' +# hasher.finish.to_string # => '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824' # # Or when using the `hash` static method: # @@ -102,12 +102,12 @@ class pub Sha256 { let hasher = new hasher.write(bytes) - hasher.finalize + hasher.finish } # Returns a new instance of the hasher. - fn pub static new -> Self { - Self { + fn pub static new -> Sha256 { + Sha256 { @block = Block.new(SHA256_BLOCK_SIZE), @words = Array.filled(with: 0, times: 64), @size = 0, @@ -125,11 +125,11 @@ class pub Sha256 { fn mut compress { let words = @words - 0.until(16).iter.each fn (i) { words[i] = @block.read_u32_be(i * 4) } + 0.until(16).iter.each fn (i) { words.set(i, @block.read_u32_be(i * 4)) } 16.until(64).iter.each fn (i) { - let w15 = words[i - 15] - let w2 = words[i - 2] + let w15 = words.get(i - 15) + let w2 = words.get(i - 2) let s0 = to_u32( rotate_right_u32(w15, 7) ^ rotate_right_u32(w15, 18) @@ -142,11 +142,14 @@ class pub Sha256 { ^ shift_right_u32(w2, 10) ) - words[i] = to_u32( - words[i - 16] - .wrapping_add(s0) - .wrapping_add(words[i - 7]) - .wrapping_add(s1) + words.set( + i, + to_u32( + words.get(i - 16) + .wrapping_add(s0) + .wrapping_add(words.get(i - 7)) + .wrapping_add(s1) + ) ) } @@ -179,8 +182,8 @@ class pub Sha256 { h .wrapping_add(s1) .wrapping_add(ch) - .wrapping_add(SHA256_TABLE[i]) - .wrapping_add(words[i]) + .wrapping_add(SHA256_TABLE.get(i)) + .wrapping_add(words.get(i)) ) let temp2 = to_u32(s0.wrapping_add(maj)) @@ -212,7 +215,7 @@ impl Hasher for Sha256 { @block.write_bytes(bytes) fn { compress } } - fn pub move finalize -> Hash { + fn pub move finish -> Hash { @block.add_padding(8) fn { compress } @block.write_length_be(@size * 8, at: 56) compress @@ -256,12 +259,12 @@ class pub Sha512 { let hasher = new hasher.write(bytes) - hasher.finalize + hasher.finish } # Returns a new instance of the hasher. - fn pub static new -> Self { - Self { + fn pub static new -> Sha512 { + Sha512 { @block = Block.new(SHA512_BLOCK_SIZE), @words = Array.filled(with: 0, times: 80), @size = 0, @@ -279,18 +282,21 @@ class pub Sha512 { fn mut compress { let words = @words - 0.until(16).iter.each fn (i) { words[i] = @block.read_i64_be(i * 8) } + 0.until(16).iter.each fn (i) { words.set(i, @block.read_i64_be(i * 8)) } 16.until(80).iter.each fn (i) { - let w15 = words[i - 15] - let w2 = words[i - 2] + let w15 = words.get(i - 15) + let w2 = words.get(i - 2) let s0 = w15.rotate_right(1) ^ w15.rotate_right(8) ^ (w15 >>> 7) let s1 = w2.rotate_right(19) ^ w2.rotate_right(61) ^ (w2 >>> 6) - words[i] = words[i - 16] - .wrapping_add(s0) - .wrapping_add(words[i - 7]) - .wrapping_add(s1) + words.set( + i, + words.get(i - 16) + .wrapping_add(s0) + .wrapping_add(words.get(i - 7)) + .wrapping_add(s1) + ) } let mut a = @a @@ -311,8 +317,8 @@ class pub Sha512 { let temp1 = h .wrapping_add(s1) .wrapping_add(ch) - .wrapping_add(SHA512_TABLE[i]) - .wrapping_add(words[i]) + .wrapping_add(SHA512_TABLE.get(i)) + .wrapping_add(words.get(i)) let temp2 = s0.wrapping_add(maj) @@ -344,7 +350,7 @@ impl Hasher for Sha512 { @block.write_bytes(bytes) fn { compress } } - fn pub move finalize -> Hash { + fn pub move finish -> Hash { @block.add_padding(16) fn { compress } # SHA512 wants a 128-bits length, but we internally maintain a 64-bits diff --git a/libstd/src/std/debug.inko b/std/src/std/debug.inko similarity index 61% rename from libstd/src/std/debug.inko rename to std/src/std/debug.inko index f19af9809..7cbf109b8 100644 --- a/libstd/src/std/debug.inko +++ b/std/src/std/debug.inko @@ -16,14 +16,14 @@ class pub StackFrame { # The line number the stack frame originates from. let pub @line: Int - fn pub static new(path: Path, name: String, line: Int) -> Self { - Self { @path = path, @name = name, @line = line } + fn pub static new(path: Path, name: String, line: Int) -> StackFrame { + StackFrame { @path = path, @name = name, @line = line } } } -impl Clone for StackFrame { - fn pub clone -> Self { - Self { @path = @path.clone, @name = @name, @line = @line } +impl Clone[StackFrame] for StackFrame { + fn pub clone -> StackFrame { + StackFrame { @path = @path.clone, @name = @name, @line = @line } } } @@ -47,24 +47,27 @@ impl Clone for StackFrame { # # fn second { # let frames = stacktrace(skip: 1) -# let frame = frames[frames.length - 1] +# let frame = frames.get(frames.length - 1) # # frame.name # => 'second' # } fn pub stacktrace(skip: Int) -> Array[StackFrame] { - let trace = _INKO.process_stacktrace(skip) - let length = _INKO.process_stacktrace_length(trace) + let trace = _INKO.process_stacktrace + let len = _INKO.process_stacktrace_length(trace) + let max = len - skip let mut index = 0 - let frames = Array.with_capacity(length) - while index < length { - let path = Path.new(_INKO.process_call_frame_path(trace, index)) - let name = _INKO.process_call_frame_name(trace, index) - let line = _INKO.process_call_frame_line(trace, index) + if max <= 0 { return [] } - frames.push(StackFrame.new(path, name, line)) + let frames = Array.with_capacity(max) - index += 1 + while index < max { + let path = Path.new(_INKO.process_stack_frame_path(trace, index)) + let name = _INKO.process_stack_frame_name(trace, index) + let line = _INKO.process_stack_frame_line(trace, index) + + frames.push(StackFrame.new(path, name, line)) + index += 1 } _INKO.process_stacktrace_drop(trace) diff --git a/libstd/src/std/drop.inko b/std/src/std/drop.inko similarity index 100% rename from libstd/src/std/drop.inko rename to std/src/std/drop.inko diff --git a/libstd/src/std/endian/big.inko b/std/src/std/endian/big.inko similarity index 72% rename from libstd/src/std/endian/big.inko rename to std/src/std/endian/big.inko index f2677c46a..4b75e109e 100644 --- a/libstd/src/std/endian/big.inko +++ b/std/src/std/endian/big.inko @@ -15,10 +15,10 @@ # big.write_u32(123456789, into: bytes, at: 0) # bytes # => ByteArray.from_array([7, 91, 205, 21]) fn pub write_u32(value: Int, into: mut ByteArray, at: Int) { - into[at] = value >> 24 - into[at + 1] = value >> 16 - into[at + 2] = value >> 8 - into[at + 3] = value + into.set(at, value >> 24) + into.set(at + 1, value >> 16) + into.set(at + 2, value >> 8) + into.set(at + 3, value) } # Writes a value interpreted as a 64-bits signed integer into `into` as a series @@ -33,14 +33,14 @@ fn pub write_u32(value: Int, into: mut ByteArray, at: Int) { # big.write_i64(123456789, into: bytes, at: 0) # bytes # => ByteArray.from_array([7, 91, 205, 21]) fn pub write_i64(value: Int, into: mut ByteArray, at: Int) { - into[at] = value >> 56 - into[at + 1] = value >> 48 - into[at + 2] = value >> 40 - into[at + 3] = value >> 32 - into[at + 4] = value >> 24 - into[at + 5] = value >> 16 - into[at + 6] = value >> 8 - into[at + 7] = value + into.set(at, value >> 56) + into.set(at + 1, value >> 48) + into.set(at + 2, value >> 40) + into.set(at + 3, value >> 32) + into.set(at + 4, value >> 24) + into.set(at + 5, value >> 16) + into.set(at + 6, value >> 8) + into.set(at + 7, value) } # Reads four bytes starting at `at` as a 32-bits signed integer. @@ -59,7 +59,10 @@ fn pub write_i64(value: Int, into: mut ByteArray, at: Int) { # big.write_u32(123456789, into: bytes, at: 0) # big.read_u32(from: bytes, at: 0) # => 123456789 fn pub read_u32(from: ref ByteArray, at: Int) -> Int { - (from[at] << 24) | (from[at + 1] << 16) | (from[at + 2] << 8) | from[at + 3] + (from.get(at) << 24) + | (from.get(at + 1) << 16) + | (from.get(at + 2) << 8) + | from.get(at + 3) } # Reads eight bytes starting at `at` as a 64-bits signed integer. @@ -78,12 +81,12 @@ fn pub read_u32(from: ref ByteArray, at: Int) -> Int { # big.write_i64(123456789, into: bytes, at: 0) # big.read_i64(from: bytes, at: 0) # => 123456789 fn pub read_i64(from: ref ByteArray, at: Int) -> Int { - (from[at] << 56) - | (from[at + 1] << 48) - | (from[at + 2] << 40) - | (from[at + 3] << 32) - | (from[at + 4] << 24) - | (from[at + 5] << 16) - | (from[at + 6] << 8) - | from[at + 7] + (from.get(at) << 56) + | (from.get(at + 1) << 48) + | (from.get(at + 2) << 40) + | (from.get(at + 3) << 32) + | (from.get(at + 4) << 24) + | (from.get(at + 5) << 16) + | (from.get(at + 6) << 8) + | from.get(at + 7) } diff --git a/libstd/src/std/endian/little.inko b/std/src/std/endian/little.inko similarity index 73% rename from libstd/src/std/endian/little.inko rename to std/src/std/endian/little.inko index 804eab034..45773135c 100644 --- a/libstd/src/std/endian/little.inko +++ b/std/src/std/endian/little.inko @@ -15,10 +15,10 @@ # little.write_u32(123456789, into: bytes, at: 0) # bytes # => ByteArray.from_array([21, 205, 91, 7]) fn pub write_u32(value: Int, into: mut ByteArray, at: Int) { - into[at] = value - into[at + 1] = value >> 8 - into[at + 2] = value >> 16 - into[at + 3] = value >> 24 + into.set(at, value) + into.set(at + 1, value >> 8) + into.set(at + 2, value >> 16) + into.set(at + 3, value >> 24) } # Writes a value interpreted as a 64-bits signed integer into `into` as a series @@ -33,14 +33,14 @@ fn pub write_u32(value: Int, into: mut ByteArray, at: Int) { # little.write_i64(123456789, into: bytes, at: 0) # bytes # => ByteArray.from_array([21, 205, 91, 7]) fn pub write_i64(value: Int, into: mut ByteArray, at: Int) { - into[at] = value - into[at + 1] = value >> 8 - into[at + 2] = value >> 16 - into[at + 3] = value >> 24 - into[at + 4] = value >> 32 - into[at + 5] = value >> 40 - into[at + 6] = value >> 48 - into[at + 7] = value >> 56 + into.set(at, value) + into.set(at + 1, value >> 8) + into.set(at + 2, value >> 16) + into.set(at + 3, value >> 24) + into.set(at + 4, value >> 32) + into.set(at + 5, value >> 40) + into.set(at + 6, value >> 48) + into.set(at + 7, value >> 56) } # Reads four bytes starting at `at` as a 32-bits signed integer. @@ -59,7 +59,10 @@ fn pub write_i64(value: Int, into: mut ByteArray, at: Int) { # little.write_u32(123456789, into: bytes, at: 0) # little.read_u32(from: bytes, at: 0) # => 123456789 fn pub read_u32(from: ref ByteArray, at: Int) -> Int { - (from[at + 3] << 24) | (from[at + 2] << 16) | (from[at + 1] << 8) | from[at] + (from.get(at + 3) << 24) + | (from.get(at + 2) << 16) + | (from.get(at + 1) << 8) + | from.get(at) } # Reads eight bytes starting at `at` as a 64-bits signed integer. @@ -78,12 +81,12 @@ fn pub read_u32(from: ref ByteArray, at: Int) -> Int { # little.write_i64(123456789, into: bytes, at: 0) # little.read_i64(from: bytes, at: 0) # => 123456789 fn pub read_i64(from: ref ByteArray, at: Int) -> Int { - (from[at + 7] << 56) - | (from[at + 6] << 48) - | (from[at + 5] << 40) - | (from[at + 4] << 32) - | (from[at + 3] << 24) - | (from[at + 2] << 16) - | (from[at + 1] << 8) - | from[at] + (from.get(at + 7) << 56) + | (from.get(at + 6) << 48) + | (from.get(at + 5) << 40) + | (from.get(at + 4) << 32) + | (from.get(at + 3) << 24) + | (from.get(at + 2) << 16) + | (from.get(at + 1) << 8) + | from.get(at) } diff --git a/libstd/src/std/env.inko b/std/src/std/env.inko similarity index 56% rename from libstd/src/std/env.inko rename to std/src/std/env.inko index f65029b3f..9148f45b3 100644 --- a/libstd/src/std/env.inko +++ b/std/src/std/env.inko @@ -20,8 +20,18 @@ # - https://github.com/rustsec/advisory-db/issues/926 import std::fs::path::Path import std::io::Error +import std::string::ToString -# Returns the value of an environment variable. +# The architecture of the CPU the code is compiled for. +let pub ARCH = _INKO_ARCH + +# The operating system the code is compiled for. +let pub OS = _INKO_OS + +# The ABI of the operating system the code is compiled for. +let pub ABI = _INKO_ABI + +# Returns an optional value of an environment variable. # # # Examples # @@ -29,11 +39,12 @@ import std::io::Error # # import std::env # -# env.get('HOME') # => '/home/alice' -fn pub get(name: String) -> Option[String] { - let val = _INKO.env_get(name) - - if _INKO.is_undefined(val) { Option.None } else { Option.Some(val) } +# env.opt('HOME') # => Option.Some('/home/alice') +fn pub opt(name: String) -> Option[String] { + match _INKO.env_get(name) { + case { @tag = 0, @value = val } -> Option.Some(val as String) + case _ -> Option.None + } } # Returns all defined environment variables and their values. @@ -44,12 +55,14 @@ fn pub get(name: String) -> Option[String] { # # import std::env # -# env.variables # => Map { 'HOME': '/home/alice', ... } +# env.variables.opt('HOME') # => Option.Some('/home/alice') fn pub variables -> Map[String, String] { - _INKO.env_variables.into_iter.reduce(Map.new) fn (map, name) { - match get(name) { + let vars = _INKO.env_variables as Array[String] + + vars.into_iter.reduce(Map.new) fn (map, name) { + match opt(name) { case Some(v) -> { - map[name] = v + map.set(name, v) map } case None -> map @@ -65,11 +78,12 @@ fn pub variables -> Map[String, String] { # # import std::env # -# env.home_directory # => '/home/alice' +# env.home_directory # => Option.Some('/home/alice') fn pub home_directory -> Option[Path] { - let dir = _INKO.env_home_directory - - if _INKO.is_undefined(dir) { Option.None } else { Option.Some(dir.into_path) } + match _INKO.env_home_directory { + case { @tag = 0, @value = val } -> Option.Some(Path.new(val as String)) + case _ -> Option.None + } } # Returns the path to the temporary directory. @@ -87,7 +101,7 @@ fn pub temporary_directory -> Path { # Returns the current working directory. # -# This method will throw if the directory could not be obtained. Possible +# This method will return an `Error` if we failed to get the directory. Possible # causes for this could be: # # 1. The directory no longer exists. @@ -99,30 +113,27 @@ fn pub temporary_directory -> Path { # # import std::env # -# try! env.working_directory # => '/home/alice/example' -fn pub working_directory !! Error -> Path { - let path = - try _INKO.env_get_working_directory else (e) throw Error.from_int(e) - - path.into_path +# env.working_directory # => Result.Ok('/home/alice/example') +fn pub working_directory -> Result[Path, Error] { + match _INKO.env_get_working_directory { + case { @tag = 0, @value = val } -> Result.Ok(Path.new(val as String)) + case { @tag = _, @value = err } -> Result.Error(Error.from_int(err as Int)) + } } # Changes the current working directory to the given directory. # -# This method throws if the directory couldn't be changed. -# # # Examples # # Changing the current working directory: # # import std::env # -# try! env.working_directory = '..' -fn pub working_directory=(directory: String) !! Error { - try { - _INKO.env_set_working_directory(directory) - } else (e) { - throw Error.from_int(e) +# env.working_directory = '..' +fn pub working_directory=(directory: ref ToString) -> Result[Nil, Error] { + match _INKO.env_set_working_directory(directory.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = err } -> Result.Error(Error.from_int(err as Int)) } } @@ -136,15 +147,16 @@ fn pub working_directory=(directory: String) !! Error { # # Assuming this program is executed using `inko foo.inko first second`: # env.arguments # => ['first', 'second'] fn pub arguments -> Array[String] { - _INKO.env_arguments + _INKO.env_arguments as Array[String] } # Returns the path to the current executable. # # If the program is executed through a symbolic link, the returned path may # point to the symbolic link instead of the executable the link points to. -fn pub executable !! Error -> Path { - let path = try _INKO.env_executable else (e) throw Error.from_int(e) - - path.into_path +fn pub executable -> Result[Path, Error] { + match _INKO.env_executable { + case { @tag = 0, @value = val } -> Result.Ok(Path.new(val as String)) + case { @tag = _, @value = err } -> Result.Error(Error.from_int(err as Int)) + } } diff --git a/libstd/src/std/float.inko b/std/src/std/float.inko similarity index 83% rename from libstd/src/std/float.inko rename to std/src/std/float.inko index 982dbec43..7dcd465bf 100644 --- a/libstd/src/std/float.inko +++ b/std/src/std/float.inko @@ -60,10 +60,11 @@ class builtin Float { # Parsing a `Float` with an exponent: # # Float.parse('1.2e1') # => Option.Some(12.0) - fn pub static parse(string: String) -> Option[Self] { - let res = _INKO.string_to_float(string, -1, -1) - - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + fn pub static parse(string: String) -> Option[Float] { + match _INKO.string_to_float(string, -1, -1) { + case { @tag = 0, @value = v } -> Option.Some(v as Float) + case _ -> Option.None + } } # Returns the absolute value of `self`. @@ -72,7 +73,7 @@ class builtin Float { # # 42.0.absolute # => 42 # -42.0.absolute # => 42 - fn pub absolute -> Self { + fn pub absolute -> Float { Float.from_bits(to_bits & MAX) } @@ -82,7 +83,7 @@ class builtin Float { # # 42.0.opposite # => -42 # -42.0.opposite # => 42 - fn pub opposite -> Self { + fn pub opposite -> Float { Float.from_bits(to_bits ^ MIN) } @@ -201,10 +202,6 @@ class builtin Float { } impl ToInt for Float { - # Converts a `Float` to an `Int`. - # - # The `Float` will be rounded towards zero. Converting a `NAN`, `INFINITY`, - # or `NEGATIVE_INFINITY` to an `Int` will result in a panic. fn pub to_int -> Int { _INKO.float_to_int(self) } @@ -216,44 +213,44 @@ impl ToFloat for Float { } } -impl Clone for Float { - fn pub clone -> Self { - _INKO.float_clone(self) +impl Clone[Float] for Float { + fn pub clone -> Float { + self } } -impl Add[Float] for Float { - fn pub +(other: ref Self) -> Self { +impl Add[Float, Float] for Float { + fn pub +(other: ref Float) -> Float { _INKO.float_add(self, other) } } -impl Subtract[Float] for Float { - fn pub -(other: ref Self) -> Self { +impl Subtract[Float, Float] for Float { + fn pub -(other: ref Float) -> Float { _INKO.float_sub(self, other) } } -impl Divide for Float { - fn pub /(other: ref Self) -> Self { +impl Divide[Float, Float] for Float { + fn pub /(other: ref Float) -> Float { _INKO.float_div(self, other) } } -impl Multiply for Float { - fn pub *(other: ref Self) -> Self { +impl Multiply[Float, Float] for Float { + fn pub *(other: ref Float) -> Float { _INKO.float_mul(self, other) } } -impl Modulo for Float { - fn pub %(other: ref Self) -> Self { +impl Modulo[Float, Float] for Float { + fn pub %(other: ref Float) -> Float { _INKO.float_mod(self, other) } } -impl Compare for Float { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Float] for Float { + fn pub cmp(other: ref Float) -> Ordering { if self > other { Ordering.Greater } else if self < other { @@ -263,25 +260,25 @@ impl Compare for Float { } } - fn pub <(other: ref Self) -> Bool { + fn pub <(other: ref Float) -> Bool { _INKO.float_lt(self, other) } - fn pub <=(other: ref Self) -> Bool { + fn pub <=(other: ref Float) -> Bool { _INKO.float_le(self, other) } - fn pub >(other: ref Self) -> Bool { + fn pub >(other: ref Float) -> Bool { _INKO.float_gt(self, other) } - fn pub >=(other: ref Self) -> Bool { + fn pub >=(other: ref Float) -> Bool { _INKO.float_ge(self, other) } } -impl Equal for Float { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Float] for Float { + fn pub ==(other: ref Float) -> Bool { _INKO.float_eq(self, other) } } diff --git a/libstd/src/std/fmt.inko b/std/src/std/fmt.inko similarity index 95% rename from libstd/src/std/fmt.inko rename to std/src/std/fmt.inko index 53d22eb36..29f10dce7 100644 --- a/libstd/src/std/fmt.inko +++ b/std/src/std/fmt.inko @@ -41,8 +41,8 @@ class pub DefaultFormatter { let @buffer: StringBuffer let @nesting: Int - fn pub static new -> Self { - Self { @buffer = StringBuffer.new, @nesting = 0 } + fn pub static new -> DefaultFormatter { + DefaultFormatter { @buffer = StringBuffer.new, @nesting = 0 } } } diff --git a/std/src/std/fs/dir.inko b/std/src/std/fs/dir.inko new file mode 100644 index 000000000..c35bedd7a --- /dev/null +++ b/std/src/std/fs/dir.inko @@ -0,0 +1,127 @@ +# Manipulating directories on a filesystem. +import std::fs::path::Path +import std::io::Error +import std::string::ToString + +# Creates a new empty directory at the given path. +# +# # Errors +# +# This method returns an `Error` if any of the following conditions are met: +# +# 1. The user lacks the necessary permissions to create the directory. +# 2. The directory already exists. +# +# # Examples +# +# Creating a directory: +# +# import std::fs::dir +# +# dir.create('/tmp/test').unwrap +fn pub create(path: ref ToString) -> Result[Nil, Error] { + match _INKO.directory_create(path.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# Creates a new empty directory at the given path, while also creating any +# intermediate directories. +# +# # Errors +# +# This method returns an `Error` if any of the following conditions are met: +# +# 1. The user lacks the necessary permissions to create the directory. +# +# # Examples +# +# Creating a directory: +# +# import std::fs::dir +# +# dir.create_all('/tmp/foo/bar/test').unwrap +fn pub create_all(path: ref ToString) -> Result[Nil, Error] { + match _INKO.directory_create_recursive(path.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# Removes the directory at the given path. +# +# # Errors +# +# This method returns an `Error` if any of the following conditions are met: +# +# 1. The user lacks the necessary permissions to remove the directory. +# 2. The directory is not empty. +# 3. The directory does not exist. +# +# # Examples +# +# Removing a directory: +# +# import std::fs::dir +# +# dir.create('/tmp/test').unwrap +# dir.remove('/tmp/test').unwrap +fn pub remove(path: ref ToString) -> Result[Nil, Error] { + match _INKO.directory_remove(path.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# Removes the directory and its contents at the given path. +# +# # Errors +# +# This method returns an `Error` if any of the following conditions are met: +# +# 1. The user lacks the necessary permissions to remove the directory. +# 2. The directory does not exist. +# +# # Examples +# +# Removing a directory: +# +# import std::fs::dir +# +# dir.create_all('/tmp/foo/bar').unwrap +# dir.remove_all('/tmp/foo').unwrap +fn pub remove_all(path: ref ToString) -> Result[Nil, Error] { + match _INKO.directory_remove_recursive(path.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# Returns an `Array` containing the paths to the contents of the directory. +# +# # Errors +# +# This method returns an `Error` if any of the following conditions are met: +# +# 1. The user lacks the necessary permissions to read the contents of the +# directory. +# 2. The path does not point to a valid directory. +# +# # Examples +# +# Listing the contents of a directory: +# +# import std::fs::dir +# +# let paths = dir.list('.').unwrap +# +# paths[0].to_string # => 'README.md' +fn pub list(path: ref ToString) -> Result[Array[Path], Error] { + match _INKO.directory_list(path.to_string) { + case { @tag = 0, @value = v } -> Result.Ok( + (v as Array[String]).into_iter.map fn (path) { Path.new(path) }.to_array + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} diff --git a/std/src/std/fs/file.inko b/std/src/std/fs/file.inko new file mode 100644 index 000000000..3e3a18cd3 --- /dev/null +++ b/std/src/std/fs/file.inko @@ -0,0 +1,308 @@ +# Types and methods for manipulating files on a filesystem. +# +# Rather than using a single "File" type for all different file modes, Inko uses +# three separate file types: +# +# - `ReadOnlyFile`: read-only file operations +# - `WriteOnlyFile`: write-only file operations +# - `ReadWriteFile`: read-write file operations +# +# Using different types per file mode allows for a type-safe file API. +# +# Files are automatically closed when they are dropped. Any errors that may +# occur when closing a file are ignored. +import std::drop::Drop +import std::fs::path::(IntoPath, Path) +import std::io::(Error, Read, Seek, Size, Write) +import std::string::ToString + +let FILE_READ_ONLY = 0 +let FILE_WRITE_ONLY = 1 +let FILE_APPEND_ONLY = 2 +let FILE_READ_WRITE = 3 +let FILE_READ_APPEND = 4 + +# Removes the file for the given file path. +# +# # Examples +# +# Removing a file: +# +# import std::fs::file::(self, WriteOnlyFile) +# +# let handle = WriteOnlyFile.new('/tmp/test.txt').unwrap +# +# handle.write('hello').unwrap +# file.remove('/tmp/test.txt').unwrap +fn pub remove(path: ref ToString) -> Result[Nil, Error] { + match _INKO.file_remove(path.to_string) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# Copies a file from the source destination to the target destination, +# returning the number of copied bytes. +# +# # Examples +# +# Copying a file: +# +# import std::fs::file::(self, WriteOnlyFile) +# +# let handle = WriteOnlyFile.new('/tmp/test.txt').unwrap +# +# handle.write('hello').unwrap +# file.copy(from: '/tmp/test.txt', to: '/tmp/test2.txt').unwrap +fn pub copy(from: ref ToString, to: ref ToString) -> Result[Int, Error] { + match _INKO.file_copy(from.to_string, to.to_string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } +} + +# A file that can only be used for reads. +class pub ReadOnlyFile { + # The path of the file. + let pub @path: Path + + # The internal file descriptor. + let @fd: Any + + # Returns a new `ReadOnlyFile`. + # + # # Examples + # + # Opening a file in read-only mode: + # + # import std::fs::file::ReadOnlyFile + # + # let handle = ReadOnlyFile.new('/dev/null').unwrap + fn pub static new(path: IntoPath) -> Result[ReadOnlyFile, Error] { + let path = path.into_path + + match _INKO.file_open(path.to_string, FILE_READ_ONLY) { + case { @tag = 0, @value = v } -> Result.Ok( + ReadOnlyFile { @path = path, @fd = v } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Drop for ReadOnlyFile { + fn mut drop { + _INKO.file_drop(@fd) + } +} + +impl Read for ReadOnlyFile { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.file_read(@fd, into, size) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Seek for ReadOnlyFile { + fn pub mut seek(position: Int) -> Result[Int, Error] { + match _INKO.file_seek(@fd, position) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Size for ReadOnlyFile { + fn pub size -> Result[Int, Error] { + match _INKO.file_size(@path.to_string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +# A file that can only be used for writes. +class pub WriteOnlyFile { + # The path of the file. + let pub @path: Path + + # The internal file descriptor. + let @fd: Any + + # Opens a file in write-only mode. + # + # # Examples + # + # import std::fs::file::WriteOnlyFile + # + # let file = WriteOnlyFile.new('/dev/null').unwrap + fn pub static new(path: IntoPath) -> Result[WriteOnlyFile, Error] { + let path = path.into_path + + match _INKO.file_open(path.to_string, FILE_WRITE_ONLY) { + case { @tag = 0, @value = v } -> Result.Ok( + WriteOnlyFile { @path = path, @fd = v } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + # Opens a file in append-only mode. + # + # # Examples + # + # import std::fs::file::WriteOnlyFile + # + # let file = WriteOnlyFile.append('/dev/null').unwrap + fn pub static append(path: IntoPath) -> Result[WriteOnlyFile, Error] { + let path = path.into_path + + match _INKO.file_open(path.to_string, FILE_APPEND_ONLY) { + case { @tag = 0, @value = v } -> Result.Ok( + WriteOnlyFile { @path = path, @fd = v } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Drop for WriteOnlyFile { + fn mut drop { + _INKO.file_drop(@fd) + } +} + +impl Write for WriteOnlyFile { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.file_write_bytes(@fd, bytes) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.file_write_string(@fd, string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut flush -> Result[Nil, Error] { + match _INKO.file_flush(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Seek for WriteOnlyFile { + fn pub mut seek(position: Int) -> Result[Int, Error] { + match _INKO.file_seek(@fd, position) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +# A file that can be used for both reads and writes. +class pub ReadWriteFile { + # The path of the file. + let pub @path: Path + + # The internal file descriptor. + let @fd: Any + + # Opens a file for both reading and writing: + # + # # Examples + # + # import std::fs::file::ReadWriteFile + # + # let handle = ReadWriteFile.new('/dev/null').unwrap + fn pub static new(path: IntoPath) -> Result[ReadWriteFile, Error] { + let path = path.into_path + + match _INKO.file_open(path.to_string, FILE_READ_WRITE) { + case { @tag = 0, @value = v } -> Result.Ok( + ReadWriteFile { @path = path, @fd = v } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + # Opens a file for both reading and appending: + # + # # Examples + # + # import std::fs::file::ReadWriteFile + # + # let handle = ReadWriteFile.append('/dev/null').unwrap + fn pub static append(path: IntoPath) -> Result[ReadWriteFile, Error] { + let path = path.into_path + + match _INKO.file_open(path.to_string, FILE_READ_APPEND) { + case { @tag = 0, @value = v } -> Result.Ok( + ReadWriteFile { @path = path, @fd = v } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Drop for ReadWriteFile { + fn mut drop { + _INKO.file_drop(@fd) + } +} + +impl Read for ReadWriteFile { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.file_read(@fd, into, size) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Write for ReadWriteFile { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.file_write_bytes(@fd, bytes) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.file_write_string(@fd, string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut flush -> Result[Nil, Error] { + match _INKO.file_flush(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Seek for ReadWriteFile { + fn pub mut seek(position: Int) -> Result[Int, Error] { + match _INKO.file_seek(@fd, position) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +impl Size for ReadWriteFile { + fn pub size -> Result[Int, Error] { + match _INKO.file_size(@path.to_string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} diff --git a/libstd/src/std/fs/path.inko b/std/src/std/fs/path.inko similarity index 69% rename from libstd/src/std/fs/path.inko rename to std/src/std/fs/path.inko index ecdd361c5..cacab3a32 100644 --- a/libstd/src/std/fs/path.inko +++ b/std/src/std/fs/path.inko @@ -7,32 +7,12 @@ import std::string::(ToString, IntoString) import std::sys import std::time::DateTime -let FSLASH = 47 -let BSLASH = 92 -let COLON = 58 - -# Returns the main path separator for the current platform. -fn pub separator -> String { - if sys.windows? { '\\' } else { '/' } -} - -# Returns `True` if the given `String` starts with a Windows drive name, such as -# C:/. -fn starts_with_windows_drive_name?(path: String) -> Bool { - if path.size < 3 { return false } - - let start = path.byte(0) - - # Windows drive letters are limited to the range a-z and A-Z. - (start >= 97 and start <= 122) - or (start >= 65 and start <= 90) - and path.byte(1) == COLON - and path_separator?(path.byte(2)) -} +# The character used to separate components in a file path. +let pub SEPARATOR = '/' # Returns `True` if the byte is a valid path separator byte. fn path_separator?(byte: Int) -> Bool { - if sys.windows? { byte == FSLASH or byte == BSLASH } else { byte == FSLASH } + byte == 47 } # Returns the number of bytes leading up to the last path separator. @@ -41,17 +21,11 @@ fn path_separator?(byte: Int) -> Bool { fn bytes_before_last_separator(path: String) -> Int { if path.empty? { return -1 } - let windows_drive_path = - sys.windows? and starts_with_windows_drive_name?(path) - - # If the path starts with a Windows drive name (e.g. "C:\foo") we don't want - # to trim off the \ in C:\, as it's part of the drive name. - let trim_until = if windows_drive_path { 2 } else { 0 } let mut index = path.size - 1 # Trailing separators should be ignored, so we'll skip over them until the # first non-separator byte. - while index > trim_until and path_separator?(path.byte(index)) { + while index > 0 and path_separator?(path.byte(index)) { index -= 1 } @@ -63,10 +37,6 @@ fn bytes_before_last_separator(path: String) -> Int { if path_separator?(byte) { in_separator = true } else if in_separator { - # We have reached the ":" in a drive name such as "C:\". In this case we - # want to include the "\" since it's part of the drive name. - if windows_drive_path and index == 1 { return 3 } - return index + 1 } @@ -79,7 +49,6 @@ fn bytes_before_last_separator(path: String) -> Int { # Returns `True` if the given file path is an absolute path. fn absolute_path?(path: String) -> Bool { path_separator?(path.byte(0)) - or (sys.windows? and starts_with_windows_drive_name?(path)) } # A path to a file or directory. @@ -105,8 +74,8 @@ class pub Path { # The raw file path. let @path: String - fn pub static new(path: String) -> Self { - Self { @path = path } + fn pub static new(path: String) -> Path { + Path { @path = path } } # Returns `True` if the path points to a file. @@ -134,12 +103,14 @@ class pub Path { # # let path = Path.new('README.md') # - # try! path.created_at # => DateTime { ... } - fn pub created_at !! Error -> DateTime { - let time = - try _INKO.path_created_at(@path) else (err) throw Error.from_int(err) - - DateTime.from_timestamp(time, _INKO.time_system_offset) + # path.created_at.unwrap # => DateTime { ... } + fn pub created_at -> Result[DateTime, Error] { + match _INKO.path_created_at(@path) { + case { @tag = 0, @value = val } -> Result.Ok( + DateTime.from_timestamp(val as Float, _INKO.time_system_offset) + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Returns the modification time of `self`. @@ -152,12 +123,14 @@ class pub Path { # # let path = Path.new('README.md') # - # try! path.modified_at # => DateTime { ... } - fn pub modified_at !! Error -> DateTime { - let time = - try _INKO.path_modified_at(@path) else (err) throw Error.from_int(err) - - DateTime.from_timestamp(time, _INKO.time_system_offset) + # path.modified_at.unwrap # => DateTime { ... } + fn pub modified_at -> Result[DateTime, Error] { + match _INKO.path_modified_at(@path) { + case { @tag = 0, @value = val } -> Result.Ok( + DateTime.from_timestamp(val as Float, _INKO.time_system_offset) + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Returns the access time of `self`. @@ -170,12 +143,14 @@ class pub Path { # # let path = Path.new('README.md') # - # try! path.accessed_at # => DateTime { ... } - fn pub accessed_at !! Error -> DateTime { - let time = - try _INKO.path_accessed_at(@path) else (err) throw Error.from_int(err) - - DateTime.from_timestamp(time, _INKO.time_system_offset) + # path.accessed_at.unwrap # => DateTime { ... } + fn pub accessed_at -> Result[DateTime, Error] { + match _INKO.path_accessed_at(@path) { + case { @tag = 0, @value = val } -> Result.Ok( + DateTime.from_timestamp(val as Float, _INKO.time_system_offset) + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Returns `True` if this `Path` is an absolute path. @@ -229,7 +204,7 @@ class pub Path { } else if path_separator?(@path.byte(@path.size - 1)) { "{@path}{path_str}" } else { - "{@path}{separator}{path_str}" + "{@path}{SEPARATOR}{path_str}" } Path.new(new_path) @@ -260,6 +235,44 @@ class pub Path { Path.new(@path.slice_bytes(start: 0, length: length)) } + + # Returns the last component in `self`. + # + # If `self` is a file, then the tail will be the file name including its + # extension. If `self` is a directory, the directory name is returned. + # + # # Examples + # + # import std::fs::path::Path + # + # Path.new('foo/bar/baz.txt') # => 'baz.txt' + fn pub tail -> String { + let len = bytes_before_last_separator(@path) + + if len < 0 { return @path } + + @path.slice_bytes(start: len + 1, length: @path.size - len) + } + + # Returns the canonical, absolute version of `self`. + # + # # Errors + # + # This method may return an `Error` for cases such as when `self` doesn't + # exist, or when a component that isn't the last component is _not_ a + # directory. + # + # # Examples + # + # import std::fs::path::Path + # + # Path.new('/foo/../bar').expand.unwrap # => Path.new('/bar') + fn pub expand -> Result[Path, Error] { + match _INKO.path_expand(@path) { + case { @tag = 0, @value = v } -> Result.Ok(Path.new(v as String)) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } } # A type from which a new `Path` can be created. @@ -277,7 +290,7 @@ trait pub IntoPath { fn pub move into_path -> Path } -impl Equal for Path { +impl Equal[Path] for Path { # Returns `True` if `self` is equal to the given `Path`. # # # Examples @@ -290,7 +303,7 @@ impl Equal for Path { # let path2 = Path.new('foo') # # path1 == path2 # => True - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Path) -> Bool { @path == other.to_string } } @@ -329,14 +342,17 @@ impl Size for Path { # # let path = Path.new('/dev/null') # - # try! path.size # => 0 - fn pub size !! Error -> Int { - try _INKO.file_size(@path) else (err) throw Error.from_int(err) + # path.size.unwrap # => 0 + fn pub size -> Result[Int, Error] { + match _INKO.file_size(@path) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } } -impl Clone for Path { - fn pub clone -> Self { +impl Clone[Path] for Path { + fn pub clone -> Path { Path.new(@path) } } diff --git a/libstd/src/std/hash.inko b/std/src/std/hash.inko similarity index 94% rename from libstd/src/std/hash.inko rename to std/src/std/hash.inko index 1717b26e1..8784977ab 100644 --- a/libstd/src/std/hash.inko +++ b/std/src/std/hash.inko @@ -9,7 +9,7 @@ trait pub Hasher { fn pub mut write(value: Int) # Returns the hash for the values written so far. - fn pub hash -> Int + fn pub move finish -> Int } # A value that can be hashed. diff --git a/std/src/std/hash/siphash.inko b/std/src/std/hash/siphash.inko new file mode 100644 index 000000000..4fbc79a25 --- /dev/null +++ b/std/src/std/hash/siphash.inko @@ -0,0 +1,99 @@ +# The SipHash hashing algorithm. +# +# SipHash isn't cryptographically secure, instead it's intended for e.g. hashing +# of objects as part of the `Map` type. +import std::crypto::math::(rotate_left_u64) +import std::endian::little::(read_i64) +import std::hash::Hasher + +# An implementation of the SipHash 1-3 algorithm. +# +# # Examples +# +# import std::hash::siphash::SipHasher13 +# import std::rand::Random +# +# let rng = Random.new +# let hasher = SipHasher13.new(key0: rng.int, key1: rng.int) +# +# hasher.write(42) +# hasher.finish +class pub SipHasher13 { + let @length: Int + let @m_index: Int + let @m: Int + let @v0: Int + let @v1: Int + let @v2: Int + let @v3: Int + + # Returns a new hasher using two default keys. + fn pub static default -> SipHasher13 { + new(_INKO.hash_key0, _INKO.hash_key1) + } + + # Returns a new hasher using the two keys. + # + # Both keys _should_ be randomly generated. The type `std::rand::Random` can + # be used to generate these keys. + fn pub static new(key0: Int, key1: Int) -> SipHasher13 { + SipHasher13 { + @length = 0, + @m_index = 0, + @m = 0, + @v0 = 0x736F6D6570736575 ^ key0, + @v1 = 0x646F72616E646F6D ^ key1, + @v2 = 0x6C7967656E657261 ^ key0, + @v3 = 0x7465646279746573 ^ key1, + } + } + + fn mut round { + @v0 = @v0.wrapping_add(@v1) + @v2 = @v2.wrapping_add(@v3) + @v1 = rotate_left_u64(@v1, 13) + @v3 = rotate_left_u64(@v3, 16) + + @v1 ^= @v0 + @v3 ^= @v2 + @v0 = rotate_left_u64(@v0, 32) + + @v2 = @v2.wrapping_add(@v1) + @v0 = @v0.wrapping_add(@v3) + @v1 = rotate_left_u64(@v1, 17) + @v3 = rotate_left_u64(@v3, 21) + + @v1 ^= @v2 + @v3 ^= @v0 + @v2 = rotate_left_u64(@v2, 32) + } +} + +impl Hasher for SipHasher13 { + fn pub mut write(value: Int) { + @length += 1 + @m |= (value & 0xFF) << ((@m_index := @m_index + 1) * 8) + + if @m_index < 8 { return } + + @v3 ^= @m + round + @v0 ^= @m + @m_index = 0 + @m = 0 + } + + fn pub move finish -> Int { + let len = @length + + while @m_index < 7 { write(0) } + + write(len) + @v2 ^= 0xFF + round + round + round + + @v0 ^ @v1 ^ @v2 ^ @v3 + } +} diff --git a/libstd/src/std/init.inko b/std/src/std/init.inko similarity index 88% rename from libstd/src/std/init.inko rename to std/src/std/init.inko index af2bdcbc5..800bf2e1a 100644 --- a/libstd/src/std/init.inko +++ b/std/src/std/init.inko @@ -13,3 +13,5 @@ import std::option import std::process import std::string import std::tuple +import std::channel +import std::result diff --git a/libstd/src/std/int.inko b/std/src/std/int.inko similarity index 76% rename from libstd/src/std/int.inko rename to std/src/std/int.inko index bee013918..b72fc699b 100644 --- a/libstd/src/std/int.inko +++ b/std/src/std/int.inko @@ -41,10 +41,11 @@ class builtin Int { # # Int.from_base2('11') # => Option.Some(3) # Int.from_base2('ff') # => Option.None - fn pub static from_base2(string: String) -> Option[Self] { - let res = _INKO.string_to_int(string, 2, -1, -1) - - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + fn pub static from_base2(string: String) -> Option[Int] { + match _INKO.string_to_int(string, 2, -1, -1) { + case { @tag = 0, @value = v } -> Option.Some(v as Int) + case _ -> Option.None + } } # Parses an `Int` from a `String`, using base 10. @@ -56,10 +57,11 @@ class builtin Int { # # Int.from_base10('12') # => Option.Some(12) # Int.from_base10('ff') # => Option.None - fn pub static from_base10(string: String) -> Option[Self] { - let res = _INKO.string_to_int(string, 10, -1, -1) - - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + fn pub static from_base10(string: String) -> Option[Int] { + match _INKO.string_to_int(string, 10, -1, -1) { + case { @tag = 0, @value = v } -> Option.Some(v as Int) + case _ -> Option.None + } } # Parses an `Int` from a `String`, using base 16. @@ -75,10 +77,11 @@ class builtin Int { # # Int.from_base16('ef') # => Option.Some(239) # Int.from_base16('zz') # => Option.None - fn pub static from_base16(string: String) -> Option[Self] { - let res = _INKO.string_to_int(string, 16, -1, -1) - - if _INKO.is_undefined(res) { Option.None } else { Option.Some(res) } + fn pub static from_base16(string: String) -> Option[Int] { + match _INKO.string_to_int(string, 16, -1, -1) { + case { @tag = 0, @value = v } -> Option.Some(v as Int) + case _ -> Option.None + } } # Formats `self` as a `String` using base 2. @@ -124,7 +127,7 @@ class builtin Int { # # -4.absolute # => 4 # 4.absolute # => 4 - fn pub absolute -> Self { + fn pub absolute -> Int { if self < 0 { 0 - self } else { clone } } @@ -134,7 +137,7 @@ class builtin Int { # # -42.opposite # => 42 # 42.opposite # => -42 - fn pub opposite -> Self { + fn pub opposite -> Int { 0 - self } @@ -300,8 +303,8 @@ impl ToFloat for Int { } } -impl Compare for Int { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Int] for Int { + fn pub cmp(other: ref Int) -> Ordering { if self > other { Ordering.Greater } else if self < other { @@ -311,32 +314,32 @@ impl Compare for Int { } } - fn pub <(other: ref Self) -> Bool { + fn pub <(other: ref Int) -> Bool { _INKO.int_lt(self, other) } - fn pub <=(other: ref Self) -> Bool { + fn pub <=(other: ref Int) -> Bool { _INKO.int_le(self, other) } - fn pub >(other: ref Self) -> Bool { + fn pub >(other: ref Int) -> Bool { _INKO.int_gt(self, other) } - fn pub >=(other: ref Self) -> Bool { + fn pub >=(other: ref Int) -> Bool { _INKO.int_ge(self, other) } } -impl Equal for Int { +impl Equal[Int] for Int { fn pub ==(other: ref Int) -> Bool { _INKO.int_eq(self, other) } } -impl Clone for Int { - fn pub clone -> Self { - _INKO.int_clone(self) +impl Clone[Int] for Int { + fn pub clone -> Int { + self } } @@ -346,74 +349,82 @@ impl ToString for Int { } } -impl Add[Int] for Int { - fn pub +(other: ref Self) -> Self { +impl Add[Int, Int] for Int { + fn pub +(other: ref Int) -> Int { _INKO.int_add(self, other) } } -impl Subtract[Int] for Int { - fn pub -(other: ref Self) -> Self { +impl Subtract[Int, Int] for Int { + fn pub -(other: ref Int) -> Int { _INKO.int_sub(self, other) } } -impl Divide for Int { - fn pub /(other: ref Self) -> Self { - _INKO.int_div(self, other) +impl Divide[Int, Int] for Int { + fn pub /(other: ref Int) -> Int { + # This implements floored division, rather than rounding towards zero. This + # makes division work more natural when using negative numbers. + # + # This code is based on the upcoming div_floor() implementation of the Rust + # standard library: https://github.com/rust-lang/rust/pull/88582. + let d = _INKO.int_div(self, other) + let r = _INKO.int_rem(self, other) + + if (r > 0 and other < 0) or (r < 0 and other > 0) { d - 1 } else { d } } } -impl Multiply for Int { - fn pub *(other: ref Self) -> Self { +impl Multiply[Int, Int] for Int { + fn pub *(other: ref Int) -> Int { _INKO.int_mul(self, other) } } -impl Modulo for Int { - fn pub %(other: ref Self) -> Self { - _INKO.int_mod(self, other) +impl Modulo[Int, Int] for Int { + fn pub %(other: ref Int) -> Int { + _INKO.int_rem(_INKO.int_add(_INKO.int_rem(self, other), other), other) } } -impl BitAnd for Int { - fn pub &(other: ref Self) -> Self { +impl BitAnd[Int, Int] for Int { + fn pub &(other: ref Int) -> Int { _INKO.int_bit_and(self, other) } } -impl BitOr for Int { - fn pub |(other: ref Self) -> Self { +impl BitOr[Int, Int] for Int { + fn pub |(other: ref Int) -> Int { _INKO.int_bit_or(self, other) } } -impl BitXor for Int { - fn pub ^(other: ref Self) -> Self { +impl BitXor[Int, Int] for Int { + fn pub ^(other: ref Int) -> Int { _INKO.int_bit_xor(self, other) } } -impl ShiftLeft for Int { - fn pub <<(other: ref Self) -> Self { +impl ShiftLeft[Int, Int] for Int { + fn pub <<(other: ref Int) -> Int { _INKO.int_shl(self, other) } } -impl ShiftRight for Int { - fn pub >>(other: ref Self) -> Self { +impl ShiftRight[Int, Int] for Int { + fn pub >>(other: ref Int) -> Int { _INKO.int_shr(self, other) } } -impl UnsignedShiftRight for Int { - fn pub >>>(other: ref Self) -> Self { +impl UnsignedShiftRight[Int, Int] for Int { + fn pub >>>(other: ref Int) -> Int { _INKO.int_unsigned_shr(self, other) } } -impl Power for Int { - fn pub **(other: ref Self) -> Self { +impl Power[Int, Int] for Int { + fn pub **(other: ref Int) -> Int { _INKO.int_pow(self, other) } } diff --git a/libstd/src/std/io.inko b/std/src/std/io.inko similarity index 91% rename from libstd/src/std/io.inko rename to std/src/std/io.inko index 122b969a3..748104730 100644 --- a/libstd/src/std/io.inko +++ b/std/src/std/io.inko @@ -36,7 +36,7 @@ class pub enum Error { case UnexpectedEof # Returns a new `Error` according to the given error code. - fn pub static from_int(code: Int) -> Self { + fn pub static from_int(code: Int) -> Error { match code { case 0 -> Other case 1 -> NotFound @@ -96,8 +96,8 @@ impl ToString for Error { } } -impl Equal for Error { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Error] for Error { + fn pub ==(other: ref Error) -> Bool { match self { case Other -> match other { case Other -> true @@ -199,7 +199,7 @@ impl Format for Error { # Trait for retrieving the size of an IO object. trait pub Size { - fn pub size !! Error -> Int + fn pub size -> Result[Int, Error] } # Trait for reading from a stream. @@ -210,7 +210,7 @@ trait pub Read { # # The `size` argument specifies how many bytes are to be read. The actual # number of bytes read may be less than this value. - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] # Reads all bytes from the stream into the `ByteArray`. # @@ -218,14 +218,14 @@ trait pub Read { # bytes and re-throws the error. # # The return value is the number of bytes read. - fn pub mut read_all(bytes: mut ByteArray) !! Error -> Int { + fn pub mut read_all(bytes: mut ByteArray) -> Result[Int, Error] { let mut total = 0 let mut read_size = INITIAL_READ_ALL_SIZE loop { let bytes_read = try read(into: bytes, size: read_size) - if bytes_read == 0 { return total } + if bytes_read == 0 { return Result.Ok(total) } total += bytes_read @@ -240,18 +240,18 @@ trait pub Read { trait pub Write { # Writes an `Array` of bytes to the stream, returning the number of bytes # written. - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] # Writes a `String` to the stream, returning the number of bytes written. - fn pub mut write_string(string: String) !! Error -> Int + fn pub mut write_string(string: String) -> Result[Int, Error] # Writes a `String` followed by a Unix newline to the stream. - fn pub mut print(string: String) !! Error -> Int { - try { write_string(string) } + try { write_string("\n") } + fn pub mut print(string: String) -> Result[Int, Error] { + Result.Ok((try write_string(string)) + (try write_string("\n"))) } # Flushes any pending writes. - fn pub mut flush !! Error + fn pub mut flush -> Result[Nil, Error] } # Trait for seeking to a given offset in a stream of bytes. @@ -260,5 +260,5 @@ trait pub Seek { # # If `position` is negative, seeking is performed in reverse order relative to # the end. - fn pub mut seek(position: Int) !! Error -> Int + fn pub mut seek(position: Int) -> Result[Int, Error] } diff --git a/libstd/src/std/iter.inko b/std/src/std/iter.inko similarity index 81% rename from libstd/src/std/iter.inko rename to std/src/std/iter.inko index d967e3d66..6b518ec5a 100644 --- a/libstd/src/std/iter.inko +++ b/std/src/std/iter.inko @@ -28,16 +28,12 @@ import std::string::(ToString, StringBuffer) let pub EOF = -1 # A generic iterator over a sequence of values of type `T`. -# -# The type parameter `T` is the type of values that is produced. The type -# parameter `E` is the error type that may be thrown. If an iterator doesn't -# throw, this parameter should be assigned to `Never`. -trait pub Iter[T, E] { +trait pub Iter[T] { # Returns the next value in the iterator. # # If a value is produced, it must be wrapped in a Some; otherwise a None is to # be returned. - fn pub mut next !! E -> Option[T] + fn pub mut next -> Option[T] # Calls the block for every value in `self`. # @@ -49,9 +45,9 @@ trait pub Iter[T, E] { # iter.each fn (num) { # num # => 10, 20, 30 # } - fn pub move each(block: fn (T)) !! E { + fn pub move each(block: fn (T)) { loop { - match try self.next { + match self.next { case Some(v) -> block.call(v) case _ -> return } @@ -70,8 +66,8 @@ trait pub Iter[T, E] { # index # => 0, 1, 2 # num # => 10, 20, 30 # } - fn pub move each_with_index(block: fn (Int, T)) !! E { - try with_index.each fn move (pair) { + fn pub move each_with_index(block: fn (Int, T)) { + with_index.each fn move (pair) { match pair { case (index, value) -> block.call(index, value) } @@ -86,7 +82,7 @@ trait pub Iter[T, E] { # # iter.next # => Option.Some((0, 10)) # iter.next # => Option.Some((1, 20)) - fn pub move with_index -> Iter[(Int, T), E] { + fn pub move with_index -> Iter[(Int, T)] { let mut index = 0 map fn move (val) { (index := index + 1, val) } @@ -102,9 +98,9 @@ trait pub Iter[T, E] { # let values = [1, 2, 3] # # values.iter.map fn (n) { n * 2 }.to_array # => [2, 4, 6] - fn pub move map[R](block: fn (T) -> R) -> Iter[R, E] { + fn pub move map[R](block: fn (T) -> R) -> Iter[R] { Enum.new fn move { - try { self.next }.map fn (v) { block.call(v) } + self.next.map fn (v) { block.call(v) } } } @@ -120,15 +116,40 @@ trait pub Iter[T, E] { # let numbers = [10, 20, 50, 80] # # numbers.iter.find fn (number) { number > 50 } # => 80 - fn pub mut find(block: fn (ref T) -> Bool) !! E -> Option[T] { + fn pub mut find(block: fn (ref T) -> Bool) -> Option[T] { loop { - match try self.next { + match self.next { case Some(v) -> if block.call(v) { return Option.Some(v) } case _ -> return Option.None } } } + # Returns an `Iter` that combines `find` with `map`. + # + # For each value in `self`, the supplied closure is called. If the closure + # returns a `Some`, the value is returned an iteration stops. + # + # # Examples + # + # let vals = [10, 20, 30] + # let val = vals.into_iter.find_map fn (v) { + # if v == 20 { Option.Some(v.to_string) } else { Option.None } + # } + # + # val # => Option.Some('20') + fn pub mut find_map[R](block: fn (T) -> Option[R]) -> Option[R] { + loop { + match self.next { + case Some(v) -> match block.call(v) { + case Some(r) -> return Option.Some(r) + case _ -> {} + } + case _ -> return Option.None + } + } + } + # Returns `True` if `self` contains any value for which the `block` argument # returned `True`. # @@ -139,9 +160,9 @@ trait pub Iter[T, E] { # Checking if an `Iter` contains a value: # # [10, 20, 30].iter.any? fn (value) { value >= 20 } # => True - fn pub mut any?(block: fn (T) -> Bool) !! E -> Bool { + fn pub mut any?(block: fn (T) -> Bool) -> Bool { loop { - match try self.next { + match self.next { case Some(v) -> if block.call(v) { return true } case _ -> return false } @@ -159,10 +180,10 @@ trait pub Iter[T, E] { # .iter # .select fn (value) { value > 10 } # .to_array # => [20, 30] - fn pub move select(block: fn (ref T) -> Bool) -> Iter[T, E] { + fn pub move select(block: fn (ref T) -> Bool) -> Iter[T] { Enum.new fn move { loop { - match try self.next { + match self.next { case Some(v) -> if block.call(v) { return Option.Some(v) } case _ -> return Option.None } @@ -183,10 +204,10 @@ trait pub Iter[T, E] { # iter.next # => Option.Some(10) # iter.next # => Option.Some(30) # iter.next # => Option.None - fn pub move select_map[R](block: fn (T) -> Option[R]) -> Iter[R, E] { + fn pub move select_map[R](block: fn (T) -> Option[R]) -> Iter[R] { Enum.new fn move { loop { - match try self.next { + match self.next { case Some(v) -> match block.call(v) { case Some(r) -> return Option.Some(r) case _ -> next @@ -212,8 +233,8 @@ trait pub Iter[T, E] { # # pair.0 # => [30, 40, 50] # pair.1 # => [10, 20] - fn pub move partition(block: fn (ref T) -> Bool) !! E -> (Array[T], Array[T]) { - try reduce(([], [])) fn move (acc, val) { + fn pub move partition(block: fn (ref T) -> Bool) -> (Array[T], Array[T]) { + reduce(([], [])) fn move (acc, val) { if block.call(ref val) { acc.0.push(val) } else { acc.1.push(val) } acc @@ -232,9 +253,9 @@ trait pub Iter[T, E] { # # [10, 20].iter.all? fn (value) { value.positive? } # => True # [-1, 20].iter.all? fn (value) { value.positive? } # => False - fn pub mut all?(block: fn (T) -> Bool) !! E -> Bool { + fn pub mut all?(block: fn (T) -> Bool) -> Bool { loop { - match try self.next { + match self.next { case Some(v) -> if block.call(v).false? { return false } case _ -> return true } @@ -264,10 +285,8 @@ trait pub Iter[T, E] { # let zip = a.iter.zip(b.iter) # # zip.next # => (10, 40) - fn pub move zip[U](other: Iter[U, E]) -> Iter[(T, U), E] { - Enum.new fn move { - try { self.next }.zip(try other.next) - } + fn pub move zip[U](other: Iter[U]) -> Iter[(T, U)] { + Enum.new fn move { self.next.zip(other.next) } } # Combines all values in the iterator into the specified accumulator. @@ -304,11 +323,11 @@ trait pub Iter[T, E] { # # For the last element the return value is `6`, so the return value of the # reduce method is also `6`. - fn pub move reduce[A](accumulator: A, block: fn (A, T) -> A) !! E -> A { + fn pub move reduce[A](accumulator: A, block: fn (A, T) -> A) -> A { let mut result = accumulator loop { - match try self.next { + match self.next { case Some(v) -> result = block.call(result, v) case _ -> return result } @@ -320,12 +339,12 @@ trait pub Iter[T, E] { # Each chunk is up to the amount specified by the `size` argument. If the # number of values can't be evenly divided, the last chunk may contain fewer # than `size` elements. - fn pub move chunks(size: Int) -> Iter[Array[T], E] { + fn pub move chunks(size: Int) -> Iter[Array[T]] { Enum.new fn move { let chunk = [] while chunk.length < size { - match try self.next { + match self.next { case Some(val) -> chunk.push(val) case _ -> break } @@ -344,8 +363,8 @@ trait pub Iter[T, E] { # Transforming an `Iter` back into an `Array`: # # [1, 2, 3].iter.to_array # => [1, 2, 3] - fn pub move to_array !! E -> Array[T] { - try reduce([]) fn (values, value) { + fn pub move to_array -> Array[T] { + reduce([]) fn (values, value) { values.push(value) values } @@ -358,15 +377,15 @@ trait pub Iter[T, E] { # # Examples # # [1, 2, 3].iter.count # => 3 - fn pub move count !! E -> Int { - try reduce(0) fn (count, _) { count + 1 } + fn pub move count -> Int { + reduce(0) fn (count, _) { count + 1 } } } # A type that can be moved into an iterator. -trait pub IntoIter[T, E] { +trait pub IntoIter[T] { # Moves `self` into an iterator. - fn pub move into_iter -> Iter[T, E] + fn pub move into_iter -> Iter[T] } # A type for easily creating iterators using blocks. @@ -379,7 +398,7 @@ trait pub IntoIter[T, E] { # # import std::iter::(Iter, Enum) # -# fn example(max: Int) -> Iter[Int, Never] { +# fn example(max: Int) -> Iter[Int] { # let mut index = 0 # # Enum.new fn move { @@ -397,12 +416,12 @@ trait pub IntoIter[T, E] { # nums.next # => Option.Some(0) # nums.next # => Option.Some(1) # } -class pub Enum[T, E] { - let @block: fn !! E -> Option[T] +class pub Enum[T] { + let @block: fn -> Option[T] # Returns a new iterator using the block. - fn pub static new(block: fn !! E -> Option[T]) -> Self { - Self { @block = block } + fn pub static new(block: fn -> Option[T]) -> Enum[T] { + Enum { @block = block } } # Returns an iterator that produces the given value once. @@ -415,10 +434,10 @@ class pub Enum[T, E] { # # vals.next # => Option.Some(42) # vals.next # => Option.None - fn pub static once(value: T) -> Iter[T, Never] { + fn pub static once(value: T) -> Enum[T] { let mut val = Option.Some(value) - Self { @block = fn move { val := Option.None } } + Enum { @block = fn move { val := Option.None } } } # Returns an iterator that iterates from zero up to (but not including) the @@ -441,23 +460,23 @@ class pub Enum[T, E] { # iter.next #=> Option.Some(10) # iter.next #=> Option.Some(20) # iter.next #=> Option.None - fn pub static indexed(limit: Int, block: fn (Int) !! E -> T) -> Self { + fn pub static indexed(limit: Int, block: fn (Int) -> T) -> Enum[T] { let mut index = 0 let wrapper = fn move { if index < limit { - Option.Some(try block.call(index := index + 1)) + Option.Some(block.call(index := index + 1)) } else { Option.None } } - Self { @block = wrapper } + Enum { @block = wrapper } } } -impl Iter[T, E] for Enum { - fn pub mut next !! E -> Option[T] { - try @block.call +impl Iter[T] for Enum { + fn pub mut next -> Option[T] { + @block.call } } @@ -473,7 +492,7 @@ impl Iter[T, E] for Enum { # easiest way of doing this is to have `Iter.next` reuse the implementation of # `Bytes.next_byte` like so: # -# impl Iter[Int, Never] for MyType { +# impl Iter[Int] for MyType { # fn pub mut next -> Option[Int] { # match next_byte { # case EOF -> Option.None @@ -482,19 +501,16 @@ impl Iter[T, E] for Enum { # } # } # -# impl Bytes[Never] for MyType { +# impl Bytes for MyType { # fn pub mut next_byte -> Int { # # ... # } # } -# -# The type parameter `E` specifies the error that `next` may throw. If a stream -# can't throw, this parameter should be assigned to `Never`. -trait pub Bytes[E]: Iter[Int, E] { +trait pub Bytes: Iter[Int] { # Returns the next byte in the iterator. # # If all input is consumed, this method must return `std::iter::EOF`. - fn pub mut next_byte !! E -> Int + fn pub mut next_byte -> Int } # Joins the values of an iterator together using a separator. @@ -506,8 +522,8 @@ trait pub Bytes[E]: Iter[Int, E] { # let vals = [10, 20, 30].into_iter # # iter.join(vals, ',') => '10,20,30' -fn pub join[T: ToString, E](iter: Iter[T, E], with: String) !! E -> String { - let result = try iter.reduce(StringBuffer.new) fn (buff, val) { +fn pub join[T: ToString](iter: Iter[T], with: String) -> String { + let result = iter.reduce(StringBuffer.new) fn (buff, val) { if buff.length > 0 { buff.push(with) } buff.push(val.to_string) diff --git a/libstd/src/std/json.inko b/std/src/std/json.inko similarity index 86% rename from libstd/src/std/json.inko rename to std/src/std/json.inko index 6daefc85b..52ac58263 100644 --- a/libstd/src/std/json.inko +++ b/std/src/std/json.inko @@ -12,7 +12,7 @@ # # import std::json # -# try! json.parse('[10]') # => Json.Array([Json.Int(10)]) +# json.parse('[10]').unwrap # => Json.Array([Json.Int(10)]) # # The parser enforces limits on the number of nested objects and the length of # strings. These limits can be adjusted by using the `Parser` type directly like @@ -24,8 +24,7 @@ # # parser.max_depth = 4 # parser.max_string_size = 128 -# -# try! parser.parse +# parser.parse # # # Generating JSON # @@ -162,6 +161,12 @@ class pub Error { let pub @offset: Int } +impl Equal[Error] for Error { + fn pub ==(other: ref Error) -> Bool { + @message == other.message and @line == other.line + } +} + impl Format for Error { fn pub fmt(formatter: mut Formatter) { formatter.write(to_string) @@ -251,7 +256,7 @@ impl Format for Json { } } -impl Equal for Json { +impl Equal[Json] for Json { fn pub ==(other: ref Json) -> Bool { match self { case Int(lhs) -> match other { @@ -322,8 +327,8 @@ class pub Parser { let pub @max_string_size: Int # Returns a new parser that will parse the given `String`. - fn pub static new(string: String) -> Self { - Self { + fn pub static new(string: String) -> Parser { + Parser { @string = string, @index = 0, @size = string.size, @@ -344,8 +349,8 @@ class pub Parser { # # let parser = Parser.new('[10, 20]') # - # try! parser.parse # => Json.Array([Json.Int(10), Json.Int(20)]) - fn pub move parse !! Error -> Json { + # parser.parse.unwrap # => Json.Array([Json.Int(10), Json.Int(20)]) + fn pub move parse -> Result[Json, Error] { let result = try value whitespace @@ -353,10 +358,10 @@ class pub Parser { # Only trailing whitespace is allowed. if current != EOF { throw unexpected(current) } - result + Result.Ok(result) } - fn mut value !! Error -> Json { + fn mut value -> Result[Json, Error] { if @depth >= @max_depth { throw error("Only up to {@max_depth} nested objects/arrays are allowed") } @@ -365,14 +370,14 @@ class pub Parser { loop { match current { - case MINUS -> return try number - case BRACKET_OPEN -> return try array - case CURLY_OPEN -> return try object - case LOWER_T -> return try self.true - case LOWER_F -> return try self.false - case LOWER_N -> return try null - case DQUOTE -> return try string - case byte if digit?(byte) -> return try number + case MINUS -> return number + case BRACKET_OPEN -> return array + case CURLY_OPEN -> return object + case LOWER_T -> return self.true + case LOWER_F -> return self.false + case LOWER_N -> return null + case DQUOTE -> return string + case byte if digit?(byte) -> return number # This is to take care of any random garbage that may be included in the # JSON document, including Unicode BOMs. This also saves us from having # to explicitly check for all the different BOMs. @@ -381,11 +386,11 @@ class pub Parser { } } - fn mut string !! Error -> Json { - Json.String(try string_value) + fn mut string -> Result[Json, Error] { + string_value.map fn (val) { Json.String(val) } } - fn mut string_value !! Error -> String { + fn mut string_value -> Result[String, Error] { advance let start = @index @@ -396,7 +401,7 @@ class pub Parser { # method of parsing that supports escape sequences. Using this as a # fallback instead of the default allows us to avoid unnecessary # allocations for many strings. - case BSLASH -> return try string_with_escape_sequence(start) + case BSLASH -> return string_with_escape_sequence(start) case DQUOTE -> break case EOF -> throw unexpected(current) case val if val >= 0x0 and val <= 0x001F -> throw invalid_control(val) @@ -416,16 +421,16 @@ class pub Parser { let string = slice_string(start) advance - string + Result.Ok(string) } - fn mut string_with_escape_sequence(started_at: Int) !! Error -> String { + fn mut string_with_escape_sequence(started_at: Int) -> Result[String, Error] { let buffer = @string.slice_bytes(started_at, @index - started_at).to_byte_array loop { match current { - case BSLASH -> match ESCAPE_TABLE[peek] { + case BSLASH -> match ESCAPE_TABLE.get(peek) { case -1 if peek == LOWER_U -> try escaped_unicode(buffer) case -1 -> { let start = @index @@ -457,17 +462,17 @@ class pub Parser { ) } - buffer.into_string + Result.Ok(buffer.into_string) } - fn mut escaped_unicode(buffer: mut ByteArray) !! Error { + fn mut escaped_unicode(buffer: mut ByteArray) -> Result[Nil, Error] { # Skip the "\u" @index += 2 let start = @index let high = try codepoint - if utf8.encode_scalar(high, buffer) > 0 { return } + if utf8.encode_scalar(high, buffer) > 0 { return Result.Ok(nil) } # At this point the codepoint is either straight up invalid (e.g. "\uZZZZ"), # or it's a UTF-16 surrogate. @@ -486,13 +491,13 @@ class pub Parser { let codepoint = utf8.codepoint_from_surrogates(high, low) # The encoding may fail for pairs such as "\uDFFF\uDFFF". - if utf8.encode_scalar(codepoint, buffer) > 0 { return } + if utf8.encode_scalar(codepoint, buffer) > 0 { return Result.Ok(nil) } } throw error("'{slice_string(start)}' is an invalid UTF-16 surrogate pair") } - fn mut codepoint !! Error -> Int { + fn mut codepoint -> Result[Int, Error] { let start = @index @index += 4 @@ -501,31 +506,30 @@ class pub Parser { throw error("Expected four hexadecimal digits, but we ran out of input") } - let codepoint = _INKO.string_to_int(@string, 16, start, @index) - - if _INKO.is_undefined(codepoint) { - throw error("'{slice_string(start)}' is an invalid Unicode codepoint") + match _INKO.string_to_int(@string, 16, start, @index) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case _ -> Result.Error( + error("'{slice_string(start)}' is an invalid Unicode codepoint") + ) } - - codepoint } - fn mut true !! Error -> Json { + fn mut true -> Result[Json, Error] { try identifier('true') - Json.Bool(true) + Result.Ok(Json.Bool(true)) } - fn mut false !! Error -> Json { + fn mut false -> Result[Json, Error] { try identifier('false') - Json.Bool(false) + Result.Ok(Json.Bool(false)) } - fn mut null !! Error -> Json { + fn mut null -> Result[Json, Error] { try identifier('null') - Json.Null + Result.Ok(Json.Null) } - fn mut array !! Error -> Json { + fn mut array -> Result[Json, Error] { advance let values = [] @@ -539,10 +543,10 @@ class pub Parser { @depth -= 1 advance - Json.Array(values) + Result.Ok(Json.Array(values)) } - fn mut object !! Error -> Json { + fn mut object -> Result[Json, Error] { advance whitespace @@ -561,15 +565,15 @@ class pub Parser { try separator(CURLY_CLOSE) - map[key] = val + map.set(key, val) } advance @depth -= 1 - Json.Object(map) + Result.Ok(Json.Object(map)) } - fn mut number !! Error -> Json { + fn mut number -> Result[Json, Error] { let start = @index if current == MINUS { @@ -603,27 +607,31 @@ class pub Parser { # number parser. As part of parsing the JSON number we already validate # it. This means we can bypass `Int.from_base10` (and `Float.parse` # below), and instead use the underlying runtime functions. - let res = _INKO.string_to_int(@string, 10, start, @index) - - # If the number is too big to fit in an integer, we'll promote the - # number to a float. - if _INKO.is_undefined(res).false? { return Json.Int(res) } + match _INKO.string_to_int(@string, 10, start, @index) { + # If the number is too big to fit in an integer, we'll promote the + # number to a float. + case { @tag = 0, @value = v } -> return Result.Ok(Json.Int(v as Int)) + case _ -> {} + } } } # At this point we've already validated the input format, and it's # compatible with the underlying float parser, so no extra checks are # needed. - Json.Float(_INKO.string_to_float(@string, start, @index)) + Result.Ok(Json.Float( + _INKO.string_to_float(@string, start, @index).value as Float + )) } - fn mut exponent !! Error { + fn mut exponent -> Result[Nil, Error] { advance if current == MINUS or current == PLUS { advance } if digit?(current).false? { throw unexpected(current) } while digit?(current) { advance } + Result.Ok(nil) } fn mut advance_line { @@ -651,7 +659,7 @@ class pub Parser { if index < @size { _INKO.string_byte(@string, index) } else { EOF } } - fn mut identifier(name: String) !! Error { + fn mut identifier(name: String) -> Result[Nil, Error] { let mut index = 0 let max = name.size @@ -661,9 +669,11 @@ class pub Parser { advance index += 1 } + + Result.Ok(nil) } - fn mut separator(closing: Int) !! Error { + fn mut separator(closing: Int) -> Result[Nil, Error] { whitespace if current != closing { @@ -674,6 +684,8 @@ class pub Parser { if current == closing { throw unexpected(current) } } + + Result.Ok(nil) } fn mut whitespace { @@ -725,8 +737,8 @@ class pub Generator { # The `indent` argument specifies the number of spaces to use per indentation # level. If this value is less than or equal to zero, no indentation is # applied. - fn pub static new(indent: Int) -> Self { - Self { + fn pub static new(indent: Int) -> Generator { + Generator { @pretty = indent > 0, @spaces = ' '.repeat(indent), @depth = 0, @@ -824,7 +836,7 @@ class pub Generator { # # import std::json # -# try! json.parse('[10]') # => Json.Array([Json.Int(10)]) -fn pub parse(string: String) !! Error -> Json { - try Parser.new(string).parse +# json.parse('[10]').unwrap # => Json.Array([Json.Int(10)]) +fn pub parse(string: String) -> Result[Json, Error] { + Parser.new(string).parse } diff --git a/libstd/src/std/map.inko b/std/src/std/map.inko similarity index 77% rename from libstd/src/std/map.inko rename to std/src/std/map.inko index c4276d406..b371717a7 100644 --- a/libstd/src/std/map.inko +++ b/std/src/std/map.inko @@ -1,9 +1,8 @@ # A hash map using linear probing and Robin Hood entry stealing. import std::cmp::(Contains, Equal) -import std::drop::Drop import std::fmt::(Format, Formatter) import std::hash::(Hash, Hasher) -import std::index::(Index, IndexMut, SetIndex) +import std::hash::siphash::SipHasher13 import std::iter::Iter import std::process::(panic) @@ -18,7 +17,7 @@ let EMPTY = -1 let DEFAULT_CAPACITY = 4 # An entry stored in a Map. -class pub Entry[K: Hash + Equal, V] { +class pub Entry[K: Hash + Equal[K], V] { # The key that was hashed. let @key: K @@ -48,38 +47,6 @@ class pub Entry[K: Hash + Equal, V] { } } -# The default hasher to use for `Map` types. -# -# Given two different instances of a `DefaultHasher`, both produce the same hash -# code for a given value V. Hash codes are not persistent between restarts of -# the OS process, and as such should never be relied upon directly, persisted, -# or in any way be relied upon directly. -class pub DefaultHasher { - # The internal/native hasher. - let @hasher: Any - - # Returns a new `Hasher`. - fn pub static new -> Self { - Self { @hasher = _INKO.hasher_new } - } -} - -impl Drop for DefaultHasher { - fn mut drop { - _INKO.hasher_drop(@hasher) - } -} - -impl Hasher for DefaultHasher { - fn pub mut write(value: Int) { - _INKO.hasher_write_int(@hasher, value) - } - - fn pub hash -> Int { - _INKO.hasher_to_hash(@hasher) - } -} - # A hash map using linear probing and Robin Hood hashing. # # A `Map` preserves the order in which values are inserted, even when entries @@ -107,7 +74,7 @@ impl Hasher for DefaultHasher { # * http://codecapsule.com/2013/11/11/robin-hood-hashing/ # * http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/ # * https://www.sebastiansylvan.com/post/robin-hood-hashing-should-be-your-default-hash-table-implementation/ -class pub Map[K: Hash + Equal, V] { +class pub Map[K: Hash + Equal[K], V] { # The slots we can hash into. # # An index of `-1` indicates the slot isn't used. A value of `0` or more @@ -123,7 +90,7 @@ class pub Map[K: Hash + Equal, V] { let @resize_at: Int # Returns a new empty `Map`. - fn pub static new -> Self { + fn pub static new -> Map[K, V] { with_capacity(DEFAULT_CAPACITY) } @@ -135,14 +102,14 @@ class pub Map[K: Hash + Equal, V] { # # let map = Map.with_capacity(32) # - # map['name'] = 'Alice' - fn pub static with_capacity(amount: Int) -> Self { + # map.set('name', 'Alice') + fn pub static with_capacity(amount: Int) -> Map[K, V] { let size = if amount <= 0 { DEFAULT_CAPACITY } else { amount.nearest_power_of_two } let slots = Array.filled(with: EMPTY, times: size) let resize_at = resize_threshold(size) - Self { @slots = slots, @entries = [], @resize_at = resize_at } + Map { @slots = slots, @entries = [], @resize_at = resize_at } } # Removes the given key, returning its value if the key was present. @@ -159,13 +126,13 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.remove('name') # => Option.Some('Alice') fn pub mut remove(key: ref K) -> Option[V] { let mut slot = slot_index(hash_key(key)) let mut dist = 0 - let mut index = @slots[slot] + let mut index = @slots.get(slot) # For the removal we need both the slot and the entry index, so we have to # duplicate the logic of `entries_index()` here, as this is the only place @@ -173,24 +140,24 @@ class pub Map[K: Hash + Equal, V] { loop { if index == EMPTY { return Option.None } - let entry = @entries[index] + let entry = @entries.get(index) if dist > entry.distance { return Option.None } if entry.key == key { break } slot = slot_index(slot + 1) - index = @slots[slot] + index = @slots.get(slot) dist += 1 } let value = @entries.remove_at(index).into_value - @slots[slot] = EMPTY + @slots.set(slot, EMPTY) # Because we shifted the entries to the left, any slots pointing to entries # _after_ the removed value have to be updated accordingly. @slots.iter.each_with_index fn (slot, entry) { - if entry > index { @slots[slot] = entry - 1 } + if entry > index { @slots.set(slot, entry - 1) } } let mut prev_slot = slot @@ -198,15 +165,15 @@ class pub Map[K: Hash + Equal, V] { slot = slot_index(slot + 1) loop { - let mut index = @slots[slot] + let mut index = @slots.get(slot) if index == EMPTY { break } - let entry = @entries[index] + let entry = @entries.get_mut(index) if entry.distance > 0 { - @slots[slot] = EMPTY - @slots[prev_slot] = index + @slots.set(slot, EMPTY) + @slots.set(prev_slot, index) entry.distance -= 1 } else { @@ -228,13 +195,13 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.iter.each fn (entry) { # entry.key # => 'name' # entry.value # => 'Alice' # } - fn pub iter -> Iter[ref Entry[K, V], Never] { + fn pub iter -> Iter[ref Entry[K, V]] { @entries.iter } @@ -246,13 +213,13 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.iter_mut.each fn (entry) { # entry.key # => 'name' # entry.value # => 'Alice' # } - fn pub mut iter_mut -> Iter[mut Entry[K, V], Never] { + fn pub mut iter_mut -> Iter[mut Entry[K, V]] { @entries.iter_mut } @@ -263,13 +230,13 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.into_iter.each fn (e) { # e.key # => 'name' # e.value # => 'Alice' # } - fn pub move into_iter -> Iter[Entry[K, V], Never] { + fn pub move into_iter -> Iter[Entry[K, V]] { @entries.into_iter } @@ -281,12 +248,12 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.keys.each fn (key) { # key # => 'name' # } - fn pub keys -> Iter[ref K, Never] { + fn pub keys -> Iter[ref K] { iter.map fn (e) { e.key } } @@ -298,12 +265,12 @@ class pub Map[K: Hash + Equal, V] { # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.values.each fn (value) { # value # => 'Alice' # } - fn pub values -> Iter[ref V, Never] { + fn pub values -> Iter[ref V] { iter.map fn (e) { e.value } } @@ -315,30 +282,62 @@ class pub Map[K: Hash + Equal, V] { # # let map = Map.new # - # map.get('name') # => Option.None + # map.opt('name') # => Option.None # # Getting the value of an existing key: # # let mut map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # - # map.get('name') # => Option.Some('Alice') - fn pub get(key: ref K) -> Option[ref V] { + # map.opt('name') # => Option.Some('Alice') + fn pub opt(key: ref K) -> Option[ref V] { match entries_index(key) { case EMPTY -> Option.None - case index -> Option.Some(@entries[index].value) + case index -> Option.Some(@entries.get(index).value) } } - # Returns an optional mutable reference to the key's value. - fn pub mut get_mut(key: ref K) -> Option[mut V] { - match entries_index(key) { - case EMPTY -> Option.None - case index -> Option.Some(@entries[index].value) + # Returns an immutable reference to the value of the given key. + # + # # Panics + # + # This method panics if the key doesn't exist. + # + # # Examples + # + # Getting the value of an existing key: + # + # let mut map = Map.new + # + # map.set('name', 'Alice') + # map.get('name') # => 'Alice' + fn pub get(index: ref K) -> ref V { + match entries_index(index) { + case EMPTY -> panic("The key doesn't exist") + case index -> @entries.get(index).value } } + # Inserts the key and value in this `Map`, returning the inserted value. + # + # # Examples + # + # Inserting a new key-value pair: + # + # let mut map = Map.new + # + # map.set('name', 'Alice') # => 'Alice' + fn pub mut set(index: K, value: V) { + if length >= @resize_at { resize } + + let hash = hash_key(index) + let entry = + Entry { @key = index, @value = value, @hash = hash, @distance = 0 } + + insert_entry(entry) + } + # Merges two `Map` objects together. # # # Examples @@ -346,14 +345,14 @@ class pub Map[K: Hash + Equal, V] { # let map1 = Map.new # let map2 = Map.new # - # map1['name'] = 'Alice' - # map2['city'] = 'Amsterdam' + # map1.set('name', 'Alice') + # map2.set('city', 'Amsterdam') # # map1.merge(map2) # # map1['name'] # => 'Alice' # map2['city'] # => 'Amsterdam' - fn pub mut merge(other: Self) { + fn pub mut merge(other: Map[K, V]) { other.into_iter.each fn (entry) { entry.distance = 0 entry.hash = hash_key(entry.key) @@ -376,7 +375,7 @@ class pub Map[K: Hash + Equal, V] { # # let map = Map.new # - # map['name'] = 'Alice' + # map.set('name', 'Alice') # # map.length # => 1 fn pub length -> Int { @@ -405,18 +404,18 @@ class pub Map[K: Hash + Equal, V] { let mut slot = slot_index(rehash.hash) loop { - let index = @slots[slot] + let index = @slots.get(slot) if index == EMPTY { - @slots[slot] = rehash_index + @slots.set(slot, rehash_index) return } - let entry = @entries[index] + let entry = @entries.get(index) if entry.distance < rehash.distance { - @slots[slot] = rehash_index + @slots.set(slot, rehash_index) shift_stolen_slots(slot, index) return @@ -432,16 +431,16 @@ class pub Map[K: Hash + Equal, V] { let mut slot = slot_index(insert.hash) loop { - let index = @slots[slot] + let index = @slots.get(slot) if index == EMPTY { - @slots[slot] = @entries.length + @slots.set(slot, @entries.length) @entries.push(insert) return } - let entry = @entries[index] + let entry = @entries.get_mut(index) if entry.key == insert.key { entry.value = insert.into_value @@ -450,7 +449,7 @@ class pub Map[K: Hash + Equal, V] { } if entry.distance < insert.distance { - @slots[slot] = @entries.length + @slots.set(slot, @entries.length) @entries.push(insert) shift_stolen_slots(slot, index) @@ -479,23 +478,23 @@ class pub Map[K: Hash + Equal, V] { # at the next one. let mut slot = slot_index(start_slot + 1) let mut stolen_index = start_index - let mut stolen = @entries[stolen_index] + let mut stolen = @entries.get_mut(stolen_index) loop { stolen.distance += 1 - let index = @slots[slot] + let index = @slots.get(slot) if index == EMPTY { - @slots[slot] = stolen_index + @slots.set(slot, stolen_index) return } - let entry = @entries[index] + let entry = @entries.get_mut(index) if entry.distance < stolen.distance { - @slots[slot] = stolen_index + @slots.set(slot, stolen_index) stolen_index = index stolen = entry } @@ -509,11 +508,11 @@ class pub Map[K: Hash + Equal, V] { let mut dist = 0 loop { - let index = @slots[slot] + let index = @slots.get(slot) if index == EMPTY { return EMPTY } - let entry = @entries[index] + let entry = @entries.get(index) if dist > entry.distance { return EMPTY } if entry.key == key { return index } @@ -524,10 +523,10 @@ class pub Map[K: Hash + Equal, V] { } fn hash_key(key: ref K) -> Int { - let hasher: Hasher = DefaultHasher.new + let hasher: Hasher = SipHasher13.default key.hash(mut hasher) - hasher.hash + hasher.finish } fn slot_index(hash: Int) -> Int { @@ -537,89 +536,52 @@ class pub Map[K: Hash + Equal, V] { } } -impl Equal for Map if V: Equal { - # Returns `true` if `self` and the given `Map` are identical to each - # other. - # - # # Examples - # - # Comparing two `Map` instances: - # - # let map1 = Map.new - # let map2 = Map.new - # - # map1['name'] = 'Alice' - # map2['name'] = 'Alice' - # - # map1 == map2 # => true - fn pub ==(other: ref Self) -> Bool { - if length != other.length { return false } - - iter.all? fn (ours) { - match other.entries_index(ours.key) { - case EMPTY -> false - case index -> other.entries[index].value == ours.value - } - } - } -} - -impl Index[ref K, ref V] for Map { - # Returns a reference to the value of the given key. - # - # # Examples - # - # Getting the value of an existing key: - # - # let mut map = Map.new - # - # map['name'] = 'Alice' - # - # map['name'] # => 'Alice' - # - # # Panics - # - # This method panics if the key doesn't exist. - fn pub index(index: ref K) -> ref V { - match entries_index(index) { - case EMPTY -> panic("The key doesn't exist") - case index -> @entries[index].value +impl Map if V: mut { + # Returns an optional mutable reference to the key's value. + fn pub mut opt_mut(key: ref K) -> Option[mut V] { + match entries_index(key) { + case EMPTY -> Option.None + case index -> Option.Some(@entries.get_mut(index).value) } } -} -impl IndexMut[ref K, mut V] for Map { # Returns a mutable reference to the value of the given key. # # # Panics # # This method panics if the key doesn't exist. - fn pub mut index_mut(index: ref K) -> mut V { + fn pub mut get_mut(index: ref K) -> mut V { match entries_index(index) { case EMPTY -> panic("The key doesn't exist") - case index -> @entries[index].value + case index -> @entries.get_mut(index).value } } } -impl SetIndex[K, V] for Map { - # Inserts the key and value in this `Map`, returning the inserted value. +impl Equal[Map[K, V]] for Map if V: Equal[V] { + # Returns `true` if `self` and the given `Map` are identical to each + # other. # # # Examples # - # Inserting a new key-value pair: + # Comparing two `Map` instances: # - # let mut map = Map.new + # let map1 = Map.new + # let map2 = Map.new # - # map['name'] = 'Alice' # => 'Alice' - fn pub mut set_index(index: K, value: V) { - if length >= @resize_at { resize } - - let hash = hash_key(index) - let entry = - Entry { @key = index, @value = value, @hash = hash, @distance = 0 } + # map1.set('name', 'Alice') + # map2.set('name', 'Alice') + # + # map1 == map2 # => true + fn pub ==(other: ref Map[K, V]) -> Bool { + if length != other.length { return false } - insert_entry(entry) + iter.all? fn (ours) { + match other.entries_index(ours.key) { + case EMPTY -> false + case index -> other.entries.get(index).value == ours.value + } + } } } @@ -641,8 +603,7 @@ impl Contains[K] for Map { # # let map = Map.new # - # map['name'] = 'Alice' - # + # map.set('name', 'Alice') # map.contains?('name') # => true # map.contains?('city') # => false fn pub contains?(value: ref K) -> Bool { diff --git a/libstd/src/std/net/ip.inko b/std/src/std/net/ip.inko similarity index 91% rename from libstd/src/std/net/ip.inko rename to std/src/std/net/ip.inko index bb0512fe1..b4ef114d6 100644 --- a/libstd/src/std/net/ip.inko +++ b/std/src/std/net/ip.inko @@ -57,7 +57,7 @@ class pub enum IpAddress { # import std::net::ip::IpAddress # # IpAdress.v4(127, 0, 0, 1) - fn pub static v4(a: Int, b: Int, c: Int, d: Int) -> Self { + fn pub static v4(a: Int, b: Int, c: Int, d: Int) -> IpAddress { V4(Ipv4Address.new(a, b, c, d)) } @@ -79,7 +79,7 @@ class pub enum IpAddress { f: Int, g: Int, h: Int - ) -> Self { + ) -> IpAddress { V6(Ipv6Address.new(a, b, c, d, e, f, g, h)) } @@ -101,7 +101,7 @@ class pub enum IpAddress { # import std::net::ip::IpAddress # # IpAddress.parse('::1') # => Option.Some(IpAddress.V6(Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 1))) - fn pub static parse(address: String) -> Option[Self] { + fn pub static parse(address: String) -> Option[IpAddress] { if address.contains?(':') { Ipv6Address.parse(address).map fn (v) { V6(v) } } else { @@ -158,8 +158,8 @@ class pub enum IpAddress { } } -impl Equal for IpAddress { - fn pub ==(other: ref Self) -> Bool { +impl Equal[IpAddress] for IpAddress { + fn pub ==(other: ref IpAddress) -> Bool { match self { case V4(a) -> match other { case V4(b) -> a == b @@ -191,8 +191,8 @@ impl ToString for IpAddress { } } -impl Clone for IpAddress { - fn pub clone -> Self { +impl Clone[IpAddress] for IpAddress { + fn pub clone -> IpAddress { match self { case V4(ip) -> IpAddress.V4(ip.clone) case V6(ip) -> IpAddress.V6(ip.clone) @@ -229,7 +229,7 @@ class pub Ipv6Address { # import std::net::ip::Ipv6Address # # Ipv6Address.parse('::1').unwrap.v6? # => true - fn pub static parse(input: String) -> Option[Self] { + fn pub static parse(input: String) -> Option[Ipv6Address] { let bytes = input.to_byte_array let mut cursor = 0 let max = bytes.length @@ -248,17 +248,17 @@ class pub Ipv6Address { # IPv6 addresses can embed IPv4 addresses, so instead of reading until we # encounter a ":" we will also stop reading when running into a ".". while cursor < max - and bytes[cursor] != COLON_BYTE - and bytes[cursor] != DOT_BYTE + and bytes.get(cursor) != COLON_BYTE + and bytes.get(cursor) != DOT_BYTE { - segment_bytes.push(bytes[cursor]) + segment_bytes.push(bytes.get(cursor)) cursor += 1 } # The moment we encounter a dot we'll enter IPv4 mode, and remain in this # mode until we reach the end of the input, as embedded IPv4 addresses # must be at the end of an IPv6 address. - if ipv4_mode.false? and cursor < max and bytes[cursor] == DOT_BYTE { + if ipv4_mode.false? and cursor < max and bytes.get(cursor) == DOT_BYTE { ipv4_mode = true base16 = false max_hextet_value = IPV4_OCTET_MAXIMUM @@ -290,7 +290,7 @@ class pub Ipv6Address { # We have reached another ":", which is used to compress one or more empty # groups together. - if cursor < max and bytes[cursor] == COLON_BYTE { + if cursor < max and bytes.get(cursor) == COLON_BYTE { # Zero compression can only be applied once. if compressed { return Option.None } @@ -307,7 +307,7 @@ class pub Ipv6Address { # When the compression is at the end of the input (e.g. "1::") there is # no point in looking ahead, so we don't. while pad_cursor < max and look_ahead { - let byte = bytes[pad_cursor] + let byte = bytes.get(pad_cursor) if byte == COLON_BYTE { pad -= 1 } @@ -333,21 +333,21 @@ class pub Ipv6Address { } if ipv4_segments.length == IPV4_OCTETS { - segments.push(octets_to_hextet(ipv4_segments[0], ipv4_segments[1])) - segments.push(octets_to_hextet(ipv4_segments[2], ipv4_segments[3])) + segments.push(octets_to_hextet(ipv4_segments.get(0), ipv4_segments.get(1))) + segments.push(octets_to_hextet(ipv4_segments.get(2), ipv4_segments.get(3))) } if segments.length != IPV6_HEXTETS { return Option.None } Option.Some(Ipv6Address.new( - segments[0], - segments[1], - segments[2], - segments[3], - segments[4], - segments[5], - segments[6], - segments[7], + segments.get(0), + segments.get(1), + segments.get(2), + segments.get(3), + segments.get(4), + segments.get(5), + segments.get(6), + segments.get(7), )) } @@ -361,8 +361,10 @@ class pub Ipv6Address { f: Int, g: Int, h: Int - ) -> Self { - Self { @a = a, @b = b, @c = c, @d = d, @e = e, @f = f, @g = g, @h = h } + ) -> Ipv6Address { + Ipv6Address { + @a = a, @b = b, @c = c, @d = d, @e = e, @f = f, @g = g, @h = h + } } # Returns `true` if `self` is an IPv4-compatible IPv6 address. @@ -473,7 +475,7 @@ impl Format for Ipv6Address { } } -impl Equal for Ipv6Address { +impl Equal[Ipv6Address] for Ipv6Address { # Returns `true` if `self` and the given IP address are the same. # # # Examples @@ -488,7 +490,7 @@ impl Equal for Ipv6Address { # # addr1 == addr2 # => true # addr1 == addr3 # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Ipv6Address) -> Bool { @a == other.a and @b == other.b and @c == other.c @@ -544,7 +546,7 @@ impl ToString for Ipv6Address { # Find the longest sequence of empty hextets, which we will compress # together. while index < segments.length { - let hextet = segments[index] + let hextet = segments.get(index) if hextet == 0 { if current_len == 0 { current_at = index } @@ -591,9 +593,9 @@ impl IntoString for Ipv6Address { } } -impl Clone for Ipv6Address { - fn pub clone -> Self { - Self { +impl Clone[Ipv6Address] for Ipv6Address { + fn pub clone -> Ipv6Address { + Ipv6Address { @a = @a, @b = @b, @c = @c, @@ -624,7 +626,7 @@ class pub Ipv4Address { # let addr = Ipv4Address.parse('1.2.3.4').unwrap # # addr.v4? # => true - fn pub static parse(input: String) -> Option[Self] { + fn pub static parse(input: String) -> Option[Ipv4Address] { # No IPv4 address can be longer than 15 characters (255.255.255.255). if input.size > 15 { return Option.None } @@ -654,16 +656,16 @@ class pub Ipv4Address { if segments.length != IPV4_OCTETS { return Option.None } Option.Some(Ipv4Address { - @a = segments[0], - @b = segments[1], - @c = segments[2], - @d = segments[3] + @a = segments.get(0), + @b = segments.get(1), + @c = segments.get(2), + @d = segments.get(3), }) } # Returns a new IPv4 address using the given octets. - fn pub static new(a: Int, b: Int, c: Int, d: Int) -> Self { - Self { @a = a, @b = b, @c = c, @d = d } + fn pub static new(a: Int, b: Int, c: Int, d: Int) -> Ipv4Address { + Ipv4Address { @a = a, @b = b, @c = c, @d = d } } # Returns `true` if `self` is a broadcast address (255.255.255.255). @@ -844,7 +846,7 @@ impl Format for Ipv4Address { } } -impl Equal for Ipv4Address { +impl Equal[Ipv4Address] for Ipv4Address { # Returns `true` if `self` and the given IP address are the same. # # # Examples @@ -859,7 +861,7 @@ impl Equal for Ipv4Address { # # addr1 == addr2 # => true # addr1 == addr3 # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Ipv4Address) -> Bool { @a == other.a and @b == other.b and @c == other.c and @d == other.d } } @@ -886,8 +888,8 @@ impl IntoString for Ipv4Address { } } -impl Clone for Ipv4Address { - fn pub clone -> Self { - Self { @a = @a, @b = @b, @c = @c, @d = @d } +impl Clone[Ipv4Address] for Ipv4Address { + fn pub clone -> Ipv4Address { + Ipv4Address { @a = @a, @b = @b, @c = @c, @d = @d } } } diff --git a/libstd/src/std/net/socket.inko b/std/src/std/net/socket.inko similarity index 54% rename from libstd/src/std/net/socket.inko rename to std/src/std/net/socket.inko index e6c175250..2e6a80006 100644 --- a/libstd/src/std/net/socket.inko +++ b/std/src/std/net/socket.inko @@ -42,11 +42,11 @@ # import std::net::socket::TcpServer # import std::time::Duration # -# let server = try! TcpServer.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9000) +# let server = TcpServer.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9000).unwrap # let _guard = server.socket.timeout_after = Duration.from_secs(3) # # # This times out after roughly three seconds. -# try! server.accept +# server.accept.unwrap # # For more information about timeouts versus deadlines, consider reading [this # article](https://vorpus.org/blog/timeouts-and-cancellation-for-humans/). @@ -70,6 +70,10 @@ let MAXIMUM_LISTEN_BACKLOG = 65_535 # A value that signals the lack of a socket deadline. let NO_DEADLINE = -1 +let IPV4 = 0 +let IPV6 = 1 +let UNIX = 2 + # The type of a socket. class pub enum Type { # The type corresponding to `SOCK_STREAM`. @@ -106,8 +110,8 @@ class pub SocketAddress { # The port number of this socket address. let pub @port: Int - fn pub static new(address: String, port: Int) -> Self { - Self { @address = address, @port = port } + fn pub static new(address: String, port: Int) -> SocketAddress { + SocketAddress { @address = address, @port = port } } # Returns the IPv4/IPv6 address associated with `self`. @@ -116,9 +120,9 @@ class pub SocketAddress { } } -impl Equal for SocketAddress { +impl Equal[SocketAddress] for SocketAddress { # Returns `true` if `self` and `other` are the same. - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref SocketAddress) -> Bool { @address == other.address and @port == other.port } } @@ -155,12 +159,14 @@ class pub Socket { # # import std::net::socket::(Type, Socket) # - # try! Socket.ipv4(Type.DGRAM) - fn pub static ipv4(type: Type) !! Error -> Self { - let id = type.into_int - let fd = try _INKO.socket_allocate_ipv4(id) else (e) throw Error.from_int(e) - - Self { @fd = fd, @deadline = NO_DEADLINE } + # Socket.ipv4(Type.DGRAM).unwrap + fn pub static ipv4(type: Type) -> Result[Socket, Error] { + match _INKO.socket_new(IPV4, type.into_int) { + case { @tag = 0, @value = v } -> Result.Ok( + Socket { @fd = v, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Creates a new IPv6 socket. @@ -169,12 +175,14 @@ class pub Socket { # # import std::net::socket::(Type, Socket) # - # try! Socket.ipv6(Type.DGRAM) - fn pub static ipv6(type: Type) !! Error -> Self { - let id = type.into_int - let fd = try _INKO.socket_allocate_ipv6(id) else (e) throw Error.from_int(e) - - Self { @fd = fd, @deadline = NO_DEADLINE } + # Socket.ipv6(Type.DGRAM).unwrap + fn pub static ipv6(type: Type) -> Result[Socket, Error] { + match _INKO.socket_new(IPV6, type.into_int) { + case { @tag = 0, @value = v } -> Result.Ok( + Socket { @fd = v, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the point in time after which socket operations must time out, known as @@ -222,13 +230,14 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let socket = try! Socket.ipv4(Type.DGRAM) + # let socket = Socket.ipv4(Type.DGRAM).unwrap # - # try! socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - fn pub mut bind(ip: ref ToString, port: Int) !! Error { - let addr = ip.to_string - - try _INKO.socket_bind(@fd, addr, port) else (e) throw Error.from_int(e) + # socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + fn pub mut bind(ip: ref ToString, port: Int) -> Result[Nil, Error] { + match _INKO.socket_bind(@fd, ip.to_string, port) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Connects this socket to the specified address. @@ -240,19 +249,16 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let listener = try! Socket.ipv4(Type.STREAM) - # let client = try! Socket.ipv4(Type.STREAM) + # let listener = Socket.ipv4(Type.STREAM).unwrap + # let client = Socket.ipv4(Type.STREAM).unwrap # - # try! socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! socket.listen - # try! client.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - fn pub mut connect(ip: ref ToString, port: Int) !! Error { - let addr = ip.to_string - - try { - _INKO.socket_connect(@fd, addr, port, @deadline) - } else (err) { - throw Error.from_int(err) + # socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # socket.listen.unwrap + # client.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + fn pub mut connect(ip: ref ToString, port: Int) -> Result[Nil, Error] { + match _INKO.socket_connect(@fd, ip.to_string, port, @deadline) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -266,15 +272,14 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let socket = try! Socket.ipv4(Type.STREAM) + # let socket = Socket.ipv4(Type.STREAM).unwrap # - # try! socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! socket.listen - fn pub mut listen !! Error { - try { - _INKO.socket_listen(@fd, MAXIMUM_LISTEN_BACKLOG) - } else (err) { - throw Error.from_int(err) + # socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # socket.listen.unwrap + fn pub mut listen -> Result[Nil, Error] { + match _INKO.socket_listen(@fd, MAXIMUM_LISTEN_BACKLOG) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -289,29 +294,28 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let listener = try! Socket.ipv4(Type.STREAM) - # let stream = try! Socket.ipv4(Type.STREAM) + # let listener = Socket.ipv4(Type.STREAM).unwrap + # let stream = Socket.ipv4(Type.STREAM).unwrap # - # try! listener.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! listener.listen + # listener.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # listener.listen.unwrap # - # try! stream.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! stream.write_string('ping') + # stream.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # stream.write_string('ping').unwrap # - # let client = try! listener.accept + # let client = listener.accept.unwrap # let buffer = ByteArray.new # - # try! client.read(into: buffer, size: 4) + # client.read(into: buffer, size: 4).unwrap # # buffer.to_string # => 'ping' - fn pub accept !! Error -> Socket { - let fd = try { - _INKO.socket_accept_ip(@fd, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub accept -> Result[Socket, Error] { + match _INKO.socket_accept(@fd, @deadline) { + case { @tag = 0, @value = fd } -> Result.Ok( + Socket { @fd = fd, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } - - Socket { @fd = fd, @deadline = NO_DEADLINE } } # Sends a `String` to the given address. @@ -323,23 +327,24 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let socket = try! Socket.ipv4(Type.DGRAM) + # let socket = Socket.ipv4(Type.DGRAM).unwrap # - # try! socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! socket.send_string_to( - # string: 'hello', - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 9999 - # ) + # socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # socket + # .send_string_to( + # string: 'hello', + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 9999 + # ) + # .unwrap fn pub mut send_string_to( string: String, ip: ref ToString, port: Int - ) !! Error -> Int { - try { - _INKO.socket_send_string_to(@fd, string, ip.to_string, port, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[Int, Error] { + match _INKO.socket_send_string_to(@fd, string, ip.to_string, port, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -352,24 +357,25 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let socket = try! Socket.ipv4(Type.DGRAM) + # let socket = Socket.ipv4(Type.DGRAM).unwrap # let bytes = 'hello'.to_byte_array # - # try! socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # try! socket.send_bytes_to( - # bytes: bytes, - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 9999 - # ) + # socket.bind(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # socket + # .send_bytes_to( + # bytes: bytes, + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 9999 + # ) + # .unwrap fn pub mut send_bytes_to( bytes: ByteArray, ip: ref ToString, port: Int - ) !! Error -> Int { - try { - _INKO.socket_send_bytes_to(@fd, bytes, ip.to_string, port, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[Int, Error] { + match _INKO.socket_send_bytes_to(@fd, bytes, ip.to_string, port, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -386,16 +392,18 @@ class pub Socket { # import std::net::socket::(Socket, Type) # import std::net::ip::IpAddress # - # let socket = try! Socket.ipv4(Type.DGRAM) + # let socket = Socket.ipv4(Type.DGRAM).unwrap # let bytes = ByteArray.new # - # try! socket.send_string_to( - # 'hello', - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 9999 - # ) + # socket + # .send_string_to( + # 'hello', + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 9999 + # ) + # .unwrap # - # let received_from = try! socket.receive_from(bytes: bytes, size: 5) + # let received_from = socket.receive_from(bytes: bytes, size: 5).unwrap # # bytes.to_string # => 'hello' # received_from.address # => '0.0.0.0' @@ -403,180 +411,165 @@ class pub Socket { fn pub mut receive_from( bytes: ref ByteArray, size: Int - ) !! Error -> SocketAddress { - let raw_addr = try { - _INKO.socket_receive_from(@fd, bytes, size, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[SocketAddress, Error] { + let raw_addr = match _INKO.socket_receive_from(@fd, bytes, size, @deadline) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) } let addr = _INKO.socket_address_pair_address(raw_addr) let port = _INKO.socket_address_pair_port(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - SocketAddress.new(addr, port) + Result.Ok(SocketAddress.new(addr, port)) } # Returns the local address of this socket. - fn pub local_address !! Error -> SocketAddress { - let raw_addr = - try _INKO.socket_local_address(@fd) else (e) throw Error.from_int(e) + fn pub local_address -> Result[SocketAddress, Error] { + let raw_addr = match _INKO.socket_local_address(@fd) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + } + let addr = _INKO.socket_address_pair_address(raw_addr) let port = _INKO.socket_address_pair_port(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - SocketAddress.new(addr, port) + Result.Ok(SocketAddress.new(addr, port)) } # Returns the peer address of this socket. - fn pub peer_address !! Error -> SocketAddress { - let raw_addr = - try _INKO.socket_peer_address(@fd) else (e) throw Error.from_int(e) + fn pub peer_address -> Result[SocketAddress, Error] { + let raw_addr = match _INKO.socket_peer_address(@fd) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + } + let addr = _INKO.socket_address_pair_address(raw_addr) let port = _INKO.socket_address_pair_port(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - SocketAddress.new(addr, port) - } - - # Returns the value of the `IP_TTL` option. - fn pub ttl !! Error -> Int { - try _INKO.socket_get_ttl(@fd) else (e) throw Error.from_int(e) + Result.Ok(SocketAddress.new(addr, port)) } # Sets the value of the `IP_TTL` option. - fn pub mut ttl=(value: Int) !! Error { - try _INKO.socket_set_ttl(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `IPV6_V6ONLY` option. - fn pub only_ipv6? !! Error -> Bool { - try _INKO.socket_get_only_v6(@fd) else (e) throw Error.from_int(e) + fn pub mut ttl=(value: Int) -> Result[Nil, Error] { + match _INKO.socket_set_ttl(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `IPV6_V6ONLY` option. - fn pub mut only_ipv6=(value: Bool) !! Error { - try _INKO.socket_set_only_v6(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `TCP_NODELAY` option. - fn pub no_delay? !! Error -> Bool { - try _INKO.socket_get_nodelay(@fd) else (e) throw Error.from_int(e) + fn pub mut only_ipv6=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_only_v6(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `TCP_NODELAY` option. - fn pub mut no_delay=(value: Bool) !! Error { - try _INKO.socket_set_nodelay(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_BROADCAST` option. - fn pub broadcast? !! Error -> Bool { - try _INKO.socket_get_broadcast(@fd) else (e) throw Error.from_int(e) + fn pub mut no_delay=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_nodelay(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_BROADCAST` option. - fn pub mut broadcast=(value: Bool) !! Error { - try _INKO.socket_set_broadcast(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_LINGER` option. - fn pub linger !! Error -> Duration { - let nanos = - try _INKO.socket_get_linger(@fd) else (e) throw Error.from_int(e) - - Duration.from_nanos(nanos) + fn pub mut broadcast=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_broadcast(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_LINGER` option. - fn pub mut linger=(value: ref Duration) !! Error { - let nanos = value.to_nanos - - try _INKO.socket_set_linger(@fd, nanos) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_RCVBUF` option. - fn pub receive_buffer_size !! Error -> Int { - try _INKO.socket_get_recv_size(@fd) else (e) throw Error.from_int(e) + fn pub mut linger=(value: ref Duration) -> Result[Nil, Error] { + match _INKO.socket_set_linger(@fd, value.to_nanos) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_RCVBUF` option. - fn pub mut receive_buffer_size=(value: Int) !! Error { - try _INKO.socket_set_recv_size(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_SNDBUF` option. - fn pub send_buffer_size !! Error -> Int { - try _INKO.socket_get_send_size(@fd) else (e) throw Error.from_int(e) + fn pub mut receive_buffer_size=(value: Int) -> Result[Nil, Error] { + match _INKO.socket_set_recv_size(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_SNDBUF` option. - fn pub mut send_buffer_size=(value: Int) !! Error { - try _INKO.socket_set_send_size(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_KEEPALIVE` option. - fn pub keepalive !! Error -> Bool { - try _INKO.socket_get_keepalive(@fd) else (e) throw Error.from_int(e) + fn pub mut send_buffer_size=(value: Int) -> Result[Nil, Error] { + match _INKO.socket_set_send_size(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_KEEPALIVE` option. - fn pub mut keepalive=(value: Bool) !! Error { - try _INKO.socket_set_keepalive(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_REUSEADDR` option. - fn pub reuse_address !! Error -> Bool { - try _INKO.socket_get_reuse_address(@fd) else (e) throw Error.from_int(e) + fn pub mut keepalive=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_keepalive(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_REUSEADDR` option. - fn pub mut reuse_address=(value: Bool) !! Error { - try { - _INKO.socket_set_reuse_address(@fd, value) - } else (err) { - throw Error.from_int(err) + fn pub mut reuse_address=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_reuse_address(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - # Returns the value of the `SO_REUSEPORT` option. - # - # Not all platforms may support this option, in which case the returned value - # will be `false`. - fn pub reuse_port !! Error -> Bool { - try _INKO.socket_get_reuse_port(@fd) else (e) throw Error.from_int(e) - } - # Sets the value of the `SO_REUSEPORT` option. # # Not all platforms may support this option, in which case the supplied # argument will be ignored. - fn pub mut reuse_port=(value: Bool) !! Error { - try _INKO.socket_set_reuse_port(@fd, value) else (e) throw Error.from_int(e) + fn pub mut reuse_port=(value: Bool) -> Result[Nil, Error] { + match _INKO.socket_set_reuse_port(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down the reading half of this socket. - fn pub mut shutdown_read !! Error { - try _INKO.socket_shutdown_read(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown_read -> Result[Nil, Error] { + match _INKO.socket_shutdown_read(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down the writing half of this socket. - fn pub mut shutdown_write !! Error { - try _INKO.socket_shutdown_write(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown_write -> Result[Nil, Error] { + match _INKO.socket_shutdown_write(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down both the reading and writing half of this socket. - fn pub mut shutdown !! Error { - try _INKO.socket_shutdown_read_write(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown -> Result[Nil, Error] { + match _INKO.socket_shutdown_read_write(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - let fd = try _INKO.socket_try_clone(@fd) else (e) throw Error.from_int(e) - - Self { @fd = fd, @deadline = NO_DEADLINE } + fn pub try_clone -> Result[Socket, Error] { + match _INKO.socket_try_clone(@fd) { + case { @tag = 0, @value = fd } -> Result.Ok( + Socket { @fd = fd, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } } @@ -587,34 +580,31 @@ impl Drop for Socket { } impl Read for Socket { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try { - _INKO.socket_read(@fd, into, size, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.socket_read(@fd, into, size, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } } impl Write for Socket { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try { - _INKO.socket_write_bytes(@fd, bytes, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.socket_write_bytes(@fd, bytes, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut write_string(string: String) !! Error -> Int { - try { - _INKO.socket_write_string(@fd, string, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.socket_write_string(@fd, string, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut flush { - # Sockets can't be flushed, so this method is just a noop. + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) } } @@ -635,10 +625,10 @@ class pub UdpSocket { # import std::net::socket::UdpSocket # import std::net::ip::IpAddress # - # let ip = try! IpAddress.parse('0.0.0.0') + # let ip = IpAddress.parse('0.0.0.0').unwrap # - # try! UdpSocket.new(ip, port: 0) - fn pub static new(ip: IpAddress, port: Int) !! Error -> Self { + # UdpSocket.new(ip, port: 0).unwrap + fn pub static new(ip: IpAddress, port: Int) -> Result[UdpSocket, Error] { let socket = if ip.v6? { try Socket.ipv6(Type.DGRAM) } else { @@ -646,8 +636,7 @@ class pub UdpSocket { } try socket.bind(ip, port) - - Self { @socket = socket } + Result.Ok(UdpSocket { @socket = socket }) } # Connects `self` to the remote address. @@ -664,13 +653,13 @@ class pub UdpSocket { # import std::net::ip::IpAddress # # let socket1 = - # try! UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 40_000) + # UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 40_000).unwrap # let socket2 = - # try! UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 41_000) + # UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 41_000).unwrap # - # try! socket1.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 41_000) - fn pub mut connect(ip: ref ToString, port: Int) !! Error { - try @socket.connect(ip, port) + # socket1.connect(ip: IpAddress.v4(0, 0, 0, 0), port: 41_000).unwrap + fn pub mut connect(ip: ref ToString, port: Int) -> Result[Nil, Error] { + @socket.connect(ip, port) } # Sends a `String` to the given address. @@ -683,19 +672,21 @@ class pub UdpSocket { # import std::net::ip::IpAddress # # let socket = - # try! UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) - # - # try! socket.send_string_to( - # string: 'hello', - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 9999 - # ) + # UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap + # + # socket + # .send_string_to( + # string: 'hello', + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 9999 + # ) + # .unwrap fn pub mut send_string_to( string: String, ip: ref ToString, port: Int - ) !! Error -> Int { - try @socket.send_string_to(string, ip, port) + ) -> Result[Int, Error] { + @socket.send_string_to(string, ip, port) } # Sends a `ByteArray` to the given address. @@ -708,20 +699,22 @@ class pub UdpSocket { # import std::net::ip::IpAddress # # let socket = - # try! UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9999) + # UdpSocket.new(ip: IpAddress.v4(0, 0, 0, 0), port: 9999).unwrap # let bytes = 'hello'.to_byte_array # - # try! socket.send_bytes_to( - # bytes: bytes, - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 9999 - # ) + # socket + # .send_bytes_to( + # bytes: bytes, + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 9999 + # ) + # .unwrap fn pub mut send_bytes_to( bytes: ByteArray, ip: ref ToString, port: Int - ) !! Error -> Int { - try @socket.send_bytes_to(bytes, ip, port) + ) -> Result[Int, Error] { + @socket.send_bytes_to(bytes, ip, port) } # Receives a single datagram message on the socket, returning the address the @@ -731,42 +724,44 @@ class pub UdpSocket { fn pub mut receive_from( bytes: ref ByteArray, size: Int - ) !! Error -> SocketAddress { - try @socket.receive_from(bytes, size) + ) -> Result[SocketAddress, Error] { + @socket.receive_from(bytes, size) } # Returns the local address of this socket. # # See the documentation of `Socket.local_address` for more information. - fn pub local_address !! Error -> SocketAddress { - try @socket.local_address + fn pub local_address -> Result[SocketAddress, Error] { + @socket.local_address } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[UdpSocket, Error] { + @socket.try_clone.map fn (sock) { UdpSocket { @socket = sock } } } } impl Read for UdpSocket { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try @socket.read(into, size) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + @socket.read(into, size) } } impl Write for UdpSocket { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try @socket.write_bytes(bytes) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + @socket.write_bytes(bytes) } - fn pub mut write_string(string: String) !! Error -> Int { - try @socket.write_string(string) + fn pub mut write_string(string: String) -> Result[Int, Error] { + @socket.write_string(string) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } # A TCP socket connected to another TCP socket. @@ -788,10 +783,10 @@ class pub TcpClient { # import std::net::socket::TcpClient # import std::net::ip::IpAddress # - # let ip = try! IpAddress.parse('127.0.0.1') + # let ip = IpAddress.parse('127.0.0.1').unwrap # - # try! TcpClient.new(ip, port: 40_000) - fn pub static new(ip: IpAddress, port: Int) !! Error -> Self { + # TcpClient.new(ip, port: 40_000).unwrap + fn pub static new(ip: IpAddress, port: Int) -> Result[TcpClient, Error] { let socket = if ip.v6? { try Socket.ipv6(Type.STREAM) } else { @@ -799,8 +794,7 @@ class pub TcpClient { } try socket.connect(ip, port) - - Self { @socket = socket } + Result.Ok(TcpClient { @socket = socket }) } # Creates a new `TcpClient` but limits the amount of time spent waiting for @@ -817,16 +811,18 @@ class pub TcpClient { # import std::net::ip::IpAddress # import std::time::Duration # - # try! TcpClient.with_timeout( - # ip: IpAddress.v4(0, 0, 0, 0), - # port: 40_000, - # timeout_after: Duration.from_secs(5) - # ) + # TcpClient + # .with_timeout( + # ip: IpAddress.v4(0, 0, 0, 0), + # port: 40_000, + # timeout_after: Duration.from_secs(5) + # ) + # .unwrap fn pub static with_timeout( ip: IpAddress, port: Int, timeout_after: ToInstant - ) !! Error -> Self { + ) -> Result[TcpClient, Error] { let socket = if ip.v6? { try Socket.ipv6(Type.STREAM) } else { @@ -836,64 +832,65 @@ class pub TcpClient { let _ = socket.timeout_after = timeout_after try socket.connect(ip, port) - - Self { @socket = socket } + Result.Ok(TcpClient { @socket = socket }) } # Returns the local address of this socket. # # See the documentation of `Socket.local_address` for more information. - fn pub local_address !! Error -> SocketAddress { - try @socket.local_address + fn pub local_address -> Result[SocketAddress, Error] { + @socket.local_address } # Returns the peer address of this socket. # # See the documentation of `Socket.peer_address` for more information. - fn pub peer_address !! Error -> SocketAddress { - try @socket.peer_address + fn pub peer_address -> Result[SocketAddress, Error] { + @socket.peer_address } # Shuts down the reading half of this socket. - fn pub mut shutdown_read !! Error { - try @socket.shutdown_read + fn pub mut shutdown_read -> Result[Nil, Error] { + @socket.shutdown_read } # Shuts down the writing half of this socket. - fn pub mut shutdown_write !! Error { - try @socket.shutdown_write + fn pub mut shutdown_write -> Result[Nil, Error] { + @socket.shutdown_write } # Shuts down both the reading and writing half of this socket. - fn pub mut shutdown !! Error { - try @socket.shutdown + fn pub mut shutdown -> Result[Nil, Error] { + @socket.shutdown } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[TcpClient, Error] { + @socket.try_clone.map fn (sock) { TcpClient { @socket = sock } } } } impl Read for TcpClient { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try @socket.read(into, size) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + @socket.read(into, size) } } impl Write for TcpClient { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try @socket.write_bytes(bytes) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + @socket.write_bytes(bytes) } - fn pub mut write_string(string: String) !! Error -> Int { - try @socket.write_string(string) + fn pub mut write_string(string: String) -> Result[Int, Error] { + @socket.write_string(string) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } # A TCP socket server that can accept incoming connections. @@ -917,10 +914,10 @@ class pub TcpServer { # import std::net::socket::TcpServer # import std::net::ip::IpAddress # - # let ip = try! IpAddress.parse('0.0.0.0') + # let ip = IpAddress.parse('0.0.0.0').unwrap # - # try! TcpServer.new(ip, port: 40_000) - fn pub static new(ip: IpAddress, port: Int) !! Error -> Self { + # TcpServer.new(ip, port: 40_000).unwrap + fn pub static new(ip: IpAddress, port: Int) -> Result[TcpServer, Error] { let socket = if ip.v6? { try Socket.ipv6(Type.STREAM) } else { @@ -929,11 +926,9 @@ class pub TcpServer { try socket.reuse_address = true try socket.reuse_port = true - try socket.bind(ip, port) try socket.listen - - Self { @socket = socket } + Result.Ok(TcpServer { @socket = socket }) } # Accepts a new incoming connection from `self`. @@ -948,35 +943,35 @@ class pub TcpServer { # import std::net::ip::IpAddress # # let listener = - # try! TcpServer.new(ip: IpAddress.v4(127, 0, 0, 1), port: 40_000) + # TcpServer.new(ip: IpAddress.v4(127, 0, 0, 1), port: 40_000).unwrap # let client = - # try! TcpClient.new(ip: IpAddress.v4(127, 0, 0, 1), port: 40_000) + # TcpClient.new(ip: IpAddress.v4(127, 0, 0, 1), port: 40_000).unwrap # # client.write_string('ping') # - # let connection = try! listener.accept + # let connection = listener.accept.unwrap # let buffer = ByteArray.new # - # try! connection.read(into: buffer, size: 4) + # connection.read(into: buffer, size: 4).unwrap # # buffer.to_string # => 'ping' - fn pub accept !! Error -> TcpClient { - TcpClient { @socket = try @socket.accept } + fn pub accept -> Result[TcpClient, Error] { + @socket.accept.map fn (sock) { TcpClient { @socket = sock } } } # Returns the local address of this socket. # # See the documentation of `Socket.local_address` for more information. - fn pub local_address !! Error -> SocketAddress { - try @socket.local_address + fn pub local_address -> Result[SocketAddress, Error] { + @socket.local_address } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[TcpServer, Error] { + @socket.try_clone.map fn (sock) { TcpServer { @socket = sock } } } } @@ -1009,8 +1004,8 @@ class pub UnixAddress { # import std::net::socket::UnixAddress # # UnixAddress.new("\0example") - fn pub static new(address: ref ToString) -> Self { - Self { @address = address.to_string } + fn pub static new(address: ref ToString) -> UnixAddress { + UnixAddress { @address = address.to_string } } # Returns the path of this address. @@ -1067,7 +1062,7 @@ impl Format for UnixAddress { } } -impl Equal for UnixAddress { +impl Equal[UnixAddress] for UnixAddress { # Returns `true` if `self` and `other` are the same socket addresses. # # # Examples @@ -1078,7 +1073,7 @@ impl Equal for UnixAddress { # # UnixAddress.new('a.sock') == UnixAddress.new('a.sock') # => true # UnixAddress.new('a.sock') == UnixAddress.new('b.sock') # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref UnixAddress) -> Bool { @address == other.address } } @@ -1127,12 +1122,14 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # try! UnixSocket.new(Type.DGRAM) - fn pub static new(type: Type) !! Error -> UnixSocket { - let id = type.into_int - let fd = try _INKO.socket_allocate_unix(id) else (e) throw Error.from_int(e) - - UnixSocket { @fd = fd, @deadline = NO_DEADLINE } + # UnixSocket.new(Type.DGRAM).unwrap + fn pub static new(type: Type) -> Result[UnixSocket, Error] { + match _INKO.socket_new(UNIX, type.into_int) { + case { @tag = 0, @value = fd } -> Result.Ok( + UnixSocket { @fd = fd, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the point in time after which socket operations must time out, known as @@ -1179,14 +1176,13 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let socket = try! UnixSocket.new(Type.DGRAM) + # let socket = UnixSocket.new(Type.DGRAM).unwrap # - # try! socket.bind('/tmp/test.sock') - fn pub mut bind(path: ref ToString) !! Error { - try { - _INKO.socket_bind(@fd, path.to_string, 0, @deadline) - } else (err) { - throw Error.from_int(err) + # socket.bind('/tmp/test.sock').unwrap + fn pub mut bind(path: ref ToString) -> Result[Nil, Error] { + match _INKO.socket_bind(@fd, path.to_string, 0, @deadline) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -1198,18 +1194,17 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let listener = try! UnixSocket.new(Type.STREAM) - # let stream = try! UnixSocket.new(Type.STREAM) + # let listener = UnixSocket.new(Type.STREAM).unwrap + # let stream = UnixSocket.new(Type.STREAM).unwrap # - # try! listener.bind('/tmp/test.sock') - # try! listener.listen + # listener.bind('/tmp/test.sock').unwrap + # listener.listen.unwrap # - # try! stream.connect('/tmp/test.sock') - fn pub mut connect(path: ref ToString) !! Error { - try { - _INKO.socket_connect(@fd, path.to_string, 0, @deadline) - } else (err) { - throw Error.from_int(err) + # stream.connect('/tmp/test.sock').unwrap + fn pub mut connect(path: ref ToString) -> Result[Nil, Error] { + match _INKO.socket_connect(@fd, path.to_string, 0, @deadline) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -1222,15 +1217,14 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let socket = try! UnixSocket.new(Type.STREAM) + # let socket = UnixSocket.new(Type.STREAM).unwrap # - # try! socket.bind('/tmp/test.sock') - # try! socket.listen - fn pub mut listen !! Error { - try { - _INKO.socket_listen(@fd, MAXIMUM_LISTEN_BACKLOG) - } else (err) { - throw Error.from_int(err) + # socket.bind('/tmp/test.sock').unwrap + # socket.listen.unwrap + fn pub mut listen -> Result[Nil, Error] { + match _INKO.socket_listen(@fd, MAXIMUM_LISTEN_BACKLOG) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -1244,29 +1238,28 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let listener = try! UnixSocket.new(Type.STREAM) - # let stream = try! UnixSocket.new(Type.STREAM) + # let listener = UnixSocket.new(Type.STREAM).unwrap + # let stream = UnixSocket.new(Type.STREAM).unwrap # - # try! listener.bind('/tmp/test.sock') - # try! listener.listen + # listener.bind('/tmp/test.sock').unwrap + # listener.listen.unwrap # - # try! stream.connect('/tmp/test.sock') - # try! stream.write_string('ping') + # stream.connect('/tmp/test.sock').unwrap + # stream.write_string('ping').unwrap # - # let client = try! listener.accept + # let client = listener.accept.unwrap # let buffer = ByteArray.new # - # try! client.read(into: buffer, size: 4) + # client.read(into: buffer, size: 4).unwrap # # buffer.to_string # => 'ping' - fn pub accept !! Error -> UnixSocket { - let fd = try { - _INKO.socket_accept_unix(@fd, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub accept -> Result[UnixSocket, Error] { + match _INKO.socket_accept(@fd, @deadline) { + case { @tag = 0, @value = fd } -> Result.Ok( + UnixSocket { @fd = fd, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } - - UnixSocket { @fd = fd, @deadline = NO_DEADLINE } } # Sends a `String` to the given address. @@ -1277,18 +1270,19 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let socket = try! UnixSocket.new(Type.DGRAM) + # let socket = UnixSocket.new(Type.DGRAM).unwrap # - # try! socket.bind('/tmp/test.sock') - # try! socket.send_string_to(string: 'hello', address: '/tmp/test.sock') + # socket.bind('/tmp/test.sock').unwrap + # socket.send_string_to(string: 'hello', address: '/tmp/test.sock').unwrap fn pub mut send_string_to( string: String, address: ref ToString - ) !! Error -> Int { - try { - _INKO.socket_send_string_to(@fd, string, address.to_string, 0, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[Int, Error] { + let addr = address.to_string + + match _INKO.socket_send_string_to(@fd, string, addr, 0, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -1300,19 +1294,20 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let socket = try! UnixSocket.new(Type.DGRAM) + # let socket = UnixSocket.new(Type.DGRAM).unwrap # let bytes = 'hello'.to_byte_array # - # try! socket.bind('/tmp/test.sock') - # try! socket.send_bytes_to(bytes: bytes, address: '/tmp/test.sock') + # socket.bind('/tmp/test.sock').unwrap + # socket.send_bytes_to(bytes: bytes, address: '/tmp/test.sock').unwrap fn pub mut send_bytes_to( bytes: ByteArray, address: ref ToString - ) !! Error -> Int { - try { - _INKO.socket_send_bytes_to(@fd, bytes, address.to_string, 0, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[Int, Error] { + let addr = address.to_string + + match _INKO.socket_send_bytes_to(@fd, bytes, addr, 0, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } @@ -1328,94 +1323,107 @@ class pub UnixSocket { # # import std::net::socket::(Type, UnixSocket) # - # let socket = try! UnixSocket.new(Type.DGRAM) + # let socket = UnixSocket.new(Type.DGRAM).unwrap # let bytes = ByteArray.new # - # try! socket.send_string_to('hello', address: '/tmp/test.sock') + # socket.send_string_to('hello', address: '/tmp/test.sock').unwrap # - # let received_from = try! socket.receive_from(bytes: bytes, size: 5) + # let received_from = socket.receive_from(bytes: bytes, size: 5).unwrap # # bytes.to_string # => 'hello' # received_from.to_string # => '/tmp/test.sock' fn pub mut receive_from( bytes: ref ByteArray, size: Int - ) !! Error -> UnixAddress { - let raw_addr = try { - _INKO.socket_receive_from(@fd, bytes, size, @deadline) - } else (err) { - throw Error.from_int(err) + ) -> Result[UnixAddress, Error] { + let raw_addr = match _INKO.socket_receive_from(@fd, bytes, size, @deadline) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) } let addr = _INKO.socket_address_pair_address(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - UnixAddress.new(addr) + Result.Ok(UnixAddress.new(addr)) } # Returns the local address of this socket. - fn pub local_address !! Error -> UnixAddress { - let raw_addr = - try _INKO.socket_local_address(@fd) else (e) throw Error.from_int(e) + fn pub local_address -> Result[UnixAddress, Error] { + let raw_addr = match _INKO.socket_local_address(@fd) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + } + let addr = _INKO.socket_address_pair_address(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - UnixAddress.new(addr) + Result.Ok(UnixAddress.new(addr)) } # Returns the peer address of this socket. - fn pub peer_address !! Error -> UnixAddress { - let raw_addr = - try _INKO.socket_peer_address(@fd) else (e) throw Error.from_int(e) + fn pub peer_address -> Result[UnixAddress, Error] { + let raw_addr = match _INKO.socket_peer_address(@fd) { + case { @tag = 0, @value = v } -> v + case { @tag = _, @value = e } -> throw Error.from_int(e as Int) + } + let addr = _INKO.socket_address_pair_address(raw_addr) _INKO.socket_address_pair_drop(raw_addr) - UnixAddress.new(addr) - } - - # Returns the value of the `SO_RCVBUF` option. - fn pub receive_buffer_size !! Error -> Int { - try _INKO.socket_get_recv_size(@fd) else (e) throw Error.from_int(e) + Result.Ok(UnixAddress.new(addr)) } # Sets the value of the `SO_RCVBUF` option. - fn pub mut receive_buffer_size=(value: Int) !! Error { - try _INKO.socket_set_recv_size(@fd, value) else (e) throw Error.from_int(e) - } - - # Returns the value of the `SO_SNDBUF` option. - fn pub send_buffer_size !! Error -> Int { - try _INKO.socket_get_send_size(@fd) else (e) throw Error.from_int(e) + fn pub mut receive_buffer_size=(value: Int) -> Result[Nil, Error] { + match _INKO.socket_set_recv_size(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Sets the value of the `SO_SNDBUF` option. - fn pub mut send_buffer_size=(value: Int) !! Error { - try _INKO.socket_set_send_size(@fd, value) else (e) throw Error.from_int(e) + fn pub mut send_buffer_size=(value: Int) -> Result[Nil, Error] { + match _INKO.socket_set_send_size(@fd, value) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down the reading half of this socket. - fn pub mut shutdown_read !! Error { - try _INKO.socket_shutdown_read(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown_read -> Result[Nil, Error] { + match _INKO.socket_shutdown_read(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down the writing half of this socket. - fn pub mut shutdown_write !! Error { - try _INKO.socket_shutdown_write(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown_write -> Result[Nil, Error] { + match _INKO.socket_shutdown_write(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Shuts down both the reading and writing half of this socket. - fn pub mut shutdown !! Error { - try _INKO.socket_shutdown_read_write(@fd) else (e) throw Error.from_int(e) + fn pub mut shutdown -> Result[Nil, Error] { + match _INKO.socket_shutdown_read_write(@fd) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - let fd = try _INKO.socket_try_clone(@fd) else (e) throw Error.from_int(e) - - Self { @fd = fd, @deadline = NO_DEADLINE } + fn pub try_clone -> Result[UnixSocket, Error] { + match _INKO.socket_try_clone(@fd) { + case { @tag = 0, @value = fd } -> Result.Ok( + UnixSocket { @fd = fd, @deadline = NO_DEADLINE } + ) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } } @@ -1426,33 +1434,32 @@ impl Drop for UnixSocket { } impl Read for UnixSocket { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try { - _INKO.socket_read(@fd, into, size, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.socket_read(@fd, into, size, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } } impl Write for UnixSocket { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try { - _INKO.socket_write_bytes(@fd, bytes, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.socket_write_bytes(@fd, bytes, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut write_string(string: String) !! Error -> Int { - try { - _INKO.socket_write_string(@fd, string, @deadline) - } else (err) { - throw Error.from_int(err) + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.socket_write_string(@fd, string, @deadline) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } # A Unix datagram socket. @@ -1468,13 +1475,12 @@ class pub UnixDatagram { # # import std::net::socket::UnixDatagram # - # try! UnixDatagram.new('/tmp/test.sock') - fn pub static new(address: ref ToString) !! Error -> Self { + # UnixDatagram.new('/tmp/test.sock').unwrap + fn pub static new(address: ref ToString) -> Result[UnixDatagram, Error] { let socket = try UnixSocket.new(Type.DGRAM) try socket.bind(address) - - Self { @socket = socket } + Result.Ok(UnixDatagram { @socket = socket }) } # Connects `self` to the remote addres.s @@ -1489,12 +1495,12 @@ class pub UnixDatagram { # # import std::net::socket::UnixDatagram # - # let socket1 = try! UnixDatagram.new('/tmp/test1.sock') - # let socket2 = try! UnixDatagram.new('/tmp/test2.sock') + # let socket1 = UnixDatagram.new('/tmp/test1.sock').unwrap + # let socket2 = UnixDatagram.new('/tmp/test2.sock').unwrap # - # try! socket1.connect('/tmp/test2.sock') - fn pub mut connect(address: ref ToString) !! Error { - try @socket.connect(address) + # socket1.connect('/tmp/test2.sock').unwrap + fn pub mut connect(address: ref ToString) -> Result[Nil, Error] { + @socket.connect(address) } # Sends a `String` to the given address. @@ -1505,14 +1511,14 @@ class pub UnixDatagram { # # import std::net::socket::UnixDatagram # - # let socket = try! UnixDatagram.new('/tmp/test.sock') + # let socket = UnixDatagram.new('/tmp/test.sock').unwrap # - # try! socket.send_string_to(string: 'hello', address: '/tmp/test.sock') + # socket.send_string_to(string: 'hello', address: '/tmp/test.sock').unwrap fn pub mut send_string_to( string: String, address: ref ToString - ) !! Error -> Int { - try @socket.send_string_to(string, address) + ) -> Result[Int, Error] { + @socket.send_string_to(string, address) } # Sends a `ByteArray` to the given address. @@ -1523,15 +1529,15 @@ class pub UnixDatagram { # # import std::net::socket::UnixDatagram # - # let socket = try! UnixDatagram.new('/tmp/test.sock') + # let socket = UnixDatagram.new('/tmp/test.sock').unwrap # let bytes = 'hello'.to_byte_array # - # try! socket.send_bytes_to(bytes: bytes, address: '/tmp/test.sock') + # socket.send_bytes_to(bytes: bytes, address: '/tmp/test.sock').unwrap fn pub mut send_bytes_to( bytes: ByteArray, address: ref ToString - ) !! Error -> Int { - try @socket.send_bytes_to(bytes, address) + ) -> Result[Int, Error] { + @socket.send_bytes_to(bytes, address) } # Receives a single datagram message on the socket, returning the address the @@ -1541,42 +1547,44 @@ class pub UnixDatagram { fn pub mut receive_from( bytes: ref ByteArray, size: Int - ) !! Error -> UnixAddress { - try @socket.receive_from(bytes, size) + ) -> Result[UnixAddress, Error] { + @socket.receive_from(bytes, size) } # Returns the local address of this socket. # # See the documentation of `UnixSocket.local_address` for more information. - fn pub local_address !! Error -> UnixAddress { - try @socket.local_address + fn pub local_address -> Result[UnixAddress, Error] { + @socket.local_address } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[UnixDatagram, Error] { + @socket.try_clone.map fn (sock) { UnixDatagram { @socket = sock } } } } impl Read for UnixDatagram { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try @socket.read(into, size) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + @socket.read(into, size) } } impl Write for UnixDatagram { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try @socket.write_bytes(bytes) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + @socket.write_bytes(bytes) } - fn pub mut write_string(string: String) !! Error -> Int { - try @socket.write_string(string) + fn pub mut write_string(string: String) -> Result[Int, Error] { + @socket.write_string(string) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } # A Unix stream socket connected to another Unix socket. @@ -1596,15 +1604,14 @@ class pub UnixClient { # # import std::net::socket::(UnixServer, UnixClient) # - # let listener = try! UnixServer.new('/tmp/test.sock') + # let listener = UnixServer.new('/tmp/test.sock').unwrap # - # try! UnixClient.new('/tmp/test.sock') - fn pub static new(address: ref ToString) !! Error -> Self { + # UnixClient.new('/tmp/test.sock').unwrap + fn pub static new(address: ref ToString) -> Result[UnixClient, Error] { let socket = try UnixSocket.new(Type.STREAM) try socket.connect(address) - - Self { @socket = socket } + Result.Ok(UnixClient { @socket = socket }) } # Creates a new `UnixClient` but limits the amount of time spent waiting for @@ -1620,77 +1627,80 @@ class pub UnixClient { # import std::net::socket::UnixClient # import std::time::Duration # - # try! UnixClient.with_timeout( - # address: '/tmp/test.sock' - # timeout_after: Duration.from_secs(5) - # ) + # UnixClient + # .with_timeout( + # address: '/tmp/test.sock' + # timeout_after: Duration.from_secs(5) + # ) + # .unwrap fn pub static with_timeout( address: ref ToString, timeout_after: ToInstant - ) !! Error -> Self { + ) -> Result[UnixClient, Error] { let socket = try UnixSocket.new(Type.STREAM) let _ = socket.timeout_after = timeout_after try socket.connect(address) - - Self { @socket = socket } + Result.Ok(UnixClient { @socket = socket }) } # Returns the local address of this socket. # # See the documentation of `UnixSocket.local_address` for more information. - fn pub local_address !! Error -> UnixAddress { - try @socket.local_address + fn pub local_address -> Result[UnixAddress, Error] { + @socket.local_address } # Returns the peer address of this socket. # # See the documentation of `UnixSocket.peer_address` for more information. - fn pub peer_address !! Error -> UnixAddress { - try @socket.peer_address + fn pub peer_address -> Result[UnixAddress, Error] { + @socket.peer_address } # Shuts down the reading half of this socket. - fn pub mut shutdown_read !! Error { - try @socket.shutdown_read + fn pub mut shutdown_read -> Result[Nil, Error] { + @socket.shutdown_read } # Shuts down the writing half of this socket. - fn pub mut shutdown_write !! Error { - try @socket.shutdown_write + fn pub mut shutdown_write -> Result[Nil, Error] { + @socket.shutdown_write } # Shuts down both the reading and writing half of this socket. - fn pub mut shutdown !! Error { - try @socket.shutdown + fn pub mut shutdown -> Result[Nil, Error] { + @socket.shutdown } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[UnixClient, Error] { + @socket.try_clone.map fn (sock) { UnixClient { @socket = sock } } } } impl Read for UnixClient { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try @socket.read(into, size) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + @socket.read(into, size) } } impl Write for UnixClient { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try @socket.write_bytes(bytes) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + @socket.write_bytes(bytes) } - fn pub mut write_string(string: String) !! Error -> Int { - try @socket.write_string(string) + fn pub mut write_string(string: String) -> Result[Int, Error] { + @socket.write_string(string) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } # A Unix socket server that can accept incoming connections. @@ -1708,14 +1718,13 @@ class pub UnixServer { # # import std::net::socket::UnixServer # - # try! UnixServer.new('/tmp/test.sock') - fn pub static new(address: ref ToString) !! Error -> Self { + # UnixServer.new('/tmp/test.sock').unwrap + fn pub static new(address: ref ToString) -> Result[UnixServer, Error] { let socket = try UnixSocket.new(Type.STREAM) try socket.bind(address) try socket.listen - - Self { @socket = socket } + Result.Ok(UnixServer { @socket = socket }) } # Accepts a new incoming connection from `self`. @@ -1728,33 +1737,33 @@ class pub UnixServer { # # import std::net::socket::(UnixServer, UnixClient) # - # let listener = try! UnixServer.new('/tmp/test.sock') - # let client = try! UnixClient.new('/tmp/test.sock') + # let listener = UnixServer.new('/tmp/test.sock').unwrap + # let client = UnixClient.new('/tmp/test.sock').unwrap # # client.write_string('ping') # - # let connection = try! listener.accept + # let connection = listener.accept.unwrap # let buffer = ByteArray.new # - # try! connection.read(into: buffer, size: 4) + # connection.read(into: buffer, size: 4).unwrap # # buffer.to_string # => 'ping' - fn pub accept !! Error -> UnixClient { - UnixClient { @socket = try @socket.accept } + fn pub accept -> Result[UnixClient, Error] { + @socket.accept.map fn (sock) { UnixClient { @socket = sock } } } # Returns the local address of this socket. # # See the documentation of `UnixSocket.local_address` for more information. - fn pub local_address !! Error -> UnixAddress { - try @socket.local_address + fn pub local_address -> Result[UnixAddress, Error] { + @socket.local_address } # Attempts to clone the socket. # # Cloning a socket may fail, such as when the program has too many open file # descriptors. - fn pub try_clone !! Error -> Self { - Self { @socket = try @socket.try_clone } + fn pub try_clone -> Result[UnixServer, Error] { + @socket.try_clone.map fn (sock) { UnixServer { @socket = sock } } } } diff --git a/libstd/src/std/nil.inko b/std/src/std/nil.inko similarity index 84% rename from libstd/src/std/nil.inko rename to std/src/std/nil.inko index 8bd98e684..f417765e8 100644 --- a/libstd/src/std/nil.inko +++ b/std/src/std/nil.inko @@ -15,19 +15,19 @@ import std::fmt::(Format, Formatter) class builtin Nil { # Returns a new `Nil`. fn pub static new -> Nil { - _INKO.get_nil + nil } } -impl Equal for Nil { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Nil] for Nil { + fn pub ==(other: ref Nil) -> Bool { true } } -impl Clone for Nil { - fn pub clone -> Self { - _INKO.get_nil +impl Clone[Nil] for Nil { + fn pub clone -> Nil { + nil } } diff --git a/libstd/src/std/ops.inko b/std/src/std/ops.inko similarity index 54% rename from libstd/src/std/ops.inko rename to std/src/std/ops.inko index 5d3a26fac..3fc8bd5ce 100644 --- a/libstd/src/std/ops.inko +++ b/std/src/std/ops.inko @@ -4,83 +4,79 @@ # # This trait is generic as types may wish to customise the type on the # right-hand side of the operator. -trait pub Add[T] { - # Adds the given object to `self`, producing a new instance of the same type - # as `self`. - fn pub +(other: ref T) -> Self +trait pub Add[T, R] { + # Adds the given object to `self`. + fn pub +(other: ref T) -> R } # The binary `-` operator. # # This trait is generic as types may wish to customise the type on the # right-hand side of the operator. -trait pub Subtract[T] { - # Subtracts the given object from `self`, producing a new instance of the - # same type as `self`. - fn pub -(other: ref T) -> Self +trait pub Subtract[T, R] { + # Subtracts the given object from `self`. + fn pub -(other: ref T) -> R } # The binary `/` operator. -trait pub Divide { - # Divides `self` by the given object, producing a new one of the same type. - fn pub /(other: ref Self) -> Self +trait pub Divide[T, R] { + # Divides `self` by the given object. + fn pub /(other: ref T) -> R } # The binary `*` operator. -trait pub Multiply { - # Multiplies `self` with the given object, producing a new one of the same - # type. - fn pub *(other: ref Self) -> Self +trait pub Multiply[T, R] { + # Multiplies `self` with the given object. + fn pub *(other: ref T) -> R } # The binary `%` operator. -trait pub Modulo { - # Gets the remainder after dividing `self` by the given object, producing a - # new one of the same type. - fn pub %(other: ref Self) -> Self +trait pub Modulo[T, R] { + # Gets the remainder after dividing `self` by the given object. + fn pub %(other: ref T) -> R } # The binary `**` operator. -trait pub Power { +trait pub Power[T, R] { # Raises `self` to the power of the given exponent. - fn pub **(other: ref Self) -> Self + fn pub **(other: ref T) -> R } # The binary `&` (bitwise AND) operator. -trait pub BitAnd { +trait pub BitAnd[T, R] { # Returns the result of a bitwise AND with `self` and the given object. - fn pub &(other: ref Self) -> Self + fn pub &(other: ref T) -> R } # The binary `|` (bitwise OR) operator. -trait pub BitOr { +trait pub BitOr[T, R] { # Returns the result of a bitwise OR with `self` and the given object. - fn pub |(other: ref Self) -> Self + fn pub |(other: ref T) -> R } # The binary `^` operator. -trait pub BitXor { +trait pub BitXor[T, R] { # Returns the result of a bitwise XOR with `self` and the given object. - fn pub ^(other: ref Self) -> Self + fn pub ^(other: ref T) -> R } # The binary `<<` operator. -trait pub ShiftLeft { +trait pub ShiftLeft[T, R] { # Returns the result of a bitwise shift to the left with `self` and the given # object. - fn pub <<(other: ref Self) -> Self + fn pub <<(other: ref T) -> R } # The binary `>>` operator. -trait pub ShiftRight { +trait pub ShiftRight[T, R] { # Returns the result of a bitwise shift to the right with `self` and the # given object. - fn pub >>(other: ref Self) -> Self + fn pub >>(other: ref T) -> R } # The binary `>>>` operator. -trait pub UnsignedShiftRight { +trait pub UnsignedShiftRight[T, R] { # Casts `self` to an unsigned integer, shifts it to the right, then returns # the result as a signed integer. - fn pub >>>(other: ref Self) -> Self + fn pub >>>(other: ref T) -> R } diff --git a/libstd/src/std/option.inko b/std/src/std/option.inko similarity index 90% rename from libstd/src/std/option.inko rename to std/src/std/option.inko index b80c08105..1e25815f5 100644 --- a/libstd/src/std/option.inko +++ b/std/src/std/option.inko @@ -17,7 +17,10 @@ import std::process::(panic) # An `Option` is is either a `Some` containing a value, or a `None` that doesn't # contain a value. class pub enum Option[T] { + # A value of type `T`. case Some(T) + + # The lack of a value. case None # Returns an optional immutable reference to the wrapped value. @@ -33,29 +36,30 @@ class pub enum Option[T] { } } - # Returns an optional mutable reference to the wrapped value. + # Returns the wrapped value. + # + # This method panics when used with a `None`. # # # Examples # - # Option.Some([10]).as_mut # => Option.Some(mut [10]) - fn pub mut as_mut -> Option[mut T] { + # Option.Some(10).unwrap # => 10 + fn pub move unwrap -> T { match self { - case Some(v) -> Option.Some(v) - case None -> Option.None + case Some(v) -> v + case _ -> panic("Option.unwrap can't unwrap a None") } } - # Returns the wrapped value. - # - # This method panics when used with a `None`. + # Returns the wrapped value, triggering a panic with a custom message for a + # `None`. # # # Examples # - # Option.Some(10).unwrap # => 10 - fn pub move unwrap -> T { + # Option.Some(10).expect('a number must be present') # => 10 + fn pub move expect(message: String) -> T { match self { case Some(v) -> v - case None -> panic("A None can't be unwrapped") + case _ -> panic(message) } } @@ -174,7 +178,21 @@ class pub enum Option[T] { } } -impl Equal for Option if T: Equal { +impl Option if T: mut { + # Returns an optional mutable reference to the wrapped value. + # + # # Examples + # + # Option.Some([10]).as_mut # => Option.Some(mut [10]) + fn pub mut as_mut -> Option[mut T] { + match self { + case Some(v) -> Option.Some(v) + case None -> Option.None + } + } +} + +impl Equal[Option[T]] for Option if T: Equal[T] { # Returns `true` if `self` and the given `Option` are equal. # # Two options are considered equal to each other if: @@ -196,7 +214,7 @@ impl Equal for Option if T: Equal { # Comparing two None values: # # Option.None == Option.None # => true - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Option[T]) -> Bool { match self { case Some(ours) -> match other { case Some(theirs) -> ours == theirs @@ -210,8 +228,8 @@ impl Equal for Option if T: Equal { } } -impl Clone for Option if T: Clone { - fn pub clone -> Self { +impl Clone[Option[T]] for Option if T: Clone[T] { + fn pub clone -> Option[T] { match self { case Some(v) -> Option.Some(v.clone) case None -> Option.None diff --git a/std/src/std/process.inko b/std/src/std/process.inko new file mode 100644 index 000000000..9e0ddfe7e --- /dev/null +++ b/std/src/std/process.inko @@ -0,0 +1,21 @@ +# Lightweight Inko processes. +import std::time::Duration + +# Terminates the program with an error message. +# +# A panic is an unrecoverable error meant to guard against code bugs. For +# runtime errors, use `try` and `throw` instead. +fn pub panic(message: String) -> Never { + _INKO.panic(message) +} + +# Suspends the current process for at least the given duration. +# +# The actual time the process is suspended for may be larger than the given +# duration. +# +# If the specified duration is less than or equal to zero, the process is +# rescheduled immediately. +fn pub sleep(time: ref Duration) { + _INKO.process_suspend(time.to_nanos) +} diff --git a/libstd/src/std/rand.inko b/std/src/std/rand.inko similarity index 91% rename from libstd/src/std/rand.inko rename to std/src/std/rand.inko index 1fc2ebfef..ad95497e6 100644 --- a/libstd/src/std/rand.inko +++ b/std/src/std/rand.inko @@ -20,8 +20,8 @@ class pub Random { # import std::rand::Random # # Random.from_int(42) - fn pub static from_int(seed: Int) -> Self { - Self { @rng = _INKO.random_from_int(seed) } + fn pub static from_int(seed: Int) -> Random { + Random { @rng = _INKO.random_from_int(seed) } } # Returns a new `Random` seeded using a cryptographically secure seed. @@ -34,8 +34,8 @@ class pub Random { # import std::rand::Random # # Random.new - fn pub static new -> Self { - Self { @rng = _INKO.random_new } + fn pub static new -> Random { + Random { @rng = _INKO.random_new } } # Returns a randomly generated `Int`. @@ -99,13 +99,13 @@ class pub Shuffle { let @rng: Random # Returns a new `Shuffle` that sorts values in a random order. - fn pub static new -> Self { - Self { @rng = Random.new } + fn pub static new -> Shuffle { + Shuffle { @rng = Random.new } } # Returns a new `Shuffle` that uses the given seed for sorting values. - fn pub static from_int(seed: Int) -> Self { - Self { @rng = Random.from_int(seed) } + fn pub static from_int(seed: Int) -> Shuffle { + Shuffle { @rng = Random.from_int(seed) } } # Sorts the values of the given `Array` in place such that they are in a diff --git a/libstd/src/std/range.inko b/std/src/std/range.inko similarity index 87% rename from libstd/src/std/range.inko rename to std/src/std/range.inko index e26c6d5c0..32bd69e5f 100644 --- a/libstd/src/std/range.inko +++ b/std/src/std/range.inko @@ -24,7 +24,7 @@ import std::hash::(Hash, Hasher) import std::iter::(Enum, Iter) # A range of integers. -trait pub Range: Contains[Int] + Equal + Hash + Format { +trait pub Range: Contains[Int] + Hash + Format { # Returns the first value in the range. fn pub start -> Int @@ -38,13 +38,13 @@ trait pub Range: Contains[Int] + Equal + Hash + Format { # # If the number of values in `self` can't be evenly divided by `amount`, the # last range is shorter. - fn pub step_by(amount: Int) -> Enum[Range, Never] + fn pub step_by(amount: Int) -> Enum[Range] # Returns the number of values in this range. fn pub length -> Int # Returns an iterator over the values in `self`. - fn pub iter -> Iter[Int, Never] + fn pub iter -> Iter[Int] } # An inclusive range of integers. @@ -56,8 +56,8 @@ class pub InclusiveRange { let pub @end: Int # Returns a new `InclusiveRange` over the given values. - fn pub static new(start: Int, end: Int) -> Self { - Self { @start = start, @end = end } + fn pub static new(start: Int, end: Int) -> InclusiveRange { + InclusiveRange { @start = start, @end = end } } } @@ -74,7 +74,7 @@ impl Range for InclusiveRange { true } - fn pub step_by(amount: Int) -> Enum[InclusiveRange, Never] { + fn pub step_by(amount: Int) -> Enum[InclusiveRange] { let mut current = @start let max = @end @@ -93,7 +93,7 @@ impl Range for InclusiveRange { if @end >= @start { @end - @start + 1 } else { 0 } } - fn pub iter -> Enum[Int, Never] { + fn pub iter -> Enum[Int] { let mut current = @start let end = @end @@ -121,7 +121,7 @@ impl Contains[Int] for InclusiveRange { } } -impl Equal for InclusiveRange { +impl Equal[InclusiveRange] for InclusiveRange { # Returns `true` if `self` and `other` are identical. # # # Examples @@ -133,7 +133,7 @@ impl Equal for InclusiveRange { # Comparing two different ranges: # # 1.to(10) == 1.to(5) # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref InclusiveRange) -> Bool { @start == other.start and @end == other.end } } @@ -162,8 +162,8 @@ class pub ExclusiveRange { let pub @end: Int # Returns a new `ExclusiveRange` over the given values. - fn pub static new(start: Int, end: Int) -> Self { - Self { @start = start, @end = end } + fn pub static new(start: Int, end: Int) -> ExclusiveRange { + ExclusiveRange { @start = start, @end = end } } } @@ -180,7 +180,7 @@ impl Range for ExclusiveRange { false } - fn pub step_by(amount: Int) -> Enum[ExclusiveRange, Never] { + fn pub step_by(amount: Int) -> Enum[ExclusiveRange] { let mut current = @start let max = @end @@ -199,7 +199,7 @@ impl Range for ExclusiveRange { if @end >= @start { @end - @start } else { 0 } } - fn pub iter -> Enum[Int, Never] { + fn pub iter -> Enum[Int] { let mut current = @start let end = @end @@ -227,7 +227,7 @@ impl Contains[Int] for ExclusiveRange { } } -impl Equal for ExclusiveRange { +impl Equal[ExclusiveRange] for ExclusiveRange { # Returns `true` if `self` and `other` are identical. # # # Examples @@ -239,7 +239,7 @@ impl Equal for ExclusiveRange { # Comparing two different ranges: # # 1.until(10) == 1.until(5) # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref ExclusiveRange) -> Bool { @start == other.start and @end == other.end } } diff --git a/std/src/std/result.inko b/std/src/std/result.inko new file mode 100644 index 000000000..c005c7f94 --- /dev/null +++ b/std/src/std/result.inko @@ -0,0 +1,258 @@ +# Types for error handling. +# +# The `Result` type is used for error handling, and wraps either an OK value or +# an error value. +import std::clone::Clone +import std::cmp::Equal +import std::fmt::(Format, Formatter) + +# A type that represents either success (`Ok(T)`) or failure (`Error(E)`). +class pub enum Result[T, E] { + # The case and value for a successful result. + case Ok(T) + + # The case and value for an error. + case Error(E) + + # Returns `true` if `self` is an `Ok`. + # + # # Examples + # + # let res: Result[Int, String] = Result.Ok(42) + # + # res.ok? # => true + fn pub ok? -> Bool { + match self { + case Ok(_) -> true + case _ -> false + } + } + + # Returns `true` if `self` is an `Err`. + # + # # Examples + # + # let res: Result[Int, String] = Result.Error('oops!') + # + # res.error? # => true + fn pub error? -> Bool { + match self { + case Error(_) -> true + case _ -> false + } + } + + # Converts `self` into an `Option[T]`. + # + # If `self` is an `Ok`, a `Some(T)` is returned, otherwise a `None` is + # returned. + # + # # Examples + # + # let res: Result[Int, String] = Result.Ok(42) + # + # res.ok # => Option.Some(42) + fn pub move ok -> Option[T] { + match self { + case Ok(v) -> Option.Some(v) + case _ -> Option.None + } + } + + # Converts `self` into an `Option[E]`. + # + # If `self` is an `Error`, a `Some(E)` is returned, otherwise a `None` is + # returned. + # + # # Examples + # + # let res: Result[Int, String] = Result.Error('oops!') + # + # res.error # => Option.Some(42) + fn pub move error -> Option[E] { + match self { + case Error(v) -> Option.Some(v) + case _ -> Option.None + } + } + + # Unwraps `self` into its `Ok` value. + # + # # Panics + # + # This method panics if `self` is an `Error`. + # + # # Examples + # + # let res: Result[Int, String] = Result.Ok(42) + # + # res.unwrap # => 42 + fn pub move unwrap -> T { + match self { + case Ok(val) -> val + case _ -> panic("Result.unwrap can't unwrap an Error") + } + } + + # Returns the wrapped `Ok` value, triggering a panic with a custom message for + # a `Error`. + # + # # Examples + # + # let res: Result[Int, String] = Result.Ok(10) + # + # res.expect('a number must be present') # => 10 + fn pub move expect(message: String) -> T { + match self { + case Ok(v) -> v + case _ -> panic(message) + } + } + + # Unwraps `self` into its `Ok` value, returning a default value if `self` is + # an `Error`. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Ok(42) + # let bar: Result[Int, String] = Result.Error('oops!') + # + # foo.unwrap_or(0) # => 42 + # bar.unwrap_or(0) # => 0 + fn pub move unwrap_or(default: T) -> T { + match self { + case Ok(val) -> val + case _ -> default + } + } + + # Unwraps `self` into its `Ok` value, returning a default value if `self` is + # an `Error`. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Ok(42) + # let bar: Result[Int, String] = Result.Error('oops!') + # + # foo.unwrap_or_else fn { 0 } # => 42 + # bar.unwrap_or_else fn { 0 } # => 0 + fn pub move unwrap_or_else(block: fn -> T) -> T { + match self { + case Ok(val) -> val + case _ -> block.call + } + } + + # Maps a `Result[T, E]` into a `Result[R, E]`. + # + # If `self` is an `Ok`, the supplied closure is called and its value used to + # return a new `Ok`. If `self` is an `Error`, the `Error` is returned as-is. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Ok(42) + # + # res.map fn (val) { val.to_string } # => Result.Ok('42') + fn pub move map[R](block: fn (T) -> R) -> Result[R, E] { + match self { + case Ok(v) -> Result.Ok(block.call(v)) + case Error(e) -> Result.Error(e) + } + } + + # Maps a `Result[T, E]` into a `Result[T, R]`. + # + # If `self` is an `Error`, the supplied closure is called and its value used + # to return a new `Error`. If `self` is an `Ok`, the `Ok` is returned as-is. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Error('oops!') + # + # res.map_error fn (val) { val.to_upper } # => Result.Ok('OOPS!') + fn pub move map_error[R](block: fn (E) -> R) -> Result[T, R] { + match self { + case Ok(v) -> Result.Ok(v) + case Error(e) -> Result.Error(block.call(e)) + } + } + + # Maps a `Result[T, E]` into a `Result[R, E]`. + # + # If `self` is an `Ok`, the supplied closure is called and its returned + # `Result` is returned. If `self` is an `Error`, the `Error` is returned + # as-is. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Ok(42) + # + # res.then fn (val) { Result.Ok(val.to_string) } # => Result.Ok('42') + fn pub move then[R](block: fn (T) -> Result[R, E]) -> Result[R, E] { + match self { + case Ok(v) -> block.call(v) + case Error(e) -> Result.Error(e) + } + } + + # Maps a `Result[T, E]` into a `Result[T, R]`. + # + # If `self` is an `Error`, the supplied closure is called and its returned + # `Result` is returned. If `self` is an `Ok`, the `Ok` is returned as-is. + # + # # Examples + # + # let foo: Result[Int, String] = Result.Error('oops!') + # + # res.else fn (val) { + # Result.Error(val.to_upper) + # } + # # => Result.Error('OOPS!') + fn pub move else[R](block: fn (E) -> Result[T, R]) -> Result[T, R] { + match self { + case Ok(v) -> Result.Ok(v) + case Error(e) -> block.call(e) + } + } +} + +impl Clone[Result[T, E]] for Result if T: Clone[T], E: Clone[E] { + fn pub clone -> Result[T, E] { + match self { + case Ok(v) -> Result.Ok(v.clone) + case Error(v) -> Result.Error(v.clone) + } + } +} + +impl Equal[Result[T, E]] for Result if T: Equal[T], E: Equal[E] { + fn pub ==(other: ref Result[T, E]) -> Bool { + match self { + case Ok(ours) -> match other { + case Ok(theirs) -> ours == theirs + case _ -> false + } + case Error(ours) -> match other { + case Error(theirs) -> ours == theirs + case _ -> false + } + } + } +} + +impl Format for Result if T: Format, E: Format { + fn pub fmt(formatter: mut Formatter) { + match self { + case Ok(v) -> { + formatter.write('Ok(') + v.fmt(formatter) + formatter.write(')') + } + case Error(v) -> { + formatter.write('Error(') + v.fmt(formatter) + formatter.write(')') + } + } + } +} diff --git a/libstd/src/std/set.inko b/std/src/std/set.inko similarity index 93% rename from libstd/src/std/set.inko rename to std/src/std/set.inko index 16c179312..f02da9fd2 100644 --- a/libstd/src/std/set.inko +++ b/std/src/std/set.inko @@ -8,15 +8,15 @@ import std::iter::Iter # # The order of values in this Set are not guaranteed. For values to be stored in # a `Set` they must implement the `Hash` and `Equal` traits. -class pub Set[V: Hash + Equal] { +class pub Set[V: Hash + Equal[V]] { # The Map used for storing values. # # The keys are the values inserted in this `Set`, the values are always set to # `True`. let @map: Map[V, Bool] - fn pub static new -> Self { - Self { @map = Map.new } + fn pub static new -> Set[V] { + Set { @map = Map.new } } # Inserts a new value into the `Set`. @@ -35,7 +35,7 @@ class pub Set[V: Hash + Equal] { fn pub mut insert(value: V) -> Bool { if @map.contains?(value) { return false } - @map[value] = true + @map.set(value, true) true } @@ -80,7 +80,7 @@ class pub Set[V: Hash + Equal] { # set.insert(20) # # set.iter.next # => 10 - fn pub iter -> Iter[ref V, Never] { + fn pub iter -> Iter[ref V] { @map.keys } @@ -106,7 +106,7 @@ class pub Set[V: Hash + Equal] { } } -impl Equal for Set { +impl Equal[Set[V]] for Set { # Returns `True` if `self` and the given `Set` are identical to each # other. # @@ -123,7 +123,7 @@ impl Equal for Set { # set2.insert(10) # # set1 == set2 # => True - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Set[V]) -> Bool { if length != other.length { return false } iter.all? fn (val) { other.contains?(val) } diff --git a/std/src/std/stdio.inko b/std/src/std/stdio.inko new file mode 100644 index 000000000..8e2874e71 --- /dev/null +++ b/std/src/std/stdio.inko @@ -0,0 +1,81 @@ +# STDIN, STDOUT, and STDERR streams. +import std::io::(Error, Read, Write) + +# The standard input stream of the current OS process. +class pub STDIN { + # Returns a new handle to the input stream. + fn pub static new -> STDIN { + STDIN {} + } +} + +impl Read for STDIN { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.stdin_read(into, size) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } +} + +# The standard output stream of the current OS process. +# +# To make it easier to write to STDOUT, any errors produced while writing are +# ignored. +class pub STDOUT { + # Returns a new handle to the output stream. + fn pub static new -> STDOUT { + STDOUT {} + } +} + +impl Write for STDOUT { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.stdout_write_bytes(bytes) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.stdout_write_string(string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(_INKO.stdout_flush) + } +} + +# The standard error stream of the current OS process. +# +# To make it easier to write to STDERR, any errors produced while writing are +# ignored. +class pub STDERR { + # Returns a new handle to the error stream. + fn pub static new -> STDERR { + STDERR {} + } +} + +impl Write for STDERR { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.stderr_write_bytes(bytes) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.stderr_write_string(string) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } + } + + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(_INKO.stderr_flush) + } +} diff --git a/libstd/src/std/string.inko b/std/src/std/string.inko similarity index 95% rename from libstd/src/std/string.inko rename to std/src/std/string.inko index df3dafff2..39cef48fb 100644 --- a/libstd/src/std/string.inko +++ b/std/src/std/string.inko @@ -5,6 +5,7 @@ # Various methods for `String` may operate on or mention "characters". Whenever # this is the case, we are referring to extended grapheme clusters, _not_ # Unicode scalar values or bytes. +import std::array::(bounds_check) import std::byte_array::(IntoByteArray, ToByteArray) import std::clone::Clone import std::cmp::(Contains, Equal) @@ -12,7 +13,6 @@ import std::drop::Drop import std::fmt::(Format, Formatter) import std::fs::path::(IntoPath, Path, ToPath) import std::hash::(Hash, Hasher) -import std::index::(bounds_check) import std::io::Read import std::iter::(Bytes as BytesTrait, EOF, Enum, Iter) import std::ops::Add @@ -275,7 +275,7 @@ class builtin String { # # iter.next # => Option.Some('foo') # iter.next # => Option.Some('bar') - fn pub split(separator: String) -> Enum[String, Never] { + fn pub split(separator: String) -> Enum[String] { let mut offset = 0 Enum.new fn move { @@ -458,7 +458,7 @@ class builtin String { return } - match ESCAPE_TABLE[byte] { + match ESCAPE_TABLE.get(byte) { case -1 -> buff.push(byte) case byte -> { buff.push(BSLASH) @@ -495,9 +495,9 @@ impl IntoByteArray for String { } } -impl Clone for String { - fn pub clone -> Self { - _INKO.string_clone(self) +impl Clone[String] for String { + fn pub clone -> String { + self } } @@ -513,7 +513,7 @@ impl IntoString for String { } } -impl Equal for String { +impl Equal[String] for String { # Returns `true` if the current `String` and `other` are equal to each other. # # # Examples @@ -525,7 +525,7 @@ impl Equal for String { # Returns `false` if two Strings are not equal: # # 'foo' == 'bar' # => false - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref String) -> Bool { _INKO.string_eq(self, other) } } @@ -542,14 +542,14 @@ impl Hash for String { } } -impl Add[String] for String { +impl Add[String, String] for String { # Concatenates `self` and the given `String`, returning a new `String`. # # # Examples # # 'hello ' + 'world' # => 'hello world' - fn pub +(other: ref Self) -> Self { - _INKO.strings(self, other) + fn pub +(other: ref String) -> String { + _INKO.string_concat(self, other) } } @@ -590,14 +590,11 @@ class pub Characters { let @iter: Any } -impl Iter[String, Never] for Characters { +impl Iter[String] for Characters { fn pub mut next -> Option[String] { - let val = _INKO.string_characters_next(@iter) - - if _INKO.is_undefined(val) { - Option.None - } else { - Option.Some(val as String) + match _INKO.string_characters_next(@iter) { + case { @tag = 0, @value = v } -> Option.Some(v as String) + case _ -> Option.None } } } @@ -614,7 +611,7 @@ class pub Bytes { let @index: Int } -impl Iter[Int, Never] for Bytes { +impl Iter[Int] for Bytes { fn pub mut next -> Option[Int] { match next_byte { case EOF -> Option.None @@ -623,7 +620,7 @@ impl Iter[Int, Never] for Bytes { } } -impl BytesTrait[Never] for Bytes { +impl BytesTrait for Bytes { fn pub mut next_byte -> Int { if @index < @string.size { _INKO.string_byte(@string, @index := @index + 1) @@ -634,7 +631,7 @@ impl BytesTrait[Never] for Bytes { } impl Read for Bytes { - fn pub mut read(into: mut ByteArray, size: Int) -> Int { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Never] { let mut read = 0 while read < size { @@ -647,10 +644,10 @@ impl Read for Bytes { } } - read + Result.Ok(read) } - fn pub mut read_all(bytes: mut ByteArray) -> Int { + fn pub mut read_all(bytes: mut ByteArray) -> Result[Int, Never] { read(into: bytes, size: @string.size - @index) } } @@ -696,8 +693,8 @@ class pub StringBuffer { let @strings: Array[String] # Returns a new empty `StringBuffer`. - fn pub static new -> Self { - Self { @strings = [] } + fn pub static new -> StringBuffer { + StringBuffer { @strings = [] } } # Returns a new `StringBuffer` from an existing `Array`. @@ -711,8 +708,8 @@ class pub StringBuffer { # let strings = ['foo', 'bar'] # # StringBuffer.from_array(strings).to_string # => 'foobar' - fn pub static from_array(strings: Array[String]) -> Self { - Self { @strings = strings } + fn pub static from_array(strings: Array[String]) -> StringBuffer { + StringBuffer { @strings = strings } } # Adds the given `String` to the buffer. diff --git a/libstd/src/std/sys.inko b/std/src/std/sys.inko similarity index 72% rename from libstd/src/std/sys.inko rename to std/src/std/sys.inko index 58c4972f5..0590cc0bd 100644 --- a/libstd/src/std/sys.inko +++ b/std/src/std/sys.inko @@ -5,26 +5,6 @@ import std::int::ToInt import std::io::(Error, Read, Write) import std::string::IntoString -# Returns `True` if the program is running on Windows. -fn pub windows? -> Bool { - _INKO.env_platform == 0 -} - -# Returns `True` if the program is running on Linux. -fn pub linux? -> Bool { - _INKO.env_platform == 3 -} - -# Returns `True` if the program is running on a Unix system. -fn pub unix? -> Bool { - _INKO.env_platform != 0 -} - -# Returns `True` if the program is running on Mac OS. -fn pub mac? -> Bool { - _INKO.env_platform == 1 -} - # Returns the number of available CPU cores of the current system. # # This returns the number of _logical_ cores, with a minimum value of 1. @@ -93,13 +73,13 @@ impl ToInt for Stream { # # import std::sys::(Command, Stream) # -# try! Command.new('ls').stdout(Stream.Piped).spawn +# Command.new('ls').stdout(Stream.Piped).spawn.unwrap # # We can also ignore a stream: # # import std::sys::(Command, Stream) # -# try! Command.new('ls').stderr(Stream.Null).spawn +# Command.new('ls').stderr(Stream.Null).spawn.unwrap # # # Waiting for the child process # @@ -109,9 +89,9 @@ impl ToInt for Stream { # # import std::sys::Command # -# let child = try! Command.new('ls') +# let child = Command.new('ls').unwrap # -# try! child.wait +# child.wait.unwrap # # There's also `ChildProcess.try_wait`, which returns immediately if the process # is still running; instead of waiting for it to finish. @@ -122,11 +102,11 @@ impl ToInt for Stream { # # import std::sys::Command # -# let child = try! Command.new('ls') +# let child = Command.new('ls').unwrap # let bytes = ByteArray.new # -# try! child.wait -# try! child.stdout.read_all(bytes) +# child.wait.unwrap +# child.stdout.read_all(bytes).unwrap class pub Command { # The path to the program to spawn. let @program: String @@ -173,8 +153,8 @@ class pub Command { # import std::sys::Command # # Command.new('/usr/bin/ls') - fn pub static new(program: IntoString) -> Self { - Self { + fn pub static new(program: IntoString) -> Command { + Command { @program = program.into_string, @stdin = Stream.Inherit, @stdout = Stream.Inherit, @@ -241,7 +221,7 @@ class pub Command { # # Command.new('env').variable(name: 'FOO', value: 'bar') fn pub mut variable(name: String, value: String) { - @variables[name] = value + @variables.set(name, value) } # Adds or updates multiple environment variables to the command. @@ -285,10 +265,10 @@ class pub Command { # # # Examples # - # let child = try! Command.new('ls').spawn + # let child = Command.new('ls').spawn.unwrap # - # try! child.wait - fn pub spawn !! Error -> ChildProcess { + # child.wait.unwrap + fn pub spawn -> Result[ChildProcess, Error] { let vars = [] @variables.iter.each fn (entry) { @@ -296,23 +276,18 @@ class pub Command { vars.push(entry.value) } - let directory = @directory.as_ref.unwrap_or(ref '') - let raw_child = - try { - _INKO.child_process_spawn( - @program.to_string, - @arguments, - vars, - @stdin.to_int, - @stdout.to_int, - @stderr.to_int, - directory, - ) - } else (code) { - throw Error.from_int(code) - } - - ChildProcess { @raw = raw_child } + match _INKO.child_process_spawn( + @program.to_string, + @arguments, + vars, + @stdin.to_int, + @stdout.to_int, + @stderr.to_int, + @directory.as_ref.unwrap_or(ref ''), + ) { + case { @tag = 0, @value = v } -> Result.Ok(ChildProcess { @raw = v }) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } } @@ -321,8 +296,8 @@ class pub ExitStatus { # The raw exit code. let @code: Int - fn pub static new(code: Int) -> Self { - Self { @code = code } + fn pub static new(code: Int) -> ExitStatus { + ExitStatus { @code = code } } # Returns `True` if the status signals success. @@ -349,8 +324,8 @@ class pub Stdin { # The child process the stream is connected to. let @process: ref ChildProcess - fn pub static new(process: ref ChildProcess) -> Self { - Self { @process = process } + fn pub static new(process: ref ChildProcess) -> Stdin { + Stdin { @process = process } } } @@ -361,27 +336,27 @@ impl Drop for Stdin { } impl Write for Stdin { - fn pub mut write_bytes(bytes: ref ByteArray) !! Error -> Int { - try { - _INKO.child_process_stdin_write_bytes(@process.raw, bytes) - } else (code) { - throw Error.from_int(code) + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Error] { + match _INKO.child_process_stdin_write_bytes(@process.raw, bytes) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut write_string(string: String) !! Error -> Int { - try { - _INKO.child_process_stdin_write_string(@process.raw, string.to_string) - } else (code) { - throw Error.from_int(code) + fn pub mut write_string(string: String) -> Result[Int, Error] { + match _INKO.child_process_stdin_write_string( + @process.raw, + string.to_string + ) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } - fn pub mut flush !! Error { - try { - _INKO.child_process_stdin_flush(@process.raw) - } else (code) { - throw Error.from_int(code) + fn pub mut flush -> Result[Nil, Error] { + match _INKO.child_process_stdin_flush(@process.raw) { + case { @tag = 0, @value = _ } -> Result.Ok(nil) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } } @@ -391,8 +366,8 @@ class pub Stdout { # The child process the stream is connected to. let @process: ref ChildProcess - fn pub static new(process: ref ChildProcess) -> Self { - Self { @process = process } + fn pub static new(process: ref ChildProcess) -> Stdout { + Stdout { @process = process } } } @@ -403,11 +378,10 @@ impl Drop for Stdout { } impl Read for Stdout { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try { - _INKO.child_process_stdout_read(@process.raw, into, size) - } else (code) { - throw Error.from_int(code) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.child_process_stdout_read(@process.raw, into, size) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } } @@ -417,8 +391,8 @@ class pub Stderr { # The child process the stream is connected to. let @process: ref ChildProcess - fn pub static new(process: ref ChildProcess) -> Self { - Self { @process = process } + fn pub static new(process: ref ChildProcess) -> Stderr { + Stderr { @process = process } } } @@ -429,11 +403,10 @@ impl Drop for Stderr { } impl Read for Stderr { - fn pub mut read(into: mut ByteArray, size: Int) !! Error -> Int { - try { - _INKO.child_process_stderr_read(@process.raw, into, size) - } else (code) { - throw Error.from_int(code) + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Error] { + match _INKO.child_process_stderr_read(@process.raw, into, size) { + case { @tag = 0, @value = v } -> Result.Ok(v as Int) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } } } @@ -461,11 +434,11 @@ class pub ChildProcess { # Waits for the process to terminate. # # The STDIN stream is closed before waiting. - fn pub wait !! Error -> ExitStatus { - let id = - try _INKO.child_process_wait(@raw) else (code) throw Error.from_int(code) - - ExitStatus.new(id) + fn pub wait -> Result[ExitStatus, Error] { + match _INKO.child_process_wait(@raw) { + case { @tag = 0, @value = v } -> Result.Ok(ExitStatus.new(v as Int)) + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) + } } # Returns the exit status without blocking. @@ -473,13 +446,15 @@ class pub ChildProcess { # If the process is still running, a None is returned. # # This method doesn't close the STDIN stream before waiting. - fn pub try_wait !! Error -> Option[ExitStatus] { - let id = try { - _INKO.child_process_try_wait(@raw) - } else (code) { - throw Error.from_int(code) + fn pub try_wait -> Result[Option[ExitStatus], Error] { + match _INKO.child_process_try_wait(@raw) { + case { @tag = 0, @value = v } if (v as Int) == -1 -> { + Result.Ok(Option.None) + } + case { @tag = 0, @value = v } -> { + Result.Ok(Option.Some(ExitStatus.new(v as Int))) + } + case { @tag = _, @value = e } -> Result.Error(Error.from_int(e as Int)) } - - if id == -1 { Option.None } else { Option.Some(ExitStatus.new(id)) } } } diff --git a/libstd/src/std/test.inko b/std/src/std/test.inko similarity index 53% rename from libstd/src/std/test.inko rename to std/src/std/test.inko index f99a83786..6dd67a2c0 100644 --- a/libstd/src/std/test.inko +++ b/std/src/std/test.inko @@ -59,17 +59,21 @@ # documentation of the `Test` type. import std::cmp::(Compare, Equal) import std::debug -import std::fmt +import std::env +import std::fmt::(DefaultFormatter, Format, Formatter) import std::fs::path::Path import std::io::Write -import std::process::(sleep, poll) +import std::process import std::rand::(Random, Shuffle) import std::stdio::STDOUT -import std::sys +import std::sys::(Command, ExitStatus, Stream, cpu_cores, exit) import std::time::(Duration, Instant) -fn format(value: ref fmt::Format) -> String { - let formatter: fmt::Formatter = fmt::DefaultFormatter.new +let CHILD_VAR = 'INKO_TEST_CHILD' +let TEST_PREFIX = 'test_' + +fn format(value: ref Format) -> String { + let formatter: Formatter = DefaultFormatter.new value.fmt(formatter) formatter.into_string @@ -106,8 +110,8 @@ class pub Test { path: Path, line: Int, code: uni fn (mut Test) - ) -> Self { - Self { + ) -> Test { + Test { @id = id, @name = name, @path = path, @@ -118,21 +122,21 @@ class pub Test { } # Asserts that the given arguments are equal to each other. - fn pub mut equal[T: Equal + fmt::Format](got: ref T, expected: ref T) { + fn pub mut equal[T: Equal[T] + Format](got: ref T, expected: ref T) { if got == expected { return } @failures.push(Failure.new(format(got), format(expected))) } # Asserts that the given arguments are not equal to each other. - fn pub mut not_equal[T: Equal + fmt::Format](got: ref T, expected: ref T) { + fn pub mut not_equal[T: Equal[T] + Format](got: ref T, expected: ref T) { if got != expected { return } @failures.push(Failure.new(format(got), format(expected))) } # Asserts that `got` is greater than `minimum`. - fn pub mut greater[T: Compare + fmt::Format](got: ref T, minimum: ref T) { + fn pub mut greater[T: Compare[T] + Format](got: ref T, minimum: ref T) { if got > minimum { return } @failures.push(Failure.new(format(got), "> {format(minimum)}")) @@ -152,34 +156,13 @@ class pub Test { @failures.push(Failure.new('true', 'false')) } - # Asserts that the given closure throws a value. - fn pub mut throw[E](block: fn !! E) { - try block.call else return - - @failures.push(Failure.new('no value is thrown', 'a value to be thrown')) - } - - # Asserts that the given closure doesn't throw a value. - fn pub mut no_throw[E: fmt::Format](block: fn !! E) { - try block.call else (err) { - @failures.push(Failure.new(format(err), 'no value is thrown')) + fn matches?(filter: ref Filter) -> Bool { + match filter { + case Pattern(pat) -> @name.contains?(pat) + case Location(path) -> @path == path + case None -> true } } - - # Asserts that an expression (which may throw) equals the given value. - fn pub mut try_equal[T: Equal + fmt::Format, E: fmt::Format]( - block: fn !! E -> T, - expected: ref T - ) { - let got = try block.call else (err) { - @failures.push(Failure.new(format(err), format(expected))) - return - } - - if got == expected { return } - - @failures.push(Failure.new(format(got), format(expected))) - } } # A type used for reporting test progress. @@ -200,7 +183,7 @@ trait pub Reporter { fn pub move finished(duration: Duration, seed: Int) -> Bool } -# A test reporter that writes to STDOUT. +# A test reporter that prints results in a simple text based format. class pub Plain { let @out: Write let @tests: Int @@ -208,8 +191,8 @@ class pub Plain { let @colors: Bool # Returns a new reporter that writes to the given output stream. - fn pub static new(out: Write, colors: Bool) -> Self { - Self { @out = out, @tests = 0, @failed = [], @colors = colors } + fn pub static new(out: Write, colors: Bool) -> Plain { + Plain { @out = out, @tests = 0, @failed = [], @colors = colors } } fn red(value: String) -> String { @@ -224,37 +207,36 @@ class pub Plain { impl Reporter for Plain { fn pub mut passed(test: Test) { @tests += 1 - - try! @out.write_string(green('.')) - try! @out.flush + @out.write_string(green('.')).unwrap + @out.flush.unwrap } fn pub mut failed(test: Test) { @tests += 1 - @failed.push(test) - - try! @out.write_string(red('F')) - try! @out.flush + @out.write_string(red('F')).unwrap + @out.flush.unwrap } fn pub move finished(duration: Duration, seed: Int) -> Bool { if @failed.length > 0 { - try! @out.print("\n\nFailures:") + @out.print("\n\nFailures:").unwrap @failed.iter.each_with_index fn (test_index, test) { test.failures.iter.each_with_index fn (failure_index, fail) { let num = "{test_index + failure_index + 1}." let indent = ' '.repeat(num.characters.count) - try! @out.print( - " + @out + .print( + " {num} Test: {test.name} {indent} Line: {fail.path}:{fail.line} {indent} {green("expected:")} {fail.expected} {indent} {red("got:")} {fail.got}" - ) + ) + .unwrap } } } @@ -270,21 +252,35 @@ impl Reporter for Plain { let failures = if failed > 0 { red("{failed} failures") } else { green('0 failures') } - try! @out.print( - "\nFinished running {@tests} tests in {dur}, {failures}, seed: {seed}" - ) + @out + .print( + "\nFinished running {@tests} tests in {dur}, {failures}, seed: {seed}" + ) + .unwrap @failed.empty? } } class async Runner { - fn async run(test: uni Test) -> uni Test { - recover { - let test = recover test + let @input: Channel[uni Test] + let @output: Channel[uni Test] + + fn async run { + loop { + let test = match @input.try_receive { + case Some(test) -> test + case None -> return + } + + let result = recover { + let test = recover test - test.code.call(test) - test + test.code.call(test) + test + } + + @output.send(result) } } } @@ -306,11 +302,16 @@ class pub Failure { # Returns a new failure for the given reason. # # The source location is determined automatically based on the call stack. - fn pub static new(got: String, expected: String) -> Self { + fn pub static new(got: String, expected: String) -> Failure { # This skips the call to stacktrace(), the call to this new(), and its # caller (which would be e.g. `assert_equal`). - match debug.stacktrace(skip: 3).pop.unwrap { - case { @path = path, @line = line } -> Self { + let frame = debug + .stacktrace(skip: 3) + .pop + .expect('at least one stack frame must be present') + + match frame { + case { @path = path, @line = line } -> Failure { @got = got, @expected = expected, @path = path, @@ -320,6 +321,138 @@ class pub Failure { } } +# A type describing how to filter out tests. +class pub enum Filter { + # Only run tests of which the description matches this pattern. + case Pattern(String) + + # Only run tests defined in the given file. + case Location(Path) + + # No filter is applied. + case None + + # Parses a `String` into a filter. + # + # If the `String` is a valid path, a `Location` is returned, if not a + # `Pattern` is returned. + fn pub static from_string(string: String) -> Filter { + if string.empty? { return Filter.None } + + match Path.new(string).expand { + case Ok(path) -> Filter.Location(path) + case _ -> Filter.Pattern(string) + } + } +} + +impl Equal[Filter] for Filter { + fn pub ==(other: ref Filter) -> Bool { + match self { + case Pattern(lhs) -> match other { + case Pattern(rhs) -> lhs == rhs + case _ -> false + } + case Location(lhs) -> match other { + case Location(rhs) -> lhs == rhs + case _ -> false + } + case None -> match other { + case None -> true + case _ -> false + } + } + } +} + +impl Format for Filter { + fn pub fmt(formatter: mut Formatter) { + match self { + case Pattern(val) -> { + formatter.write('Pattern(') + val.fmt(formatter) + formatter.write(')') + } + case Location(path) -> { + formatter.write('Location(') + path.fmt(formatter) + formatter.write(')') + } + case None -> formatter.write('None') + } + } +} + +# A child process to run as part of a unit test. +class pub Process { + let @cmd: Command + let @stdin: String + + fn static new(id: Int) -> Process { + let cmd = Command.new(env.executable.unwrap) + + cmd.stdin(Stream.Piped) + cmd.stdout(Stream.Piped) + cmd.stderr(Stream.Piped) + cmd.variable(CHILD_VAR, id.to_string) + Process { @cmd = cmd, @stdin = '' } + } + + # Adds an argument to the process. + fn pub mut argument(value: String) { + @cmd.argument(value) + } + + # Adds or updates an environment variable to the process. + fn pub mut variable(name: String, value: String) { + @cmd.variable(name, value) + } + + # Sets the data to write to STDIN. + fn pub mut stdin(bytes: String) { + @stdin = bytes + } + + # Spawns the process, waits for it to finish, and returns an `Output` + # containing the results. + fn pub move spawn -> Output { + let child = match @cmd.spawn { + case Ok(child) -> child + case Error(err) -> panic("Failed to spawn the child process: {err}") + } + + let _ = child.stdin.write_string(@stdin) + let stdout = ByteArray.new + let stderr = ByteArray.new + + child.stdout.read_all(stdout) + child.stderr.read_all(stderr) + + let status = match child.wait { + case Ok(val) -> val + case Error(err) -> panic("Failed to wait for the child process: {err}") + } + + Output { + @status = status, + @stdout = stdout.into_string, + @stderr = stderr.into_string + } + } +} + +# The output of a sub process. +class pub Output { + # The exit status of the process. + let pub @status: ExitStatus + + # The data written to STDOUT. + let pub @stdout: String + + # The data written to STDERR. + let pub @stderr: String +} + # A collection of tests to run. class pub Tests { # The number of tests to run concurrently. @@ -332,10 +465,8 @@ class pub Tests { # This defaults to the `Plain` reporter that writes to STDOUT. let pub @reporter: Reporter - # The pattern to use for filtering out tests. - # - # By default no tests are filtered out. - let pub @pattern: Option[String] + # The filter to apply to decide which tests to run. + let pub @filter: Filter # The seed to use for ordering the tests. # @@ -351,15 +482,20 @@ class pub Tests { # execution order you'll also need to set the `concurrency` field to `1`. let pub @seed: Option[Int] + # All the tests that have been registered. let @tests: Array[uni Test] + # Closures to call as part of a forking test/child process. + let @children: Array[fn] + # Returns a new test tests with its default settings. - fn pub static new -> Self { - Self { + fn pub static new -> Tests { + Tests { @tests = [], - @concurrency = sys.cpu_cores, - @reporter = Plain.new(out: STDOUT.new, colors: sys.unix?), - @pattern = Option.None, + @children = [], + @concurrency = cpu_cores, + @reporter = Plain.new(out: STDOUT.new, colors: true), + @filter = Filter.None, @seed = Option.None, } } @@ -368,33 +504,62 @@ class pub Tests { fn pub mut test(name: String, code: uni fn (mut Test)) { let id = @tests.length let test = recover { - match debug.stacktrace(skip: 2).pop.unwrap { - case { @path = path, @line = line } -> - Test.new(id, name, path, line, code) - } + let trace = debug.stacktrace(skip: 2) + let frame = trace + .reverse_iter + .find fn (frame) { frame.path.tail.starts_with?(TEST_PREFIX) } + .else fn { trace.opt(trace.length - 1) } + .expect('at least one stack frame must be present') + .clone + + Test.new(id, name, frame.path.clone, frame.line, code) } @tests.push(test) } + # Registers a new test using a fork/subprocess. + # + # This doesn't use the actual `fork()` system call. Instead, a new instance of + # the test executable is spawned such that it only runs the closure specified + # in the `child` argument. + fn pub mut fork(name: String, child: fn, test: uni fn (mut Test, Process)) { + let id = @children.length + + @children.push(child) + test(name, fn move (t) { test.call(t, Process.new(id)) }) + } + + # Registers a new test that asserts the given closure triggers a panic. + fn pub mut panic(name: String, code: uni fn) { + fork(name, code) fn (test, process) { + let output = process.spawn + let code = output.status.to_int + + if code == 101 { return } + + test.failures.push(Failure.new( + "the process exited with status {code}", + 'the process to panic with exit status 101' + )) + } + } + # Runs all the tests. fn pub move run { - let tests = match @pattern { - case Some(pat) -> { - @tests - .into_iter - .select_map fn (test) { - if test.name.contains?(pat) { - Option.Some(test) - } else { - Option.None - } - } - .to_array - } - case _ -> @tests + match env.opt(CHILD_VAR) { + case Some(id) -> return run_child(id) + case _ -> {} } + let filter = @filter + let tests = @tests + .into_iter + .select_map fn (test) { + if test.matches?(filter) { Option.Some(test) } else { Option.None } + } + .to_array + let seed = match @seed { case Some(seed) -> seed case _ -> Random.new.int @@ -405,30 +570,36 @@ class pub Tests { Shuffle.from_int(seed).sort(tests) let rep = @reporter - let futures = [] let start = Instant.new + let input = Channel.new(tests.length) + let output = Channel.new(tests.length) + let size = tests.length - while tests.length > 0 or futures.length > 0 { - # This schedules up to N concurrent tests, but only if we have any tests - # left to schedule. - while futures.length < @concurrency { - match tests.pop { - case Some(test) -> futures.push(async Runner {}.run(test)) - case _ -> break - } - } + # We send the tests first, as the test runners abort when the input channel + # is empty. + tests.into_iter.each fn (test) { input.send(test) } + + @concurrency.times fn (_) { + Runner { @input = input, @output = output }.run + } - poll(futures).into_iter.each fn (fut) { - let test: Test = fut.await + size.times fn (_) { + let test = recover output.receive - if test.failures.empty? { rep.passed(test) } else { rep.failed(test) } - } + if test.failures.empty? { rep.passed(test) } else { rep.failed(test) } } if rep.finished(start.elapsed, seed) { - sys.exit(status: 0) + exit(status: 0) } else { - sys.exit(status: 1) + exit(status: 1) + } + } + + fn pub move run_child(id: String) { + match Int.from_base10(id).then(fn (id) { @children.opt_mut(id) }) { + case Some(block) -> block.call + case _ -> process.panic("The child ID '{id}' is invalid") } } } diff --git a/libstd/src/std/time.inko b/std/src/std/time.inko similarity index 89% rename from libstd/src/std/time.inko rename to std/src/std/time.inko index f0fe3c820..311c6c397 100644 --- a/libstd/src/std/time.inko +++ b/std/src/std/time.inko @@ -48,7 +48,7 @@ class pub Duration { # # Duration.from_secs(10.5) fn pub static from_secs(secs: ToFloat) -> Duration { - Self { @nanos = (secs.to_float * NANOS_PER_SEC).to_int } + Duration { @nanos = (secs.to_float * NANOS_PER_SEC).to_int } } # Creates a new `Duration` from the given number of milliseconds. @@ -59,7 +59,7 @@ class pub Duration { # # Duration.from_millis(10) fn pub static from_millis(millis: ToInt) -> Duration { - Self { @nanos = millis.to_int * MICROS_PER_SEC } + Duration { @nanos = millis.to_int * MICROS_PER_SEC } } # Creates a new `Duration` from the given number of microseconds. @@ -70,7 +70,7 @@ class pub Duration { # # Duration.from_micros(10) fn pub static from_micros(micros: ToInt) -> Duration { - Self { @nanos = micros.to_int * MILLIS_PER_SEC } + Duration { @nanos = micros.to_int * MILLIS_PER_SEC } } # Creates a new `Duration` from the given number of nanoseconds. @@ -81,7 +81,7 @@ class pub Duration { # # Duration.from_nanos(10) fn pub static from_nanos(nanos: ToInt) -> Duration { - Self { @nanos = nanos.to_int } + Duration { @nanos = nanos.to_int } } # Returns the duration in seconds. @@ -150,32 +150,32 @@ impl ToInstant for Duration { } } -impl Clone for Duration { - fn pub clone -> Self { - Self { @nanos = @nanos } +impl Clone[Duration] for Duration { + fn pub clone -> Duration { + Duration { @nanos = @nanos } } } -impl Add[Duration] for Duration { - fn pub +(other: ref Duration) -> Self { +impl Add[Duration, Duration] for Duration { + fn pub +(other: ref Duration) -> Duration { Duration { @nanos = @nanos + other.nanos } } } -impl Subtract[Duration] for Duration { - fn pub -(other: ref Duration) -> Self { +impl Subtract[Duration, Duration] for Duration { + fn pub -(other: ref Duration) -> Duration { Duration { @nanos = @nanos - other.nanos } } } -impl Compare for Duration { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Duration] for Duration { + fn pub cmp(other: ref Duration) -> Ordering { @nanos.cmp(other.nanos) } } -impl Equal for Duration { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Duration] for Duration { + fn pub ==(other: ref Duration) -> Bool { @nanos == other.nanos } } @@ -243,7 +243,7 @@ class pub DateTime { # import std::time::DateTime # # DateTime.new - fn pub static new -> Self { + fn pub static new -> DateTime { from_timestamp(_INKO.time_system, _INKO.time_system_offset) } @@ -257,7 +257,7 @@ class pub DateTime { # import std::time::DateTime # # DateTime.utc - fn pub static utc -> Self { + fn pub static utc -> DateTime { from_timestamp(time: _INKO.time_system, utc_offset: 0) } @@ -270,7 +270,7 @@ class pub DateTime { # import std::time::DateTime # # DateTime.from_timestamp(time: 1661546853, utc_offset: 0).year # => 2022 - fn pub static from_timestamp(time: ToFloat, utc_offset: Int) -> Self { + fn pub static from_timestamp(time: ToFloat, utc_offset: Int) -> DateTime { # This implementation is based on the algorithms as described on # http://howardhinnant.github.io/date_algorithms.html, specifically the # `civil_from_days()` algorithm. @@ -303,7 +303,7 @@ class pub DateTime { let minute = (day_secs % SECS_PER_HOUR) / 60 let hour = day_secs / SECS_PER_HOUR - Self { + DateTime { @year = year, @month = month, @day = day, @@ -357,7 +357,7 @@ class pub DateTime { fn pub day_of_year -> Int { let days = if leap_year? { LEAP_DAYS } else { NORMAL_DAYS } - days[@month - 1] + @day + days.get(@month - 1) + @day } # Returns the number of days between `self` and the Unix epoch. @@ -390,14 +390,14 @@ class pub DateTime { # Converts the `DateTime` to another `DateTime` that uses UTC as the # timezone. - fn pub to_utc -> Self { + fn pub to_utc -> DateTime { DateTime.from_timestamp(time: to_float, utc_offset: 0) } } -impl Clone for DateTime { - fn pub clone -> Self { - Self { +impl Clone[DateTime] for DateTime { + fn pub clone -> DateTime { + DateTime { @year = @year, @month = @month, @day = @day, @@ -461,30 +461,30 @@ impl ToFloat for DateTime { } } -impl Add[Duration] for DateTime { - fn pub +(other: ref Duration) -> Self { +impl Add[Duration, DateTime] for DateTime { + fn pub +(other: ref Duration) -> DateTime { let timestamp = to_float + other.to_secs DateTime.from_timestamp(timestamp, utc_offset: @utc_offset) } } -impl Subtract[Duration] for DateTime { - fn pub -(other: ref Duration) -> Self { +impl Subtract[Duration, DateTime] for DateTime { + fn pub -(other: ref Duration) -> DateTime { let timestamp = to_float - other.to_secs DateTime.from_timestamp(timestamp, utc_offset: @utc_offset) } } -impl Compare for DateTime { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[DateTime] for DateTime { + fn pub cmp(other: ref DateTime) -> Ordering { to_float.cmp(other.to_float) } } -impl Equal for DateTime { - fn pub ==(other: ref Self) -> Bool { +impl Equal[DateTime] for DateTime { + fn pub ==(other: ref DateTime) -> Bool { @year == other.year and @month == other.month and @day == other.day @@ -519,8 +519,8 @@ class pub Instant { let @nanos: Int # Returns a new `Instant` representing the current time. - fn pub static new -> Self { - Self { @nanos = _INKO.time_monotonic } + fn pub static new -> Instant { + Instant { @nanos = _INKO.time_monotonic } } # Returns a `Duration` measuring the time elapsed since the point in time @@ -568,9 +568,9 @@ impl ToInstant for Instant { } } -impl Clone for Instant { - fn pub clone -> Self { - Self { @nanos = @nanos } +impl Clone[Instant] for Instant { + fn pub clone -> Instant { + Instant { @nanos = @nanos } } } @@ -586,8 +586,8 @@ impl ToFloat for Instant { } } -impl Add[Duration] for Instant { - fn pub +(other: ref Duration) -> Self { +impl Add[Duration, Instant] for Instant { + fn pub +(other: ref Duration) -> Instant { let nanos = @nanos + other.nanos if nanos < 0 { panic("Instant can't represent a negative time ({nanos})") } @@ -596,8 +596,8 @@ impl Add[Duration] for Instant { } } -impl Subtract[Duration] for Instant { - fn pub -(other: ref Duration) -> Self { +impl Subtract[Duration, Instant] for Instant { + fn pub -(other: ref Duration) -> Instant { let nanos = @nanos - other.nanos if nanos < 0 { panic("Instant can't represent a negative time ({nanos})") } @@ -606,14 +606,14 @@ impl Subtract[Duration] for Instant { } } -impl Compare for Instant { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Instant] for Instant { + fn pub cmp(other: ref Instant) -> Ordering { @nanos.cmp(other.nanos) } } -impl Equal for Instant { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Instant] for Instant { + fn pub ==(other: ref Instant) -> Bool { @nanos == other.nanos } } diff --git a/libstd/src/std/tuple.inko b/std/src/std/tuple.inko similarity index 76% rename from libstd/src/std/tuple.inko rename to std/src/std/tuple.inko index a2ac887de..01eaf2e30 100644 --- a/libstd/src/std/tuple.inko +++ b/std/src/std/tuple.inko @@ -44,8 +44,8 @@ class builtin Tuple1[A] { let pub @0: A } -impl Equal for Tuple1 if A: Equal { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Tuple1[A]] for Tuple1 if A: Equal[A] { + fn pub ==(other: ref Tuple1[A]) -> Bool { @0 == other.0 } } @@ -56,8 +56,8 @@ impl Hash for Tuple1 if A: Hash { } } -impl Clone for Tuple1 if A: Clone { - fn pub clone -> Self { +impl Clone[Tuple1[A]] for Tuple1 if A: Clone[A] { + fn pub clone -> Tuple1[A] { (@0.clone,) } } @@ -76,8 +76,8 @@ class builtin Tuple2[A, B] { let pub @1: B } -impl Equal for Tuple2 if A: Equal, B: Equal { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Tuple2[A, B]] for Tuple2 if A: Equal[A], B: Equal[B] { + fn pub ==(other: ref Tuple2[A, B]) -> Bool { @0 == other.0 and @1 == other.1 } } @@ -89,8 +89,8 @@ impl Hash for Tuple2 if A: Hash, B: Hash { } } -impl Clone for Tuple2 if A: Clone, B: Clone { - fn pub clone -> Self { +impl Clone[Tuple2[A, B]] for Tuple2 if A: Clone[A], B: Clone[B] { + fn pub clone -> Tuple2[A, B] { (@0.clone, @1.clone) } } @@ -112,8 +112,11 @@ class builtin Tuple3[A, B, C] { let pub @2: C } -impl Equal for Tuple3 if A: Equal, B: Equal, C: Equal { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Tuple3[A, B, C]] for Tuple3 +if + A: Equal[A], B: Equal[B], C: Equal[C] +{ + fn pub ==(other: ref Tuple3[A, B, C]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 } } @@ -126,8 +129,8 @@ impl Hash for Tuple3 if A: Hash, B: Hash, C: Hash { } } -impl Clone for Tuple3 if A: Clone, B: Clone, C: Clone { - fn pub clone -> Self { +impl Clone[Tuple3[A, B, C]] for Tuple3 if A: Clone[A], B: Clone[B], C: Clone[C] { + fn pub clone -> Tuple3[A, B, C] { (@0.clone, @1.clone, @2.clone) } } @@ -152,8 +155,11 @@ class builtin Tuple4[A, B, C, D] { let pub @3: D } -impl Equal for Tuple4 if A: Equal, B: Equal, C: Equal, D: Equal { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Tuple4[A, B, C, D]] for Tuple4 +if + A: Equal[A], B: Equal[B], C: Equal[C], D: Equal[D] +{ + fn pub ==(other: ref Tuple4[A, B, C, D]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 and @3 == other.3 } } @@ -167,8 +173,11 @@ impl Hash for Tuple4 if A: Hash, B: Hash, C: Hash, D: Hash { } } -impl Clone for Tuple4 if A: Clone, B: Clone, C: Clone, D: Clone { - fn pub clone -> Self { +impl Clone[Tuple4[A, B, C, D]] for Tuple4 +if + A: Clone[A], B: Clone[B], C: Clone[C], D: Clone[D] +{ + fn pub clone -> Tuple4[A, B, C, D] { (@0.clone, @1.clone, @2.clone, @3.clone) } } @@ -196,8 +205,11 @@ class builtin Tuple5[A, B, C, D, E] { let pub @4: E } -impl Equal for Tuple5 if A: Equal, B: Equal, C: Equal, D: Equal, E: Equal { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Tuple5[A, B, C, D, E]] for Tuple5 +if + A: Equal[A], B: Equal[B], C: Equal[C], D: Equal[D], E: Equal[E] +{ + fn pub ==(other: ref Tuple5[A, B, C, D, E]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 @@ -216,8 +228,11 @@ impl Hash for Tuple5 if A: Hash, B: Hash, C: Hash, D: Hash, E: Hash { } } -impl Clone for Tuple5 if A: Clone, B: Clone, C: Clone, D: Clone, E: Clone { - fn pub clone -> Self { +impl Clone[Tuple5[A, B, C, D, E]] for Tuple5 +if + A: Clone[A], B: Clone[B], C: Clone[C], D: Clone[D], E: Clone[E] +{ + fn pub clone -> Tuple5[A, B, C, D, E] { (@0.clone, @1.clone, @2.clone, @3.clone, @4.clone) } } @@ -251,11 +266,11 @@ class builtin Tuple6[A, B, C, D, E, F] { let pub @5: F } -impl Equal for Tuple6 +impl Equal[Tuple6[A, B, C, D, E, F]] for Tuple6 if - A: Equal, B: Equal, C: Equal, D: Equal, E: Equal, F: Equal + A: Equal[A], B: Equal[B], C: Equal[C], D: Equal[D], E: Equal[E], F: Equal[F] { - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Tuple6[A, B, C, D, E, F]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 @@ -277,11 +292,11 @@ impl Hash for Tuple6 if A: Hash, B: Hash, C: Hash, D: Hash, E: Hash, F: Hash } } -impl Clone for Tuple6 +impl Clone[Tuple6[A, B, C, D, E, F]] for Tuple6 if - A: Clone, B: Clone, C: Clone, D: Clone, E: Clone, F: Clone + A: Clone[A], B: Clone[B], C: Clone[C], D: Clone[D], E: Clone[E], F: Clone[F] { - fn pub clone -> Self { + fn pub clone -> Tuple6[A, B, C, D, E, F] { (@0.clone, @1.clone, @2.clone, @3.clone, @4.clone, @5.clone) } } @@ -318,11 +333,12 @@ class builtin Tuple7[A, B, C, D, E, F, G] { let pub @6: G } -impl Equal for Tuple7 +impl Equal[Tuple7[A, B, C, D, E, F, G]] for Tuple7 if - A: Equal, B: Equal, C: Equal, D: Equal, E: Equal, F: Equal, G: Equal + A: Equal[A], B: Equal[B], C: Equal[C], D: Equal[D], E: Equal[E], F: Equal[F], + G: Equal[G] { - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Tuple7[A, B, C, D, E, F, G]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 @@ -348,11 +364,17 @@ if } } -impl Clone for Tuple7 +impl Clone[Tuple7[A, B, C, D, E, F, G]] for Tuple7 if - A: Clone, B: Clone, C: Clone, D: Clone, E: Clone, F: Clone, G: Clone + A: Clone[A], + B: Clone[B], + C: Clone[C], + D: Clone[D], + E: Clone[E], + F: Clone[F], + G: Clone[G] { - fn pub clone -> Self { + fn pub clone -> Tuple7[A, B, C, D, E, F, G] { (@0.clone, @1.clone, @2.clone, @3.clone, @4.clone, @5.clone, @6.clone) } } @@ -392,11 +414,12 @@ class builtin Tuple8[A, B, C, D, E, F, G, H] { let pub @7: H } -impl Equal for Tuple8 +impl Equal[Tuple8[A, B, C, D, E, F, G, H]] for Tuple8 if - A: Equal, B: Equal, C: Equal, D: Equal, E: Equal, F: Equal, G: Equal, H: Equal + A: Equal[A], B: Equal[B], C: Equal[C], D: Equal[D], E: Equal[E], F: Equal[F], + G: Equal[G], H: Equal[H] { - fn pub ==(other: ref Self) -> Bool { + fn pub ==(other: ref Tuple8[A, B, C, D, E, F, G, H]) -> Bool { @0 == other.0 and @1 == other.1 and @2 == other.2 @@ -424,11 +447,18 @@ if } } -impl Clone for Tuple8 +impl Clone[Tuple8[A, B, C, D, E, F, G, H]] for Tuple8 if - A: Clone, B: Clone, C: Clone, D: Clone, E: Clone, F: Clone, G: Clone, H: Clone + A: Clone[A], + B: Clone[B], + C: Clone[C], + D: Clone[D], + E: Clone[E], + F: Clone[F], + G: Clone[G], + H: Clone[H] { - fn pub clone -> Self { + fn pub clone -> Tuple8[A, B, C, D, E, F, G, H] { ( @0.clone, @1.clone, diff --git a/libstd/src/std/utf8.inko b/std/src/std/utf8.inko similarity index 100% rename from libstd/src/std/utf8.inko rename to std/src/std/utf8.inko diff --git a/std/test/helpers.inko b/std/test/helpers.inko new file mode 100644 index 000000000..d55b99e35 --- /dev/null +++ b/std/test/helpers.inko @@ -0,0 +1,137 @@ +import std::drop::(drop) +import std::env +import std::fmt::(DefaultFormatter, Format, Formatter) +import std::fs::file::(WriteOnlyFile, remove) +import std::fs::path::Path +import std::hash::(Hash, Hasher) +import std::hash::siphash::SipHasher13 +import std::sys::(Command, Stream) + +fn pub hash(value: ref Hash) -> Int { + let hasher: Hasher = SipHasher13.default + + value.hash(hasher) + hasher.finish +} + +fn pub fmt(value: ref Format) -> String { + let fmt: Formatter = DefaultFormatter.new + + value.fmt(fmt) + fmt.into_string +} + +fn pub compiler_path -> Path { + let base = match env.working_directory { + case Ok(path) -> path + case Error(err) -> panic("Failed to determine the working directory: {err}") + } + + let target = if base.join('test').directory? { + base.join('..').join('target') + } else if base.join('std').directory? { + base.join('target') + } else { + panic('Tests must be run in either the project root, or the std/ directory') + } + + let debug = target.join('debug').join('inko') + let release = target.join('release').join('inko') + + match (debug.created_at, release.created_at) { + case (Ok(deb), Ok(rel)) -> if deb >= rel { debug } else { release } + case (Ok(_), Error(_)) -> debug + case (Error(_), Ok(_)) -> release + case _ -> panic("The path to the compiler couldn't be determined") + } +} + +class pub Script { + let @id: Int + let @path: Path + let @code: String + let @cmd: Command + let @stdin: Option[String] + let @imports: Array[String] + + fn pub static new(id: Int, code: String) -> Script { + let path = env.temporary_directory.join("inko_test_script_{id}.inko") + let exe = compiler_path + let cmd = Command.new(exe) + + cmd.argument('run') + cmd.argument('-f') + cmd.argument('plain') + cmd.argument(path.to_string) + cmd.stdin(Stream.Null) + cmd.stderr(Stream.Piped) + cmd.stdout(Stream.Piped) + + Script { + @id = id, + @path = path, + @code = code, + @cmd = cmd, + @stdin = Option.None, + @imports = ['std::stdio::(STDIN, STDERR, STDOUT)'] + } + } + + fn pub move import(module: String) -> Script { + @imports.push(module) + self + } + + fn pub move stdin(input: String) -> Script { + @stdin = Option.Some(input) + + @cmd.stdin(Stream.Piped) + self + } + + fn pub move argument(value: String) -> Script { + @cmd.argument(value) + self + } + + fn pub move variable(name: String, value: String) -> Script { + @cmd.variable(name, value) + self + } + + fn pub move run -> String { + let file = WriteOnlyFile.new(@path.clone).unwrap + + @imports.into_iter.each fn (mod) { + file.write_string("import {mod}\n").unwrap + } + + file + .write_string( +"class async Main \{ + fn async main \{ + {@code} + } +} +" + ) + .unwrap + + file.flush.unwrap + + let child = @cmd.spawn.unwrap + let bytes = ByteArray.new + + match @stdin { + case Some(input) -> child.stdin.write_string(input).unwrap + case _ -> 0 + } + + child.wait.unwrap + child.stdout.read_all(bytes).unwrap + child.stderr.read_all(bytes).unwrap + drop(file) + remove(@path).unwrap + bytes.into_string + } +} diff --git a/libstd/test/main.inko b/std/test/main.inko similarity index 89% rename from libstd/test/main.inko rename to std/test/main.inko index dd45d2f00..ddc700294 100644 --- a/libstd/test/main.inko +++ b/std/test/main.inko @@ -1,4 +1,6 @@ import std::env +import std::test::(Filter, Tests) +import std::fs::path::Path import std::crypto::test_chacha import std::crypto::test_hash @@ -12,16 +14,15 @@ import std::endian::test_little import std::fs::test_dir import std::fs::test_file import std::fs::test_path +import std::hash::test_siphash import std::net::test_ip import std::net::test_socket -import std::test::Tests import std::test_array import std::test_bool import std::test_byte_array import std::test_cmp import std::test_debug import std::test_env -import std::test_ffi import std::test_float import std::test_fmt import std::test_int @@ -34,6 +35,7 @@ import std::test_option import std::test_process import std::test_rand import std::test_range +import std::test_result import std::test_set import std::test_stdio import std::test_string @@ -56,7 +58,6 @@ class async Main { test_debug.tests(tests) test_dir.tests(tests) test_env.tests(tests) - test_ffi.tests(tests) test_file.tests(tests) test_float.tests(tests) test_fmt.tests(tests) @@ -77,9 +78,11 @@ class async Main { test_process.tests(tests) test_rand.tests(tests) test_range.tests(tests) + test_result.tests(tests) test_set.tests(tests) test_sha1.tests(tests) test_sha2.tests(tests) + test_siphash.tests(tests) test_socket.tests(tests) test_stdio.tests(tests) test_string.tests(tests) @@ -89,8 +92,7 @@ class async Main { test_tuple.tests(tests) test_utf8.tests(tests) - tests.pattern = env.arguments.into_iter.next - + tests.filter = Filter.from_string(env.arguments.opt(0).unwrap_or('')) tests.run } } diff --git a/libstd/test/std/crypto/test_chacha.inko b/std/test/std/crypto/test_chacha.inko similarity index 100% rename from libstd/test/std/crypto/test_chacha.inko rename to std/test/std/crypto/test_chacha.inko diff --git a/libstd/test/std/crypto/test_hash.inko b/std/test/std/crypto/test_hash.inko similarity index 95% rename from libstd/test/std/crypto/test_hash.inko rename to std/test/std/crypto/test_hash.inko index fcce5dbf4..7a1efdf37 100644 --- a/libstd/test/std/crypto/test_hash.inko +++ b/std/test/std/crypto/test_hash.inko @@ -44,15 +44,15 @@ fn pub tests(t: mut Tests) { block.add_padding(8) fn { calls.push(true) } t.equal(calls.length, 1) - t.equal(block[10], 0x80) + t.equal(block.get(10), 0x80) } t.test('Block.index') fn (t) { let block = Block.new(4) - block[0] = 42 + block.set(0, 42) - t.equal(block[0], 42) + t.equal(block.get(0), 42) } t.test('Hash.to_string') fn (t) { diff --git a/libstd/test/std/crypto/test_math.inko b/std/test/std/crypto/test_math.inko similarity index 76% rename from libstd/test/std/crypto/test_math.inko rename to std/test/std/crypto/test_math.inko index ad70254fa..99758eb65 100644 --- a/libstd/test/std/crypto/test_math.inko +++ b/std/test/std/crypto/test_math.inko @@ -9,6 +9,13 @@ fn pub tests(t: mut Tests) { t.equal(math.rotate_left_u32(4138929511350741, 1), 1050536875) } + t.test('math.rotate_left_u64') fn (t) { + t.equal(math.rotate_left_u64(0, 2), 0) + t.equal(math.rotate_left_u64(123, 4), 1968) + t.equal(math.rotate_left_u64(1, 63), -9223372036854775808) + t.equal(math.rotate_left_u64(4138929511350741, 1), 8277859022701482) + } + t.test('math.rotate_right_u32') fn (t) { t.equal(math.rotate_right_u32(0, 2), 0) t.equal(math.rotate_right_u32(123, 4), 2952790023) diff --git a/libstd/test/std/crypto/test_md5.inko b/std/test/std/crypto/test_md5.inko similarity index 92% rename from libstd/test/std/crypto/test_md5.inko rename to std/test/std/crypto/test_md5.inko index 8c420e92c..ce09d904a 100644 --- a/libstd/test/std/crypto/test_md5.inko +++ b/std/test/std/crypto/test_md5.inko @@ -19,10 +19,10 @@ fn pub tests(t: mut Tests) { hasher.write('123456789012345678901234567890123456789'.to_byte_array) hasher.write('123456789012345678901234567890123456789'.to_byte_array) - t.equal(hasher.finalize.to_string, '9d86352a67b623f0dc685101cef98dd9') + t.equal(hasher.finish.to_string, '9d86352a67b623f0dc685101cef98dd9') } - t.test('Md5.finalize') fn (t) { + t.test('Md5.finish') fn (t) { let cases = [ ('', 'd41d8cd98f00b204e9800998ecf8427e'), ('a', '0cc175b9c0f1b6a831c399e269772661'), @@ -55,7 +55,7 @@ fn pub tests(t: mut Tests) { let hasher = Md5.new hasher.write(pair.0.to_byte_array) - t.equal(hasher.finalize.to_string, pair.1) + t.equal(hasher.finish.to_string, pair.1) } } } diff --git a/libstd/test/std/crypto/test_poly1305.inko b/std/test/std/crypto/test_poly1305.inko similarity index 97% rename from libstd/test/std/crypto/test_poly1305.inko rename to std/test/std/crypto/test_poly1305.inko index a01dd07e4..674c8bf61 100644 --- a/libstd/test/std/crypto/test_poly1305.inko +++ b/std/test/std/crypto/test_poly1305.inko @@ -112,7 +112,7 @@ fn test_data -> Array[(Array[Int], Array[Int], Array[Int])] { fn pub tests(t: mut Tests) { t.test('Poly1305.hash') fn (t) { let tests = test_data - let test = tests[0] + let test = tests.get(0) let key = ByteArray.from_array(test.0) let input = ByteArray.from_array(test.1) let output = ByteArray.from_array(test.2) @@ -120,7 +120,7 @@ fn pub tests(t: mut Tests) { t.equal(Poly1305.hash(key, input).bytes, output) } - t.test('Poly1305.finalize') fn (t) { + t.test('Poly1305.finish') fn (t) { test_data.into_iter.each fn (tuple) { let key = tuple.0 let input = ByteArray.from_array(tuple.1) @@ -128,7 +128,7 @@ fn pub tests(t: mut Tests) { let hasher = Poly1305.new(ByteArray.from_array(key)) hasher.write(input) - t.equal(hasher.finalize.bytes, output) + t.equal(hasher.finish.bytes, output) } } } diff --git a/libstd/test/std/crypto/test_sha1.inko b/std/test/std/crypto/test_sha1.inko similarity index 92% rename from libstd/test/std/crypto/test_sha1.inko rename to std/test/std/crypto/test_sha1.inko index fb1b4ff0e..1e2507934 100644 --- a/libstd/test/std/crypto/test_sha1.inko +++ b/std/test/std/crypto/test_sha1.inko @@ -18,10 +18,10 @@ fn pub tests(t: mut Tests) { hasher.write('123456789012345678901234567890123456789'.to_byte_array) hasher.write('123456789012345678901234567890123456789'.to_byte_array) - t.equal(hasher.finalize.to_string, 'f3187648aff45ddbf1f2c9ebf1fd6705c10c2566') + t.equal(hasher.finish.to_string, 'f3187648aff45ddbf1f2c9ebf1fd6705c10c2566') } - t.test('Sha1.finalize') fn (t) { + t.test('Sha1.finish') fn (t) { let cases = [ ('', 'da39a3ee5e6b4b0d3255bfef95601890afd80709'), ('a', '86f7e437faa5a7fce15d1ddcb9eaeaea377667b8'), @@ -57,7 +57,7 @@ fn pub tests(t: mut Tests) { let hasher = Sha1.new hasher.write(pair.0.to_byte_array) - t.equal(hasher.finalize.to_string, pair.1) + t.equal(hasher.finish.to_string, pair.1) } } } diff --git a/libstd/test/std/crypto/test_sha2.inko b/std/test/std/crypto/test_sha2.inko similarity index 94% rename from libstd/test/std/crypto/test_sha2.inko rename to std/test/std/crypto/test_sha2.inko index 7f21dd068..ed6dd7b97 100644 --- a/libstd/test/std/crypto/test_sha2.inko +++ b/std/test/std/crypto/test_sha2.inko @@ -18,10 +18,10 @@ fn pub tests(t: mut Tests) { hasher.write('123456789012345678901234567890123456789'.to_byte_array) hasher.write('123456789012345678901234567890123456789'.to_byte_array) - t.equal(hasher.finalize.to_string, '1002070a9f6d34be894acf21e62d01b1b25938a81c86546eb2642f8b9731caf7') + t.equal(hasher.finish.to_string, '1002070a9f6d34be894acf21e62d01b1b25938a81c86546eb2642f8b9731caf7') } - t.test('Sha256.finalize') fn (t) { + t.test('Sha256.finish') fn (t) { let cases = [ ('', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'), ('a', 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb'), @@ -57,7 +57,7 @@ fn pub tests(t: mut Tests) { let hasher = Sha256.new hasher.write(pair.0.to_byte_array) - t.equal(hasher.finalize.to_string, pair.1) + t.equal(hasher.finish.to_string, pair.1) } } @@ -81,12 +81,12 @@ fn pub tests(t: mut Tests) { hasher.write('123456789012345678901234567890123456789'.to_byte_array) t.equal( - hasher.finalize.to_string, + hasher.finish.to_string, '5e2534f21d213edc33c937e4cdcd996b7d7486c6689b4f054afd811b25b727497d89d01506186daefee7f4b0cff9a977c319f8b594fe1c6551da1826e10d94cc' ) } - t.test('Sha512.finalize') fn (t) { + t.test('Sha512.finish') fn (t) { let cases = [ ( '', @@ -137,7 +137,7 @@ fn pub tests(t: mut Tests) { let hasher = Sha512.new hasher.write(pair.0.to_byte_array) - t.equal(hasher.finalize.to_string, pair.1) + t.equal(hasher.finish.to_string, pair.1) } } } diff --git a/libstd/test/std/endian/test_big.inko b/std/test/std/endian/test_big.inko similarity index 80% rename from libstd/test/std/endian/test_big.inko rename to std/test/std/endian/test_big.inko index af26997e7..da05ef7f1 100644 --- a/libstd/test/std/endian/test_big.inko +++ b/std/test/std/endian/test_big.inko @@ -1,4 +1,3 @@ -import helpers::Script import std::endian::big import std::int::(MIN, MAX) import std::test::Tests @@ -21,13 +20,10 @@ fn pub tests(t: mut Tests) { big.write_u32(123456789, into: bytes, at: 0) t.equal(big.read_u32(from: bytes, at: 0), 123456789) - t.true( - Script - .new(id: t.id, code: 'big.read_u32(from: ByteArray.new, at: 0)') - .import('std::endian::big') - .run - .contains?('out of bounds') - ) + } + + t.panic('big.read_u32 with not enough bytes') fn { + big.read_u32(from: ByteArray.new, at: 0) } t.test('big.write_i64') fn (t) { @@ -56,12 +52,9 @@ fn pub tests(t: mut Tests) { t.equal(big.read_i64(from: b1, at: 0), 123456789) t.equal(big.read_i64(from: b2, at: 0), MAX) t.equal(big.read_i64(from: b3, at: 0), MIN) - t.true( - Script - .new(id: t.id, code: 'big.read_i64(from: ByteArray.new, at: 0)') - .import('std::endian::big') - .run - .contains?('out of bounds') - ) + } + + t.panic('big.read_i64 with not enough bytes') fn { + big.read_i64(from: ByteArray.new, at: 0) } } diff --git a/libstd/test/std/endian/test_little.inko b/std/test/std/endian/test_little.inko similarity index 80% rename from libstd/test/std/endian/test_little.inko rename to std/test/std/endian/test_little.inko index 1d1ff4579..b18118d31 100644 --- a/libstd/test/std/endian/test_little.inko +++ b/std/test/std/endian/test_little.inko @@ -1,4 +1,3 @@ -import helpers::Script import std::endian::little import std::int::(MIN, MAX) import std::test::Tests @@ -21,13 +20,10 @@ fn pub tests(t: mut Tests) { little.write_u32(123456789, into: bytes, at: 0) t.equal(little.read_u32(from: bytes, at: 0), 123456789) - t.true( - Script - .new(id: t.id, code: 'little.read_u32(from: ByteArray.new, at: 0)') - .import('std::endian::little') - .run - .contains?('out of bounds') - ) + } + + t.panic('little.read_u32 with not enough bytes') fn { + little.read_u32(from: ByteArray.new, at: 0) } t.test('little.write_i64') fn (t) { @@ -56,12 +52,9 @@ fn pub tests(t: mut Tests) { t.equal(little.read_i64(from: b1, at: 0), 123456789) t.equal(little.read_i64(from: b2, at: 0), MAX) t.equal(little.read_i64(from: b3, at: 0), MIN) - t.true( - Script - .new(id: t.id, code: 'little.read_i64(from: ByteArray.new, at: 0)') - .import('std::endian::little') - .run - .contains?('out of bounds') - ) + } + + t.panic('little.read_i64 with not enough bytes') fn { + little.read_i64(from: ByteArray.new, at: 0) } } diff --git a/libstd/test/std/fs/test_dir.inko b/std/test/std/fs/test_dir.inko similarity index 61% rename from libstd/test/std/fs/test_dir.inko rename to std/test/std/fs/test_dir.inko index efcb5b81b..684b5e75b 100644 --- a/libstd/test/std/fs/test_dir.inko +++ b/std/test/std/fs/test_dir.inko @@ -6,31 +6,31 @@ fn pub tests(t: mut Tests) { t.test('dir.create') fn (t) { let path = env.temporary_directory.join("inko-test-dir-{t.id}") - t.no_throw fn { try dir.create(path) } + t.true(dir.create(path).ok?) t.true(path.directory?) - t.throw fn { try dir.create(path) } + t.true(dir.create(path).error?) - try! dir.remove(path) + dir.remove(path).unwrap } t.test('dir.create_all') fn (t) { let root = env.temporary_directory.join("inko-test-dir-{t.id}") let path = root.join('foo').join('bar') - t.no_throw fn { try dir.create_all(path) } + t.true(dir.create_all(path).ok?) t.true(path.directory?) - t.no_throw fn { try dir.create_all(path) } + t.true(dir.create_all(path).ok?) - try! dir.remove_all(root) + dir.remove_all(root).unwrap } t.test('dir.remove') fn (t) { let path = env.temporary_directory.join("inko-test-dir-{t.id}") - try! dir.create(path) + dir.create(path).unwrap - t.no_throw fn { try dir.remove(path) } - t.throw fn { try dir.remove(path) } + t.true(dir.remove(path).ok?) + t.true(dir.remove(path).error?) t.false(path.directory?) } @@ -38,10 +38,10 @@ fn pub tests(t: mut Tests) { let root = env.temporary_directory.join("inko-test-dir-{t.id}") let path = root.join('foo').join('bar') - try! dir.create_all(path) + dir.create_all(path).unwrap - t.no_throw fn { try dir.remove_all(root) } - t.throw fn { try dir.remove_all(root) } + t.true(dir.remove_all(root).ok?) + t.true(dir.remove_all(root).error?) t.false(root.directory?) } @@ -50,15 +50,15 @@ fn pub tests(t: mut Tests) { let foo = root.join('foo') let bar = root.join('bar') - try! dir.create(root) - try! dir.create(foo) - try! dir.create(bar) + dir.create(root).unwrap + dir.create(foo).unwrap + dir.create(bar).unwrap - let paths = try! dir.list(root) + let paths = dir.list(root).unwrap t.true(paths.contains?(foo)) t.true(paths.contains?(bar)) - try! dir.remove_all(root) + dir.remove_all(root).unwrap } } diff --git a/libstd/test/std/fs/test_file.inko b/std/test/std/fs/test_file.inko similarity index 52% rename from libstd/test/std/fs/test_file.inko rename to std/test/std/fs/test_file.inko index c13f07212..664066b64 100644 --- a/libstd/test/std/fs/test_file.inko +++ b/std/test/std/fs/test_file.inko @@ -4,16 +4,16 @@ import std::fs::path::Path import std::test::Tests fn write(string: String, to: ref Path) { - let file = try! WriteOnlyFile.new(to.clone) + let file = WriteOnlyFile.new(to.clone).unwrap - try! file.write_string(string) + file.write_string(string).unwrap } fn read(from: ref Path) -> String { - let file = try! ReadOnlyFile.new(from.clone) + let file = ReadOnlyFile.new(from.clone).unwrap let bytes = ByteArray.new - try! file.read_all(bytes) + file.read_all(bytes).unwrap bytes.into_string } @@ -22,11 +22,11 @@ fn pub tests(t: mut Tests) { t.test('file.remove') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.throw fn { try file.remove(path) } + t.true(file.remove(path).error?) write('test', to: path) - t.no_throw fn { try file.remove(path) } + t.true(file.remove(path).ok?) t.false(path.exists?) } @@ -36,23 +36,23 @@ fn pub tests(t: mut Tests) { write('test', to: path1) - t.no_throw fn { try file.copy(from: path1, to: path2) } + t.true(file.copy(from: path1, to: path2).ok?) t.equal(read(path2), 'test') - try! file.remove(path1) - try! file.remove(path2) + file.remove(path1).unwrap + file.remove(path2).unwrap } t.test('ReadOnlyFile.new') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.throw fn { try ReadOnlyFile.new(path.clone) } + t.true(ReadOnlyFile.new(path.clone).error?) write('test', to: path) - t.no_throw fn { try ReadOnlyFile.new(path.clone) } + t.true(ReadOnlyFile.new(path.clone).ok?) - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadOnlyFile.read') fn (t) { @@ -60,14 +60,14 @@ fn pub tests(t: mut Tests) { write('test', to: path) - let handle = try! ReadOnlyFile.new(path.clone) + let handle = ReadOnlyFile.new(path.clone).unwrap let bytes = ByteArray.new - try! handle.read(into: bytes, size: 4) + handle.read(into: bytes, size: 4).unwrap t.equal(bytes.into_string, 'test') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadOnlyFile.seek') fn (t) { @@ -75,15 +75,15 @@ fn pub tests(t: mut Tests) { write('test', to: path) - let handle = try! ReadOnlyFile.new(path.clone) + let handle = ReadOnlyFile.new(path.clone).unwrap let bytes = ByteArray.new - try! handle.seek(1) - try! handle.read(into: bytes, size: 4) + handle.seek(1).unwrap + handle.read(into: bytes, size: 4).unwrap t.equal(bytes.into_string, 'est') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadOnlyFile.size') fn (t) { @@ -91,112 +91,112 @@ fn pub tests(t: mut Tests) { write('test', to: path) - let handle = try! ReadOnlyFile.new(path.clone) + let handle = ReadOnlyFile.new(path.clone).unwrap - t.true(try! { handle.size } >= 0) + t.true(handle.size.unwrap >= 0) - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.new') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.no_throw fn { try WriteOnlyFile.new(path.clone) } - t.no_throw fn { try WriteOnlyFile.new(path.clone) } + t.true(WriteOnlyFile.new(path.clone).ok?) + t.true(WriteOnlyFile.new(path.clone).ok?) - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.append') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.no_throw fn { try WriteOnlyFile.append(path.clone) } - t.no_throw fn { try WriteOnlyFile.append(path.clone) } + t.true(WriteOnlyFile.append(path.clone).ok?) + t.true(WriteOnlyFile.append(path.clone).ok?) - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.write_bytes') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") { - let handle = try! WriteOnlyFile.new(path.clone) + let handle = WriteOnlyFile.new(path.clone).unwrap - try! handle.write_bytes('test'.to_byte_array) + handle.write_bytes('test'.to_byte_array).unwrap } { - let handle = try! WriteOnlyFile.append(path.clone) + let handle = WriteOnlyFile.append(path.clone).unwrap - try! handle.write_bytes('ing'.to_byte_array) + handle.write_bytes('ing'.to_byte_array).unwrap } t.equal(read(path), 'testing') - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.write_string') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") { - let handle = try! WriteOnlyFile.new(path.clone) + let handle = WriteOnlyFile.new(path.clone).unwrap - try! handle.write_string('test') + handle.write_string('test').unwrap } { - let handle = try! WriteOnlyFile.append(path.clone) + let handle = WriteOnlyFile.append(path.clone).unwrap - try! handle.write_string('ing') + handle.write_string('ing').unwrap } t.equal(read(path), 'testing') - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.flush') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - let handle = try! WriteOnlyFile.new(path.clone) + let handle = WriteOnlyFile.new(path.clone).unwrap - try! handle.write_string('test') - try! handle.flush + handle.write_string('test').unwrap + handle.flush.unwrap t.equal(read(path), 'test') - try! file.remove(path) + file.remove(path).unwrap } t.test('WriteOnlyFile.seek') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - let handle = try! WriteOnlyFile.new(path.clone) + let handle = WriteOnlyFile.new(path.clone).unwrap - try! handle.write_string('test') - try! handle.seek(1) - try! handle.write_string('ing') + handle.write_string('test').unwrap + handle.seek(1).unwrap + handle.write_string('ing').unwrap t.equal(read(path), 'ting') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.new') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.no_throw fn { try ReadWriteFile.new(path.clone) } - t.no_throw fn { try ReadWriteFile.new(path.clone) } + t.true(ReadWriteFile.new(path.clone).ok?) + t.true(ReadWriteFile.new(path.clone).ok?) - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.append') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - t.no_throw fn { try ReadWriteFile.append(path.clone) } - t.no_throw fn { try ReadWriteFile.append(path.clone) } + t.true(ReadWriteFile.append(path.clone).ok?) + t.true(ReadWriteFile.append(path.clone).ok?) - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.read') fn (t) { @@ -204,79 +204,79 @@ fn pub tests(t: mut Tests) { write('test', to: path) - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap let bytes = ByteArray.new - try! handle.read(bytes, size: 4) + handle.read(bytes, size: 4).unwrap t.equal(bytes.to_string, 'test') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.write_bytes') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") { - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap - try! handle.write_bytes('test'.to_byte_array) + handle.write_bytes('test'.to_byte_array).unwrap } { - let handle = try! ReadWriteFile.append(path.clone) + let handle = ReadWriteFile.append(path.clone).unwrap - try! handle.write_bytes('ing'.to_byte_array) + handle.write_bytes('ing'.to_byte_array).unwrap } t.equal(read(path), 'testing') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.write_string') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") { - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap - try! handle.write_string('test') + handle.write_string('test').unwrap } { - let handle = try! ReadWriteFile.append(path.clone) + let handle = ReadWriteFile.append(path.clone).unwrap - try! handle.write_string('ing') + handle.write_string('ing').unwrap } t.equal(read(path), 'testing') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.flush') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap - try! handle.write_string('test') - try! handle.flush + handle.write_string('test').unwrap + handle.flush.unwrap t.equal(read(path), 'test') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.seek') fn (t) { let path = env.temporary_directory.join("inko-test-{t.id}") - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap - try! handle.write_string('test') - try! handle.seek(1) - try! handle.write_string('ing') + handle.write_string('test').unwrap + handle.seek(1).unwrap + handle.write_string('ing').unwrap t.equal(read(path), 'ting') - try! file.remove(path) + file.remove(path).unwrap } t.test('ReadWriteFile.size') fn (t) { @@ -284,10 +284,10 @@ fn pub tests(t: mut Tests) { write('test', to: path) - let handle = try! ReadWriteFile.new(path.clone) + let handle = ReadWriteFile.new(path.clone).unwrap - t.true(try! { handle.size } >= 0) + t.true(handle.size.unwrap >= 0) - try! file.remove(path) + file.remove(path).unwrap } } diff --git a/libstd/test/std/fs/test_path.inko b/std/test/std/fs/test_path.inko similarity index 63% rename from libstd/test/std/fs/test_path.inko rename to std/test/std/fs/test_path.inko index b90d298ac..cde175e72 100644 --- a/libstd/test/std/fs/test_path.inko +++ b/std/test/std/fs/test_path.inko @@ -1,34 +1,32 @@ import helpers::(fmt) import std::env +import std::fs::dir import std::fs::file::(self, WriteOnlyFile) import std::fs::path::(self, Path) import std::sys import std::test::Tests fn created_at? -> Bool { - try env.temporary_directory.created_at else return false - true + env.temporary_directory.created_at.ok? } fn modified_at? -> Bool { - try env.temporary_directory.modified_at else return false - true + env.temporary_directory.modified_at.ok? } fn accessed_at? -> Bool { - try env.temporary_directory.accessed_at else return false - true + env.temporary_directory.accessed_at.ok? } fn write(string: String, to: ref Path) { - let file = try! WriteOnlyFile.new(to.clone) + let file = WriteOnlyFile.new(to.clone).unwrap - try! file.write_string(string) + file.write_string(string).unwrap } fn pub tests(t: mut Tests) { - t.test('path.separator') fn (t) { - t.equal(path.separator, if sys.windows? { '\\' } else { '/' }) + t.test('path::SEPARATOR') fn (t) { + t.equal(path::SEPARATOR, '/') } t.test('Path.file?') fn (t) { @@ -38,7 +36,7 @@ fn pub tests(t: mut Tests) { write('test', to: path) t.true(path.file?) - try! file.remove(path) + file.remove(path).unwrap } t.test('Path.directory?') fn (t) { @@ -55,7 +53,7 @@ fn pub tests(t: mut Tests) { t.test('Path.created_at') fn (t) { let path = env.temporary_directory - t.no_throw fn { try path.created_at } + t.true(path.created_at.ok?) } } @@ -63,7 +61,7 @@ fn pub tests(t: mut Tests) { t.test('Path.modified_at') fn (t) { let path = env.temporary_directory - t.no_throw fn { try path.modified_at } + t.true(path.modified_at.ok?) } } @@ -71,32 +69,23 @@ fn pub tests(t: mut Tests) { t.test('Path.accessed_at') fn (t) { let path = env.temporary_directory - t.no_throw fn { try path.accessed_at } + t.true(path.accessed_at.ok?) } } t.test('Path.absolute?') fn (t) { - let abs = if sys.windows? { 'C:\\foo' } else { '/foo' } - - t.true(Path.new(abs).absolute?) + t.true(Path.new('/foo').absolute?) t.false(Path.new('foo').absolute?) } t.test('Path.relative?') fn (t) { - let abs = if sys.windows? { 'C:\\foo' } else { '/foo' } - t.true(Path.new('foo').relative?) - t.false(Path.new(abs).relative?) + t.false(Path.new('/foo').relative?) } t.test('Path.join') fn (t) { - if sys.windows? { - t.equal(Path.new('foo').join('bar'), Path.new('foo\\bar')) - t.equal(Path.new('foo').join('C:\\').join('bar'), Path.new('C:\\bar')) - } else { - t.equal(Path.new('foo').join('bar'), Path.new('foo/bar')) - t.equal(Path.new('foo').join('/').join('bar'), Path.new('/bar')) - } + t.equal(Path.new('foo').join('bar'), Path.new('foo/bar')) + t.equal(Path.new('foo').join('/').join('bar'), Path.new('/bar')) } t.test('Path.directory') fn (t) { @@ -117,7 +106,7 @@ fn pub tests(t: mut Tests) { } t.test('Path.size') fn (t) { - t.true(try! { env.temporary_directory.size } >= 0) + t.true(env.temporary_directory.size.unwrap >= 0) } t.test('Path.clone') fn (t) { @@ -135,4 +124,24 @@ fn pub tests(t: mut Tests) { t.test('Path.fmt') fn (t) { t.equal(fmt(Path.new('foo')), '"foo"') } + + t.test('Path.expand') fn (t) { + let temp = env.temporary_directory + let bar = temp.join('foo').join('bar') + + dir.create_all(bar).unwrap + + let expanded = bar.join('..').join('..').expand + + t.equal(expanded, Result.Ok(temp)) + dir.remove_all(bar) + } + + t.test('Path.tail') fn (t) { + t.equal(Path.new('foo').tail, 'foo') + t.equal(Path.new('foo').join('bar').tail, 'bar') + t.equal(Path.new('foo').join('bar.txt').tail, 'bar.txt') + t.equal(Path.new('').tail, '') + t.equal(Path.new('..').tail, '..') + } } diff --git a/std/test/std/hash/test_siphash.inko b/std/test/std/hash/test_siphash.inko new file mode 100644 index 000000000..eed1624f2 --- /dev/null +++ b/std/test/std/hash/test_siphash.inko @@ -0,0 +1,85 @@ +import std::test::Tests +import std::hash::siphash::SipHasher13 + +let KEY0 = 0x0706050403020100 +let KEY1 = 0x0F0E0D0C0B0A0908 +let EXPECTED = [ + -6076480319675972388, + -3894316307686372717, + -9021946994309475251, + -8360920918932981765, + -3497793463459282136, + -2379636529018225817, + -4247691247627591001, + -3201358290706427584, + 3931806377309739662, + 2712449776846519780, + 8781603583133944191, + 8124802424143463250, + 8692937602970737058, + 3490138030451851175, + 6943038873169124660, + -3233346569078990506, + -3724515260966597786, + -7137527490068883444, + -8071514186461223362, + -999906983011582692, + -4549709543058382784, + -5074804721957078972, + 9222944995288319790, + 5934071401310241059, + -836351549525156724, + 5029774389061290361, + 4196851155228758949, + -6260628767897666763, + -356931148583219187, + -8585764469560188428, + 6057079635794323043, + 2553784116283822524, + -9145267764139149811, + 5572283034219051455, + 8475513330862814526, + -3557382191000818799, + 3239505212207161862, + -5327727376832712617, + -5479626656086083935, + 7091858058578527122, + -4480459088322095823, + 7384990322848880964, + 7309430150919325733, + 2662331968129681830, + -4410681076612561045, + -4461294832492207722, + -6814337029234371037, + 5330879445531170045, + -6975719611647439802, + -4107934715622947149, + 9128596131873425807, + 2086032310832541594, + -4302572765519274222, + 3961735597980589776, + 3571270170472135910, + -6738863651867334272, + -5423247803822609035, + 2592059436507272467, + -8141558914245359096, + -2838163980098670264, + -651642346778770194, + -5853101887941232654, + -4345140119139347232, + -7126506181673372760, +] + +fn pub tests(t: mut Tests) { + t.test('SipHasher13.finish') fn (t) { + let buf = [] + + EXPECTED.iter.each_with_index fn (index, exp) { + let hasher = SipHasher13.new(KEY0, KEY1) + + buf.iter.each fn (v) { hasher.write(v) } + t.equal(hasher.finish, exp) + buf.push(index) + } + } +} diff --git a/libstd/test/std/net/test_ip.inko b/std/test/std/net/test_ip.inko similarity index 100% rename from libstd/test/std/net/test_ip.inko rename to std/test/std/net/test_ip.inko diff --git a/std/test/std/net/test_socket.inko b/std/test/std/net/test_socket.inko new file mode 100644 index 000000000..5c44de098 --- /dev/null +++ b/std/test/std/net/test_socket.inko @@ -0,0 +1,1415 @@ +import helpers::(fmt) +import std::drop::Drop +import std::env +import std::fs::file +import std::fs::path::Path +import std::net::ip::(IpAddress, Ipv4Address, Ipv6Address) +import std::net::socket::( + Socket, SocketAddress, TcpServer, TcpClient, Type, UdpSocket, UnixAddress, + UnixDatagram, UnixServer, UnixSocket, UnixClient +) +import std::string::ToString +import std::test::Tests +import std::time::(Duration, Instant) + +class SocketPath { + let @path: Path + + fn static pair(id: Int) -> (SocketPath, SocketPath) { + let p1 = env.temporary_directory.join("inko-test-{id}-1.sock") + let p2 = env.temporary_directory.join("inko-test-{id}-2.sock") + let _ = file.remove(p1) + let _ = file.remove(p2) + + (SocketPath { @path = p1 }, SocketPath { @path = p2 }) + } + + fn static new(id: Int) -> SocketPath { + let path = env.temporary_directory.join("inko-test-{id}.sock") + let _ = file.remove(path) + + SocketPath { @path = path } + } +} + +impl ToString for SocketPath { + fn pub to_string -> String { + @path.to_string + } +} + +impl Drop for SocketPath { + fn mut drop { + let _ = file.remove(@path) + } +} + +fn pub tests(t: mut Tests) { + t.test('SocketAddress.new') fn (t) { + let addr = SocketAddress.new(address: '127.0.0.1', port: 1234) + + t.equal(addr.ip, Option.Some(IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)))) + t.equal(addr.port, 1234) + } + + t.test('SocketAddress.==') fn (t) { + let addr1 = SocketAddress.new(address: '127.0.0.1', port: 1234) + let addr2 = SocketAddress.new(address: '127.0.0.1', port: 4567) + + t.equal(addr1, addr1) + t.not_equal(addr1, addr2) + } + + t.test('SocketAddress.fmt') fn (t) { + t.equal( + fmt(SocketAddress.new(address: '127.0.0.1', port: 1234)), + '127.0.0.1:1234' + ) + } + + t.test('Socket.ipv4') fn (t) { + t.true(Socket.ipv4(Type.STREAM).ok?) + t.true(Socket.ipv4(Type.DGRAM).ok?) + } + + t.test('Socket.ipv6') fn (t) { + t.true(Socket.ipv6(Type.STREAM).ok?) + t.true(Socket.ipv6(Type.DGRAM).ok?) + } + + t.test('Socket.bind') fn (t) { + { + let sock = Socket.ipv4(Type.STREAM).unwrap + + t.true(sock.bind(ip: 'foo', port: 0).error?) + } + + { + let sock = Socket.ipv4(Type.STREAM).unwrap + + t.true(sock.bind(ip: '0.0.0.0', port: 0).ok?) + } + } + + t.test('Socket.connect') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream1 = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + t.true(stream1.connect(ip: addr.ip.unwrap, port: addr.port.clone).ok?) + + let stream2 = Socket.ipv4(Type.STREAM).unwrap + + t.true( + # connect() may not immediately raise a "connection refused" error, due + # to connect() being non-blocking. In this case the "connection refused" + # error is raised on the next operation. + # + # Since a connect() _might_ still raise the error right away, we have to + # both connect and try to use the socket in some way. + stream2.connect(ip: '0.0.0.0', port: 40_000).error? + or stream2.write_string('ping').error? + ) + } + + t.test('Socket.listen') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + socket.bind(ip: '0.0.0.0', port: 0).unwrap + + t.true(socket.listen.ok?) + } + + t.test('Socket.accept') fn (t) { + let server = Socket.ipv4(Type.STREAM).unwrap + let client = Socket.ipv4(Type.STREAM).unwrap + + server.bind(ip: '127.0.0.1', port: 0).unwrap + server.listen.unwrap + + let addr = server.local_address.unwrap + + client.connect(addr.address.clone, addr.port.clone).unwrap + + let connection = server.accept.unwrap + + t.equal(connection.local_address.unwrap, server.local_address.unwrap) + } + + t.test('Socket.send_string_to') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + socket.bind(ip: '127.0.0.1', port: 0).unwrap + + let send_to = socket.local_address.unwrap + let buffer = ByteArray.new + + socket + .send_string_to('ping', ip: send_to.address, port: send_to.port.clone) + .unwrap + + t.equal(socket.read(into: buffer, size: 4).unwrap, 4) + t.equal(buffer.into_string, 'ping') + } + + t.test('Socket.send_bytes_to') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + socket.bind(ip: '127.0.0.1', port: 0).unwrap + + let send_to = socket.local_address.unwrap + let buffer = ByteArray.new + + socket + .send_bytes_to( + bytes: 'ping'.to_byte_array, + ip: send_to.address, + port: send_to.port.clone + ) + .unwrap + + t.equal(socket.read(into: buffer, size: 4).unwrap, 4) + t.equal(buffer.into_string, 'ping') + } + + t.test('Socket.receive_from') fn (t) { + let listener = Socket.ipv4(Type.DGRAM).unwrap + let client = Socket.ipv4(Type.DGRAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + client.bind(ip: '127.0.0.1', port: 0).unwrap + + let send_to = listener.local_address.unwrap + + client + .send_string_to('ping', ip: send_to.address, port: send_to.port.clone) + .unwrap + + let bytes = ByteArray.new + let sender = listener.receive_from(bytes: bytes, size: 4).unwrap + + t.equal(sender, client.local_address.unwrap) + t.equal(bytes.into_string, 'ping') + } + + t.test('Socket.local_address with an unbound socket') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + let address = socket.local_address.unwrap + + t.equal(address.address, '0.0.0.0') + t.equal(address.port, 0) + } + + t.test('Socket.local_address with a bound socket') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + socket.bind(ip: '127.0.0.1', port: 0).unwrap + + let local_address = socket.local_address.unwrap + + t.equal(local_address.address, '127.0.0.1') + t.true(local_address.port > 0) + } + + t.test('Socket.peer_address with a disconnected socket') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + t.true(socket.peer_address.error?) + } + + t.test('Socket.peer_address with a connected socket') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let client = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + client.connect(ip: addr.address, port: addr.port.clone).unwrap + + t.equal(client.peer_address.unwrap, addr) + } + + t.test('Socket.ttl') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true((socket.ttl = 10).ok?) + } + + t.test('Socket.only_ipv6?') fn (t) { + let socket = Socket.ipv6(Type.STREAM).unwrap + + t.true((socket.only_ipv6 = true).ok?) + } + + t.test('Socket.no_delay?') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true((socket.no_delay = true).ok?) + } + + t.test('Socket.broadcast?') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + t.true((socket.broadcast = true).ok?) + } + + t.test('Socket.linger') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + let duration = Duration.from_secs(5) + + t.true((socket.linger = duration).ok?) + } + + t.test('Socket.receive_buffer_size') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true((socket.receive_buffer_size = 256).ok?) + } + + t.test('Socket.send_buffer_size') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true((socket.send_buffer_size = 256).ok?) + } + + t.test('Socket.keepalive') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true((socket.keepalive = true).ok?) + } + + t.test('Socket.reuse_adress') fn (t) { + let socket = Socket.ipv6(Type.DGRAM).unwrap + + t.true((socket.reuse_address = true).ok?) + } + + t.test('Socket.reuse_port') fn (t) { + let socket = Socket.ipv6(Type.DGRAM).unwrap + + t.true((socket.reuse_port = true).ok?) + } + + t.test('Socket.shutdown_read') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + stream.shutdown_read.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes.length, 0) + } + + t.test('Socket.shutdown_write') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + stream.shutdown_write.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('Socket.shutdown shuts down the writing half') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + stream.shutdown.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('Socket.shutdown shuts down the reading half') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + stream.shutdown.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes.length, 0) + } + + t.test('Socket.try_clone') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.true(socket.try_clone.ok?) + } + + t.test('Socket.read') fn (t) { + let socket = Socket.ipv4(Type.DGRAM).unwrap + + socket.bind(ip: '127.0.0.1', port: 0).unwrap + + let addr = socket.local_address.unwrap + let bytes = ByteArray.new + + socket.send_string_to('ping', ip: addr.address, port: addr.port.clone).unwrap + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('Socket.write_bytes') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + + let written = stream.write_bytes('ping'.to_byte_array).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('Socket.write_string') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + let stream = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + stream.connect(ip: addr.address, port: addr.port.clone).unwrap + + let written = stream.write_string('ping').unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('Socket.flush') fn (t) { + let socket = Socket.ipv4(Type.STREAM).unwrap + + t.equal(socket.flush, Result.Ok(nil)) + } + + t.test('UdpSocket.new') fn (t) { + t.true( + UdpSocket + .new(ip: IpAddress.V4(Ipv4Address.new(0, 0, 0, 0)), port: 0) + .ok? + ) + + t.true( + UdpSocket + .new(ip: IpAddress.V6(Ipv6Address.new(0, 0, 0, 0, 0, 0, 0, 0)), port: 0) + .ok? + ) + } + + t.test('UdpSocket.connect') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket1 = UdpSocket.new(ip: ip.clone, port: 0).unwrap + let socket2 = UdpSocket.new(ip: ip, port: 0).unwrap + let addr = socket2.local_address.unwrap + + t.true(socket1.connect(ip: addr.address, port: addr.port.clone).ok?) + } + + t.test('UdpSocket.send_string_to') fn (t) { + let socket = UdpSocket + .new(ip: IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)), port: 0) + .unwrap + + let addr = socket.local_address.unwrap + + socket.send_string_to('ping', ip: addr.address, port: addr.port.clone).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UdpSocket.send_bytes_to') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket = UdpSocket.new(ip: ip, port: 0).unwrap + let addr = socket.local_address.unwrap + + socket + .send_bytes_to( + 'ping'.to_byte_array, + ip: addr.address, + port: addr.port.clone + ) + .unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UdpSocket.receive_from') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let listener = UdpSocket.new(ip: ip.clone, port: 0).unwrap + let client = UdpSocket.new(ip: ip, port: 0).unwrap + let addr = listener.local_address.unwrap + + client + .send_string_to('ping', ip: addr.address, port: addr.port.clone) + .unwrap + + let bytes = ByteArray.new + let sender = listener.receive_from(bytes: bytes, size: 4).unwrap + + t.equal(sender, client.local_address.unwrap) + t.equal(bytes.into_string, 'ping') + } + + t.test('UdpSocket.local_address') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket = UdpSocket.new(ip: ip, port: 0).unwrap + let local_address = socket.local_address.unwrap + + t.equal(local_address.address, '127.0.0.1') + t.true(local_address.port > 0) + } + + t.test('UdpSocket.try_clone') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket = UdpSocket.new(ip: ip, port: 0).unwrap + + t.true(socket.try_clone.ok?) + } + + t.test('UdpSocket.read') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket = UdpSocket.new(ip: ip, port: 0).unwrap + let addr = socket.local_address.unwrap + + socket.send_string_to('ping', ip: addr.address, port: addr.port.clone).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.to_string, 'ping') + } + + t.test('UdpSocket.write_bytes') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let server_socket = UdpSocket.new(ip: ip.clone, port: 0).unwrap + let client_socket = UdpSocket.new(ip: ip, port: 0).unwrap + let addr = server_socket.local_address.unwrap + + client_socket.connect(ip: addr.address, port: addr.port.clone).unwrap + client_socket.write_bytes('ping'.to_byte_array).unwrap + + let bytes = ByteArray.new + + t.equal(server_socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UdpSocket.flush') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let socket = UdpSocket.new(ip: ip, port: 0).unwrap + + t.equal(socket.flush, Result.Ok(nil)) + } + + t.test('TcpClient.new') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + t.true(TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).ok?) + } + + t.test('TcpClient.with_timeout') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + + t.true( + TcpClient + .with_timeout( + ip: addr.ip.unwrap, + port: addr.port.clone, + timeout_after: Duration.from_secs(2) + ) + .ok? + ) + + t.true( + # This address is unroutable and so the connect times out. + TcpClient + .with_timeout( + ip: IpAddress.v4(192, 168, 0, 0), + port: addr.port.clone, + timeout_after: Duration.from_micros(500) + ) + .error? + ) + } + + t.test('TcpClient.local_address') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let local_addr = stream.local_address.unwrap + + t.equal(local_addr.address, '127.0.0.1') + t.true(local_addr.port > 0) + } + + t.test('TcpClient.peer_address') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let peer_addr = stream.peer_address.unwrap + + t.equal(peer_addr.address, addr.address) + t.equal(peer_addr.port, addr.port) + } + + t.test('TcpClient.read') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let bytes = ByteArray.new + let client = listener.accept.unwrap + + client.write_string('ping').unwrap + stream.read(into: bytes, size: 4).unwrap + + t.equal(bytes.into_string, 'ping') + } + + t.test('TcpClient.write_bytes') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(stream.write_bytes('ping'.to_byte_array).unwrap, 4) + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('TcpClient.write_string') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(stream.write_string('ping').unwrap, 4) + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('TcpClient.flush') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + t.true(stream.flush.ok?) + } + + t.test('TcpClient.shutdown_read') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + stream.shutdown_read.unwrap + + let bytes = ByteArray.new + + stream.read(into: bytes, size: 4).unwrap + + t.equal(bytes, ByteArray.new) + } + + t.test('TcpClient.shutdown_write') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + stream.shutdown_write.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('TcpClient.shutdown shuts down the writing half') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + stream.shutdown.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('TcpClient.shutdown shuts down the reading half') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + stream.shutdown.unwrap + + let bytes = ByteArray.new + + let message = stream.read(into: bytes, size: 4).unwrap + + t.equal(bytes, ByteArray.new) + } + + t.test('TcpClient.try_clone') fn (t) { + let listener = Socket.ipv4(Type.STREAM).unwrap + + listener.bind(ip: '127.0.0.1', port: 0).unwrap + listener.listen.unwrap + + let addr = listener.local_address.unwrap + let client = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + + t.true(client.try_clone.ok?) + } + + t.test('TcpServer.new') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(0, 0, 0, 0)) + + t.true(TcpServer.new(ip: ip.clone, port: 0).ok?) + + let listener = TcpServer.new(ip: ip, port: 0).unwrap + let addr = listener.local_address.unwrap + + t.true(TcpServer.new(ip: addr.ip.unwrap, port: addr.port.clone).ok?) + } + + t.test('TcpServer.accept') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let listener = TcpServer.new(ip: ip, port: 0).unwrap + let addr = listener.local_address.unwrap + let stream = TcpClient.new(ip: addr.ip.unwrap, port: addr.port.clone).unwrap + let connection = listener.accept.unwrap + + t.equal(connection.local_address.unwrap, stream.peer_address.unwrap) + } + + t.test('TcpServer.local_address') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let listener = TcpServer.new(ip: ip, port: 0).unwrap + let addr = listener.local_address.unwrap + + t.equal(addr.address, '127.0.0.1') + t.true(addr.port > 0) + } + + t.test('TcpServer.try_clone') fn (t) { + let ip = IpAddress.V4(Ipv4Address.new(127, 0, 0, 1)) + let server = TcpServer.new(ip: ip, port: 0).unwrap + + t.true(server.try_clone.ok?) + } + + t.test('UnixAddress.to_path') fn (t) { + t.equal(UnixAddress.new('foo.sock').to_path, Option.Some('foo.sock'.to_path)) + t.true(UnixAddress.new("\0foo").to_path.none?) + t.true(UnixAddress.new('').to_path.none?) + } + + t.test('UnixAddress.to_string') fn (t) { + t.equal(UnixAddress.new('foo.sock').to_string, 'foo.sock') + t.equal(UnixAddress.new("\0foo").to_string, "\0foo") + t.equal(UnixAddress.new('').to_string, '') + } + + t.test('UnixAddress.abstract?') fn (t) { + t.false(UnixAddress.new('').abstract?) + t.false(UnixAddress.new('foo.sock').abstract?) + t.true(UnixAddress.new("\0foo").abstract?) + } + + t.test('UnixAddress.unnamed?') fn (t) { + t.false(UnixAddress.new('foo.sock').unnamed?) + t.false(UnixAddress.new("\0foo").unnamed?) + t.true(UnixAddress.new('').unnamed?) + } + + t.test('UnixAddress.fmt') fn (t) { + t.equal(fmt(UnixAddress.new('foo.sock')), 'foo.sock') + t.equal(fmt(UnixAddress.new("\0foo")), '@foo') + t.equal(fmt(UnixAddress.new('')), 'unnamed') + } + + t.test('UnixAddress.==') fn (t) { + t.equal(UnixAddress.new('a.sock'), UnixAddress.new('a.sock')) + t.not_equal(UnixAddress.new('a.sock'), UnixAddress.new('b.sock')) + } + + t.test('UnixAddress.to_string') fn (t) { + t.equal(UnixAddress.new('foo.sock').to_string, 'foo.sock') + t.equal(UnixAddress.new("\0foo").to_string, "\0foo") + t.equal(UnixAddress.new('').to_string, '') + } + + t.test('UnixSocket.new') fn (t) { + t.true(UnixSocket.new(Type.DGRAM).ok?) + t.true(UnixSocket.new(Type.STREAM).ok?) + } + + t.test('UnixSocket.bind') fn (t) { + let socket1 = UnixSocket.new(Type.STREAM).unwrap + let socket2 = UnixSocket.new(Type.STREAM).unwrap + let path = SocketPath.new(t.id) + + t.true(socket1.bind(path).ok?) + t.true(socket2.bind(path).error?) + + if env::OS == 'linux' { + let socket = UnixSocket.new(Type.STREAM).unwrap + + t.true(socket.bind("\0inko-test-{t.id}").ok?) + } + } + + t.test('UnixSocket.connect') fn (t) { + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + let path = SocketPath.new(t.id) + + t.true(stream.connect(path).error?) + + listener.bind(path).unwrap + listener.listen.unwrap + + t.true(stream.connect(path).ok?) + + if env::OS == 'linux' { + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + let addr = "\0inko-test-{t.id}" + + listener.bind(addr).unwrap + listener.listen.unwrap + + t.true(stream.connect(addr).ok?) + } + } + + t.test('UnixSocket.listen') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.STREAM).unwrap + + socket.bind(path).unwrap + + t.true(socket.listen.ok?) + } + + t.test('UnixSocket.accept') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + + let client = listener.accept.unwrap + + t.equal(client.peer_address.unwrap, stream.local_address.unwrap) + } + + t.test('UnixSocket.send_string_to') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.DGRAM).unwrap + + socket.bind(path).unwrap + socket.send_string_to('ping', path).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.send_bytes_to') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.DGRAM).unwrap + + socket.bind(path).unwrap + socket.send_bytes_to('ping'.to_byte_array, path).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.receive_from') fn (t) { + let pair = SocketPath.pair(t.id) + let listener = UnixSocket.new(Type.DGRAM).unwrap + let client = UnixSocket.new(Type.DGRAM).unwrap + + listener.bind(pair.0).unwrap + client.bind(pair.1).unwrap + client.send_string_to('ping', pair.0).unwrap + + let bytes = ByteArray.new + let sender = listener.receive_from(bytes: bytes, size: 4).unwrap + + t.equal(sender, client.local_address.unwrap) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.local_address with an unbound socket') fn (t) { + let socket = UnixSocket.new(Type.DGRAM).unwrap + let address = socket.local_address.unwrap + + t.equal(address, UnixAddress.new('')) + } + + t.test('UnixSocket.local_address with a bound socket') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.DGRAM).unwrap + + socket.bind(path).unwrap + + t.equal(socket.local_address.unwrap, UnixAddress.new(path.to_string)) + } + + t.test('UnixSocket.peer_address with a disconnected socket') fn (t) { + let socket = UnixSocket.new(Type.DGRAM).unwrap + + t.true(socket.peer_address.error?) + } + + t.test('UnixSocket.peer_address with a connected socket') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let client = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + client.connect(path).unwrap + + t.equal(client.peer_address.unwrap, listener.local_address.unwrap) + } + + t.test('UnixSocket.read') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.DGRAM).unwrap + + socket.bind(path).unwrap + socket.send_string_to('ping', path).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.write_bytes') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + + let written = stream.write_bytes('ping'.to_byte_array).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.write_string') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + + let written = stream.write_string('ping').unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixSocket.flush') fn (t) { + let socket = UnixSocket.new(Type.STREAM).unwrap + + t.equal(socket.flush, Result.Ok(nil)) + } + + t.test('UnixSocket.receive_buffer_size') fn (t) { + let socket = UnixSocket.new(Type.STREAM).unwrap + + t.true((socket.receive_buffer_size = 256).ok?) + } + + t.test('UnixSocket.send_buffer_size') fn (t) { + let socket = UnixSocket.new(Type.STREAM).unwrap + + t.true((socket.send_buffer_size = 256).ok?) + } + + t.test('UnixSocket.shutdown_read') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + stream.shutdown_read.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes, ByteArray.new) + } + + t.test('UnixSocket.shutdown_write') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + stream.shutdown_write.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('UnixSocket.shutdown shuts down the writing half') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + stream.shutdown.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('UnixSocket.shutdown shuts down the reading half') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + let stream = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + stream.connect(path).unwrap + stream.shutdown.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes, ByteArray.new) + } + + t.test('UnixSocket.try_clone') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixSocket.new(Type.STREAM).unwrap + + t.true(socket.try_clone.ok?) + } + + t.test('UnixDatagram.new') fn (t) { + let path = SocketPath.new(t.id) + + t.true(UnixDatagram.new(path).ok?) + t.true(UnixDatagram.new(path).error?) + } + + t.test('UnixDatagram.connect') fn (t) { + let pair = SocketPath.pair(t.id) + let socket1 = UnixDatagram.new(pair.0).unwrap + let socket2 = UnixDatagram.new(pair.1).unwrap + + t.true(socket2.connect(pair.0).ok?) + } + + t.test('UnixDatagram.send_to') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixDatagram.new(path).unwrap + + socket.send_string_to('ping', address: path).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixDatagram.receive_from') fn (t) { + let pair = SocketPath.pair(t.id) + let listener = UnixDatagram.new(pair.0).unwrap + let client = UnixDatagram.new(pair.1).unwrap + + client.send_string_to('ping', pair.0).unwrap + + let bytes = ByteArray.new + let sender = listener.receive_from(bytes: bytes, size: 4).unwrap + + t.equal(sender, client.local_address.unwrap) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixDatagram.local_address') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixDatagram.new(path).unwrap + + t.equal(socket.local_address.unwrap, UnixAddress.new(path.to_string)) + } + + t.test('UnixDatagram.try_clone') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixDatagram.new(path).unwrap + + t.true(socket.try_clone.ok?) + } + + t.test('UnixDatagram.read') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixDatagram.new(path).unwrap + + socket.send_string_to('ping', address: path).unwrap + + let bytes = ByteArray.new + + t.equal(socket.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixDatagram.write_bytes') fn (t) { + let pair = SocketPath.pair(t.id) + let server = UnixDatagram.new(pair.0).unwrap + let client = UnixDatagram.new(pair.1).unwrap + + client.connect(pair.0).unwrap + + let bytes = ByteArray.new + + t.equal(client.write_bytes('ping'.to_byte_array).unwrap, 4) + t.equal(server.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixDatagram.flush') fn (t) { + let path = SocketPath.new(t.id) + let socket = UnixDatagram.new(path).unwrap + + t.equal(socket.flush, Result.Ok(nil)) + } + + t.test('UnixClient.new') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + t.true(UnixClient.new(path).ok?) + } + + t.test('UnixClient.with_timeout') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + # Unlike IP sockets there's no unroutable address we can use, so at best we + # can assert the following doesn't time out. This means this test is mostly + # a smoke test, unlikely to actually fail unless our runtime is bugged. + t.true( + UnixClient + .with_timeout(address: path, timeout_after: Duration.from_secs(5)) + .ok? + ) + } + + t.test('UnixClient.local_address') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + t.equal(stream.local_address.unwrap, UnixAddress.new('')) + } + + t.test('UnixClient.peer_address') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + t.equal(stream.peer_address.unwrap, UnixAddress.new(path.to_string)) + } + + t.test('UnixClient.read') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + let connection = listener.accept.unwrap + + connection.write_string('ping').unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixClient.write_bytes') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(stream.write_bytes('ping'.to_byte_array).unwrap, 4) + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixClient.write_string') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + let connection = listener.accept.unwrap + let bytes = ByteArray.new + + t.equal(stream.write_string('ping').unwrap, 4) + t.equal(connection.read(into: bytes, size: 4).unwrap, 4) + t.equal(bytes.into_string, 'ping') + } + + t.test('UnixClient.flush') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + t.true(stream.flush.ok?) + } + + t.test('UnixClient.shutdown_read') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + stream.shutdown_read.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes, ByteArray.new) + } + + t.test('UnixClient.shutdown_write') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + stream.shutdown_write.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('UnixClient.shutdown shuts down the writing half') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + stream.shutdown.unwrap + + t.true(stream.write_string('ping').error?) + } + + t.test('UnixClient.shutdown shuts down the reading half') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let stream = UnixClient.new(path).unwrap + + stream.shutdown.unwrap + + let bytes = ByteArray.new + + t.equal(stream.read(into: bytes, size: 4).unwrap, 0) + t.equal(bytes, ByteArray.new) + } + + t.test('UnixClient.try_clone') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixSocket.new(Type.STREAM).unwrap + + listener.bind(path).unwrap + listener.listen.unwrap + + let client = UnixClient.new(path).unwrap + + t.true(client.try_clone.ok?) + } + + t.test('UnixServer.new') fn (t) { + let path = SocketPath.new(t.id) + + t.true(UnixServer.new(path).ok?) + } + + t.test('UnixServer.accept') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixServer.new(path).unwrap + let stream = UnixClient.new(path).unwrap + let connection = listener.accept.unwrap + + t.equal(connection.local_address.unwrap, stream.peer_address.unwrap) + } + + t.test('UnixServer.local_address') fn (t) { + let path = SocketPath.new(t.id) + let listener = UnixServer.new(path).unwrap + let addr = listener.local_address.unwrap + + t.equal(addr, UnixAddress.new(path.to_string)) + } + + t.test('UnixServer.try_clone') fn (t) { + let path = SocketPath.new(t.id) + let server = UnixServer.new(path).unwrap + + t.true(server.try_clone.ok?) + } +} diff --git a/libstd/test/std/test_array.inko b/std/test/std/test_array.inko similarity index 77% rename from libstd/test/std/test_array.inko rename to std/test/std/test_array.inko index 3376a06a3..8893eef3c 100644 --- a/libstd/test/std/test_array.inko +++ b/std/test/std/test_array.inko @@ -1,4 +1,4 @@ -import helpers::(fmt, hash, Script) +import helpers::(fmt, hash) import std::drop::(drop, Drop) import std::rand::Random import std::test::Tests @@ -6,16 +6,16 @@ import std::test::Tests class Counter { let @value: Int - fn static new -> Self { - Self { @value = 0 } + fn static new -> Counter { + Counter { @value = 0 } } } class TrackDrop { let @counter: mut Counter - fn static new(counter: mut Counter) -> Self { - Self { @counter = counter } + fn static new(counter: mut Counter) -> TrackDrop { + TrackDrop { @counter = counter } } } @@ -31,7 +31,7 @@ fn pub tests(t: mut Tests) { let ary2: Array[Int] = Array.with_capacity(2) t.equal(ary1.capacity, 0) - t.equal(ary2.capacity, 4) + t.equal(ary2.capacity, 2) } t.test('Array.filled') fn (t) { @@ -72,28 +72,26 @@ fn pub tests(t: mut Tests) { t.equal(vals.remove_at(1), 20) t.equal(vals, [10, 30]) - t.true( - Script - .new(id: t.id, code: '[10].remove_at(5)') - .run - .contains?('out of bounds') - ) } - t.test('Array.get') fn (t) { + t.panic('Array.remove_at with an invalid index') fn { + [10].remove_at(5) + } + + t.test('Array.opt') fn (t) { let vals = [10, 20, 30] - t.equal(vals.get(1), Option.Some(ref 20)) - t.equal(vals.get(5), Option.None) - t.equal(vals.get(-5), Option.None) + t.equal(vals.opt(1), Option.Some(ref 20)) + t.equal(vals.opt(5), Option.None) + t.equal(vals.opt(-5), Option.None) } - t.test('Array.get_mut') fn (t) { + t.test('Array.opt_mut') fn (t) { let vals = [10, 20, 30] - t.equal(vals.get(1), Option.Some(mut 20)) - t.equal(vals.get(5), Option.None) - t.equal(vals.get(-5), Option.None) + t.equal(vals.opt_mut(1), Option.Some(mut 20)) + t.equal(vals.opt_mut(5), Option.None) + t.equal(vals.opt_mut(-5), Option.None) } t.test('Array.swap') fn (t) { @@ -101,12 +99,10 @@ fn pub tests(t: mut Tests) { t.equal(vals.swap(index: 1, with: 40), 20) t.equal(vals, [10, 40, 30]) - t.true( - Script - .new(id: t.id, code: '[10].swap(index: 5, with: 42)') - .run - .contains?('out of bounds') - ) + } + + t.panic('Array.swap with an invalid index') fn { + [10].swap(index: 5, with: 42) } t.test('Array.iter') fn (t) { @@ -194,37 +190,38 @@ fn pub tests(t: mut Tests) { t.false([10, 20].contains?(30)) } - t.test('Array.index') fn (t) { - let vals = [10, 20, 30] + t.test('Array.get') fn (t) { + t.equal([10].get(0), 10) + } - t.equal((ref vals)[1], ref 20) - t.true( - Script.new(id: t.id, code: '(ref [10])[2]').run.contains?('out of bounds') - ) + t.panic('Array.get with an invalid index') fn { + [10].get(1) } - t.test('Array.index_mut') fn (t) { + t.test('Array.get_mut') fn (t) { let vals = [10, 20, 30] - t.equal((mut vals)[1], mut 20) - t.true( - Script.new(id: t.id, code: '(mut [10])[2]').run.contains?('out of bounds') - ) + t.equal([10].get_mut(0), 10) } - t.test('Array.set_index') fn (t) { + t.panic('Array.get_mut with an invalid index') fn { + [10].get_mut(1) + } + + t.test('Array.set') fn (t) { let count = Counter.new let drops = [TrackDrop.new(count)] let vals = [10, 20, 30] - vals[1] = 40 - drops[0] = TrackDrop.new(count) + vals.set(1, 40) + drops.set(0, TrackDrop.new(count)) t.equal(vals, [10, 40, 30]) t.equal(count.value, 1) - t.true( - Script.new(id: t.id, code: '[10][2] = 4').run.contains?('out of bounds') - ) + } + + t.panic('Array.set with an invalid index') fn { + [10].set(1, 20) } t.test('Array.clone') fn (t) { diff --git a/libstd/test/std/test_bool.inko b/std/test/std/test_bool.inko similarity index 100% rename from libstd/test/std/test_bool.inko rename to std/test/std/test_bool.inko diff --git a/libstd/test/std/test_byte_array.inko b/std/test/std/test_byte_array.inko similarity index 85% rename from libstd/test/std/test_byte_array.inko rename to std/test/std/test_byte_array.inko index b49d7f1f0..96f8d6ef7 100644 --- a/libstd/test/std/test_byte_array.inko +++ b/std/test/std/test_byte_array.inko @@ -1,4 +1,4 @@ -import helpers::(fmt, hash, Script) +import helpers::(fmt, hash) import std::iter::EOF import std::test::Tests @@ -10,8 +10,8 @@ fn pub tests(t: mut Tests) { t.test('ByteArray.from_array') fn (t) { let bytes = ByteArray.from_array([10, 20]) - t.equal(bytes[0], 10) - t.equal(bytes[1], 20) + t.equal(bytes.get(0), 10) + t.equal(bytes.get(1), 20) } t.test('ByteArray.filled') fn (t) { @@ -75,12 +75,12 @@ fn pub tests(t: mut Tests) { t.equal(bytes.slice(start: 0, length: 10), bytes) } - t.test('ByteArray.get') fn (t) { + t.test('ByteArray.opt') fn (t) { let bytes = ByteArray.from_array([105, 110]) - t.equal(bytes.get(0), Option.Some(105)) - t.equal(bytes.get(1), Option.Some(110)) - t.equal(bytes.get(2), Option.None) + t.equal(bytes.opt(0), Option.Some(105)) + t.equal(bytes.opt(1), Option.Some(110)) + t.equal(bytes.opt(2), Option.None) } t.test('ByteArray.length') fn (t) { @@ -104,19 +104,23 @@ fn pub tests(t: mut Tests) { t.equal(bytes.iter.to_array, [10, 20]) } - t.test('ByteArray.index') fn (t) { + t.test('ByteArray.get') fn (t) { let bytes = ByteArray.from_array([10, 20]) - t.equal(bytes[0], 10) - t.equal(bytes[1], 20) + t.equal(bytes.get(0), 10) + t.equal(bytes.get(1), 20) + } + + t.panic('ByteArray.get with an invalid index') fn { + ByteArray.new.get(0) } - t.test('ByteArray.set_index') fn (t) { + t.test('ByteArray.set') fn (t) { let bytes = ByteArray.from_array([10, 20]) - bytes[0] = 50 + bytes.set(0, 50) - t.equal(bytes[0], 50) + t.equal(bytes.get(0), 50) } t.test('ByteArray.to_byte_array') fn (t) { @@ -192,11 +196,11 @@ fn pub tests(t: mut Tests) { let iter = input.iter let buff = ByteArray.new - t.equal(iter.read(into: buff, size: 2), 2) + t.equal(iter.read(into: buff, size: 2), Result.Ok(2)) t.equal(buff.to_string, 'fo') - t.equal(iter.read(into: buff, size: 2), 1) + t.equal(iter.read(into: buff, size: 2), Result.Ok(1)) t.equal(buff.to_string, 'foo') - t.equal(iter.read(into: buff, size: 2), 0) + t.equal(iter.read(into: buff, size: 2), Result.Ok(0)) t.equal(buff.to_string, 'foo') } @@ -205,15 +209,15 @@ fn pub tests(t: mut Tests) { let iter = input.iter let buff = ByteArray.new - t.equal(iter.read(into: buff, size: 1), 1) + t.equal(iter.read(into: buff, size: 1), Result.Ok(1)) t.equal(buff.to_string, 'f') input.pop - t.equal(iter.read(into: buff, size: 2), 1) + t.equal(iter.read(into: buff, size: 2), Result.Ok(1)) t.equal(buff.to_string, 'fo') input.push(111) - t.equal(iter.read(into: buff, size: 2), 1) + t.equal(iter.read(into: buff, size: 2), Result.Ok(1)) t.equal(buff.to_string, 'foo') } @@ -222,8 +226,8 @@ fn pub tests(t: mut Tests) { let iter = input.iter let buff = ByteArray.new - t.equal(iter.read_all(buff), 3) - t.equal(iter.read_all(buff), 0) + t.equal(iter.read_all(buff), Result.Ok(3)) + t.equal(iter.read_all(buff), Result.Ok(0)) t.equal(buff.to_string, 'foo') } @@ -255,12 +259,9 @@ fn pub tests(t: mut Tests) { bytes.resize(length: 0, value: 0) t.equal(bytes, ByteArray.new) + } - t.true( - Script - .new(id: t.id, code: 'ByteArray.new.resize(length: -5, value: 0)') - .run - .contains?('greater than zero') - ) + t.panic('ByteArray.resize with an invalid length') fn { + ByteArray.new.resize(length: -5, value: 0) } } diff --git a/libstd/test/std/test_cmp.inko b/std/test/std/test_cmp.inko similarity index 93% rename from libstd/test/std/test_cmp.inko rename to std/test/std/test_cmp.inko index 0380e02d8..b445207b9 100644 --- a/libstd/test/std/test_cmp.inko +++ b/std/test/std/test_cmp.inko @@ -7,8 +7,8 @@ class enum Letter { case B } -impl Compare for Letter { - fn pub cmp(other: ref Self) -> Ordering { +impl Compare[Letter] for Letter { + fn pub cmp(other: ref Letter) -> Ordering { match self { case A -> match other { case A -> Ordering.Equal @@ -22,8 +22,8 @@ impl Compare for Letter { } } -impl Equal for Letter { - fn pub ==(other: ref Self) -> Bool { +impl Equal[Letter] for Letter { + fn pub ==(other: ref Letter) -> Bool { match self { case A -> match other { case A -> true diff --git a/libstd/test/std/test_debug.inko b/std/test/std/test_debug.inko similarity index 85% rename from libstd/test/std/test_debug.inko rename to std/test/std/test_debug.inko index b8e2a9980..f258e79f4 100644 --- a/libstd/test/std/test_debug.inko +++ b/std/test/std/test_debug.inko @@ -14,9 +14,9 @@ fn pub tests(t: mut Tests) { t.test('debug.stacktrace') fn (t) { let trace = stacktrace(skip: 0) - let last = trace[trace.length - 1] + let last = trace.get(trace.length - 1) - t.equal(last.name, 'stacktrace') + t.equal(last.name, 'std::debug.stacktrace') t.true(last.path.to_string.ends_with?('debug.inko')) t.true(last.line >= 1) } diff --git a/std/test/std/test_env.inko b/std/test/std/test_env.inko new file mode 100644 index 000000000..17ab4059b --- /dev/null +++ b/std/test/std/test_env.inko @@ -0,0 +1,93 @@ +import std::env +import std::stdio::STDOUT +import std::test::Tests + +fn pub tests(t: mut Tests) { + t.test('env::ARCH') fn (t) { + t.true(env::ARCH.size > 0) + } + + t.test('env::OS') fn (t) { + t.true(env::OS.size > 0) + } + + t.test('env::ABI') fn (t) { + t.true(env::ABI.size > 0) + } + + t.fork( + 'env.opt', + child: fn { STDOUT.new.write_string(env.opt('INKO_TEST').unwrap_or('?')) }, + test: fn (test, process) { + process.variable('INKO_TEST', 'foo') + test.equal(process.spawn.stdout, 'foo') + } + ) + + t.fork( + 'env.variables', + child: fn { + let out = STDOUT.new + let vars = env.variables + + out.print(vars.get('INKO_FOO')) + out.print(vars.get('INKO_BAR')) + }, + test: fn (test, process) { + process.variable('INKO_FOO', 'foo') + process.variable('INKO_BAR', 'bar') + test.equal(process.spawn.stdout, "foo\nbar\n") + } + ) + + t.test('env.home_directory') fn (t) { + # Home directories are optional, and even if they're set the actual path may + # not exist. As such there's not really anything we can test for, other than + # asserting the path isn't empty. + match env.home_directory { + case Some(path) -> t.true(path.to_string.size > 0) + case _ -> {} + } + } + + t.test('env.working_directory') fn (t) { + let path = env.working_directory.unwrap + + t.true(path.directory?) + } + + t.fork( + 'env.working_directory=', + child: fn { + let out = STDOUT.new + + env.working_directory = env.temporary_directory + out.write_string(env.working_directory.unwrap.to_string) + }, + test: fn (test, process) { + test.equal(process.spawn.stdout, env.temporary_directory.to_string) + } + ) + + t.fork( + 'env.arguments', + child: fn { + let out = STDOUT.new + let args = env.arguments + + out.print(args.get(0)) + out.print(args.get(1)) + }, + test: fn (test, process) { + process.argument('foo') + process.argument('bar') + test.equal(process.spawn.stdout, "foo\nbar\n") + } + ) + + t.test('env.executable') fn (t) { + let path = env.executable.unwrap + + t.true(path.file?) + } +} diff --git a/libstd/test/std/test_float.inko b/std/test/std/test_float.inko similarity index 97% rename from libstd/test/std/test_float.inko rename to std/test/std/test_float.inko index 8537c7d25..bbe8f2826 100644 --- a/libstd/test/std/test_float.inko +++ b/std/test/std/test_float.inko @@ -1,7 +1,6 @@ import helpers::(fmt, hash) import std::cmp::Ordering import std::hash::Hasher -import std::map::DefaultHasher import std::test::Tests fn pub tests(t: mut Tests) { @@ -110,6 +109,9 @@ fn pub tests(t: mut Tests) { t.equal(-0.0.to_int, 0) t.equal(10.5.to_int, 10) t.equal(-10.5.to_int, -10) + t.equal(Float.not_a_number.to_int, 0) + t.equal(Float.infinity.to_int, 9_223_372_036_854_775_807) + t.equal(Float.negative_infinity.to_int, -9_223_372_036_854_775_808) } t.test('Float.to_float') fn (t) { diff --git a/libstd/test/std/test_fmt.inko b/std/test/std/test_fmt.inko similarity index 100% rename from libstd/test/std/test_fmt.inko rename to std/test/std/test_fmt.inko diff --git a/libstd/test/std/test_int.inko b/std/test/std/test_int.inko similarity index 100% rename from libstd/test/std/test_int.inko rename to std/test/std/test_int.inko diff --git a/libstd/test/std/test_io.inko b/std/test/std/test_io.inko similarity index 81% rename from libstd/test/std/test_io.inko rename to std/test/std/test_io.inko index 1853b9b63..8fcedae0d 100644 --- a/libstd/test/std/test_io.inko +++ b/std/test/std/test_io.inko @@ -6,48 +6,50 @@ class Reader { let @index: Int let @bytes: ByteArray - fn static new -> Self { - Self { @index = 0, @bytes = ByteArray.from_array([1, 2, 3]) } + fn static new -> Reader { + Reader { @index = 0, @bytes = ByteArray.from_array([1, 2, 3]) } } } impl Read for Reader { - fn pub mut read(into: mut ByteArray, size: Int) -> Int { + fn pub mut read(into: mut ByteArray, size: Int) -> Result[Int, Never] { let start = @index let mut max = @index + size if max > @bytes.length { max = @bytes.length } while @index < max { - into.push(@bytes[@index]) + into.push(@bytes.get(@index)) @index += 1 } - @index - start + Result.Ok(@index - start) } } class Writer { let @buffer: ByteArray - fn static new -> Self { - Self { @buffer = ByteArray.new } + fn static new -> Writer { + Writer { @buffer = ByteArray.new } } } impl Write for Writer { - fn pub mut write_bytes(bytes: ref ByteArray) -> Int { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Never] { bytes.iter.each fn (byte) { @buffer.push(byte) } - bytes.length + Result.Ok(bytes.length) } - fn pub mut write_string(string: String) -> Int { + fn pub mut write_string(string: String) -> Result[Int, Never] { string.to_byte_array.iter.each fn (byte) { @buffer.push(byte) } - string.size + Result.Ok(string.size) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } fn pub tests(t: mut Tests) { @@ -95,7 +97,7 @@ fn pub tests(t: mut Tests) { t.test('Read.read_all') fn (t) { let reader = Reader.new let bytes = ByteArray.new - let size = try! reader.read_all(bytes) + let size = reader.read_all(bytes).unwrap t.equal(size, 3) t.equal(bytes, ByteArray.from_array([1, 2, 3])) @@ -104,7 +106,7 @@ fn pub tests(t: mut Tests) { t.test('Write.print') fn (t) { let writer = Writer.new - try! writer.print('foo') + writer.print('foo').unwrap t.equal(writer.buffer, "foo\n".to_byte_array) } diff --git a/libstd/test/std/test_iter.inko b/std/test/std/test_iter.inko similarity index 84% rename from libstd/test/std/test_iter.inko rename to std/test/std/test_iter.inko index 65553499d..517c4460c 100644 --- a/libstd/test/std/test_iter.inko +++ b/std/test/std/test_iter.inko @@ -46,6 +46,30 @@ fn pub tests(t: mut Tests) { t.equal(iter.next, Option.Some(ref 20)) } + t.test('Iter.find_map') fn (t) { + let vals = [10, 20] + + t.equal( + vals.iter.find_map fn (val) { + if val == 20 { Option.Some('yes') } else { Option.None } + }, + Option.Some('yes') + ) + + t.equal( + vals.iter.find_map fn (val) { Option.None as Option[Int] }, + Option.None + ) + + let iter = vals.iter + + iter.find_map fn (val) { + if val == 10 { Option.Some(val) } else { Option.None } + } + + t.equal(iter.next, Option.Some(ref 20)) + } + t.test('Iter.any?') fn (t) { let vals = [10, 20] @@ -127,7 +151,7 @@ fn pub tests(t: mut Tests) { t.test('Enum.indexed') fn (t) { let vals = [10, 20, 30] - let iter = Enum.indexed(vals.length) fn (index) { vals[index] } + let iter = Enum.indexed(vals.length) fn (index) { vals.get(index) } t.equal(iter.to_array, [mut 10, mut 20, mut 30]) } diff --git a/std/test/std/test_json.inko b/std/test/std/test_json.inko new file mode 100644 index 000000000..ab2d45ec4 --- /dev/null +++ b/std/test/std/test_json.inko @@ -0,0 +1,404 @@ +import helpers::(fmt) +import std::json::(self, Error, Json, Parser) +import std::test::Tests + +fn parse(input: String) -> Result[Json, Error] { + Parser.new(input).parse +} + +fn parse_invalid(input: String) -> Option[String] { + Parser.new(input).parse.error.map fn (v) { v.to_string } +} + +fn pub tests(t: mut Tests) { + t.test('Error.fmt') fn (t) { + let err = Error { @message = 'foo', @line = 1, @offset = 5 } + + t.equal(fmt(err), 'foo, on line 1 at byte offset 5') + } + + t.test('Error.to_string') fn (t) { + let err = Error { @message = 'foo', @line = 1, @offset = 5 } + + t.equal(err.to_string, 'foo, on line 1 at byte offset 5') + } + + t.test('Json.fmt') fn (t) { + let map = Map.new + + map.set('a', Json.Int(10)) + + t.equal(fmt(Json.Int(42)), 'Int(42)') + t.equal(fmt(Json.Float(42.0)), 'Float(42.0)') + t.equal(fmt(Json.String('test')), 'String("test")') + t.equal(fmt(Json.Array([Json.Int(10)])), 'Array([Int(10)])') + t.equal(fmt(Json.Object(map)), 'Object({"a": Int(10)})') + t.equal(fmt(Json.Bool(true)), 'Bool(true)') + t.equal(fmt(Json.Bool(false)), 'Bool(false)') + t.equal(fmt(Json.Null), 'Null') + } + + t.test('Json.==') fn (t) { + let map1 = Map.new + let map2 = Map.new + let map3 = Map.new + + map1.set('a', Json.Int(10)) + map2.set('a', Json.Int(10)) + map3.set('a', Json.Int(10)) + + t.equal(Json.Int(10), Json.Int(10)) + t.not_equal(Json.Int(10), Json.Int(20)) + t.not_equal(Json.Int(10), Json.Float(20.0)) + + t.equal(Json.Float(10.0), Json.Float(10.0)) + t.not_equal(Json.Float(10.0), Json.Float(20.0)) + t.not_equal(Json.Float(10.0), Json.Int(10)) + + t.equal(Json.String('foo'), Json.String('foo')) + t.not_equal(Json.String('foo'), Json.String('bar')) + t.not_equal(Json.String('foo'), Json.Int(10)) + + t.equal(Json.Array([Json.Int(10)]), Json.Array([Json.Int(10)])) + t.not_equal(Json.Array([Json.Int(10)]), Json.Array([Json.Int(20)])) + t.not_equal(Json.Array([Json.Int(10)]), Json.Int(10)) + + t.equal(Json.Object(map1), Json.Object(map2)) + t.not_equal(Json.Object(map3), Json.Object(Map.new)) + t.not_equal(Json.Object(Map.new), Json.Int(10)) + + t.equal(Json.Bool(true), Json.Bool(true)) + t.not_equal(Json.Bool(true), Json.Bool(false)) + t.not_equal(Json.Bool(true), Json.Int(10)) + + t.equal(Json.Null, Json.Null) + t.not_equal(Json.Null, Json.Int(10)) + } + + t.test('Json.to_string') fn (t) { + let map = Map.new + + map.set('a', Json.Int(1)) + map.set('b', Json.Int(2)) + + t.equal(Json.Int(42).to_string, '42') + t.equal(Json.Float(1.2).to_string, '1.2') + t.equal(Json.String('foo').to_string, '"foo"') + t.equal(Json.String("a\nb").to_string, '"a\nb"') + t.equal(Json.String("a\rb").to_string, '"a\rb"') + t.equal(Json.String("a\tb").to_string, '"a\tb"') + t.equal(Json.String("a\u{C}b").to_string, '"a\fb"') + t.equal(Json.String("a\u{8}b").to_string, '"a\bb"') + t.equal(Json.String('a\\b').to_string, '"a\\\\b"') + t.equal(Json.Array([]).to_string, '[]') + t.equal(Json.Array([Json.Int(1), Json.Int(2)]).to_string, '[1, 2]') + t.equal(Json.Object(map).to_string, '{"a": 1, "b": 2}') + t.equal(Json.Object(Map.new).to_string, '{}') + t.equal(Json.Bool(true).to_string, 'true') + t.equal(Json.Bool(false).to_string, 'false') + t.equal(Json.Null.to_string, 'null') + } + + t.test('Json.to_pretty_string') fn (t) { + t.equal(Json.Int(42).to_pretty_string, '42') + t.equal(Json.Float(1.2).to_pretty_string, '1.2') + t.equal(Json.String('foo').to_pretty_string, '"foo"') + t.equal(Json.String("a\nb").to_pretty_string, '"a\nb"') + t.equal(Json.String("a\rb").to_pretty_string, '"a\rb"') + t.equal(Json.String("a\tb").to_pretty_string, '"a\tb"') + t.equal(Json.String("a\u{C}b").to_pretty_string, '"a\fb"') + t.equal(Json.String("a\u{8}b").to_pretty_string, '"a\bb"') + t.equal(Json.String('a\\b').to_pretty_string, '"a\\\\b"') + t.equal(Json.Bool(true).to_pretty_string, 'true') + t.equal(Json.Bool(false).to_pretty_string, 'false') + t.equal(Json.Null.to_pretty_string, 'null') + + t.equal(Json.Array([]).to_pretty_string, '[]') + t.equal( + Json.Array([Json.Int(1), Json.Int(2)]).to_pretty_string, + '[ + 1, + 2 +]' + ) + + t.equal( + Json.Array([Json.Array([Json.Int(1), Json.Int(2)])]).to_pretty_string, + '[ + [ + 1, + 2 + ] +]' + ) + + let map1 = Map.new + let map2 = Map.new + let map3 = Map.new + + map1.set('a', Json.Int(1)) + map1.set('b', Json.Int(2)) + map2.set('a', Json.Array([Json.Int(1), Json.Int(2)])) + map3.set('a', Json.Int(1)) + map3.set('b', Json.Object(map2)) + + t.equal(Json.Object(Map.new).to_pretty_string, '{}') + t.equal( + Json.Object(map1).to_pretty_string, + '{ + "a": 1, + "b": 2 +}' + ) + + t.equal( + Json.Object(map3).to_pretty_string, + '{ + "a": 1, + "b": { + "a": [ + 1, + 2 + ] + } +}' + ) + } + + t.test('Parsing integers') fn (t) { + t.equal(parse('0'), Result.Ok(Json.Int(0))) + t.equal(parse('42'), Result.Ok(Json.Int(42))) + t.equal(parse(' 42'), Result.Ok(Json.Int(42))) + t.equal(parse('42 '), Result.Ok(Json.Int(42))) + t.equal(parse("\t42"), Result.Ok(Json.Int(42))) + t.equal(parse("\r42"), Result.Ok(Json.Int(42))) + t.equal(parse('-42'), Result.Ok(Json.Int(-42))) + + t.true(parse('00').error?) + t.true(parse('10,').error?) + t.true(parse('-').error?) + t.true(parse('-01').error?) + t.true(parse('01').error?) + t.true(parse('1a').error?) + t.true(parse('-a').error?) + } + + t.test('Parsing floats') fn (t) { + t.equal(parse(' 1.2'), Result.Ok(Json.Float(1.2))) + t.equal(parse('1.2 '), Result.Ok(Json.Float(1.2))) + t.equal(parse('1.2'), Result.Ok(Json.Float(1.2))) + t.equal(parse('-1.2'), Result.Ok(Json.Float(-1.2))) + t.equal(parse('1.2e+123'), Result.Ok(Json.Float(1.2e+123))) + t.equal(parse('1.2e-123'), Result.Ok(Json.Float(1.2e-123))) + t.equal(parse('1.2E+123'), Result.Ok(Json.Float(1.2e+123))) + t.equal(parse('1.2E-123'), Result.Ok(Json.Float(1.2e-123))) + t.equal(parse('-1.2E-123'), Result.Ok(Json.Float(-1.2e-123))) + t.equal(parse('0.0'), Result.Ok(Json.Float(0.0))) + t.equal(parse('0E0'), Result.Ok(Json.Float(0.0))) + t.equal(parse('0e+1'), Result.Ok(Json.Float(0.0))) + t.equal(parse('1.2E1'), Result.Ok(Json.Float(1.2e1))) + t.equal(parse('1.2e1'), Result.Ok(Json.Float(1.2e1))) + t.equal( + parse('1.7976931348623157e+310'), + Result.Ok(Json.Float(Float.infinity)) + ) + t.equal( + parse('4.940656458412465441765687928682213723651e-330'), + Result.Ok(Json.Float(0.0)) + ) + t.equal( + parse('-0.000000000000000000000000000000000000000000000000000000000000000000000000000001'), + Result.Ok(Json.Float(-1.0E-78)) + ) + + # These numbers are too big for regular integers, so we promote them to + # floats. + t.equal( + parse('11111111111111111111111111111111111111111'), + Result.Ok(Json.Float(11111111111111111111111111111111111111111.0)) + ) + t.equal( + parse('10000000000000000999'), + Result.Ok(Json.Float(10000000000000000999.0)) + ) + + t.true(parse('00.0').error?) + t.true(parse('1.2e').error?) + t.true(parse('1.2e+').error?) + t.true(parse('1.2e-').error?) + t.true(parse('1.2E').error?) + t.true(parse('1.2E+').error?) + t.true(parse('1.2E-').error?) + t.true(parse('1.2E+a').error?) + t.true(parse('1.2E-a').error?) + t.true(parse('0E').error?) + t.true(parse('10.2,').error?) + + t.equal( + parse_invalid("\n1.2e"), + Option.Some( + 'One or more tokens are required, but we ran out of input, \ + on line 2 at byte offset 5' + ) + ) + } + + t.test('Parsing arrays') fn (t) { + t.equal(parse('[]'), Result.Ok(Json.Array([]))) + t.equal(parse('[10]'), Result.Ok(Json.Array([Json.Int(10)]))) + t.equal( + parse('[10, 20]'), + Result.Ok(Json.Array([Json.Int(10), Json.Int(20)])) + ) + + t.true(parse('[').error?) + t.true(parse(']').error?) + t.true(parse('[,10]').error?) + t.true(parse('[10,]').error?) + t.true(parse('[10').error?) + t.true(parse('[10,').error?) + t.true(parse('[10true]').error?) + t.true(parse('[],').error?) + + { + let parser = Parser.new('[[[[10]]]]') + + parser.max_depth = 2 + t.true(parser.parse.error?) + } + } + + t.test('Parsing booleans') fn (t) { + t.equal(parse('true'), Result.Ok(Json.Bool(true))) + t.equal(parse('false'), Result.Ok(Json.Bool(false))) + + t.true(parse('t').error?) + t.true(parse('tr').error?) + t.true(parse('tru').error?) + t.true(parse('f').error?) + t.true(parse('fa').error?) + t.true(parse('fal').error?) + t.true(parse('fals').error?) + } + + t.test('Parsing null') fn (t) { + t.equal(parse('null'), Result.Ok(Json.Null)) + + t.true(parse('n').error?) + t.true(parse('nu').error?) + t.true(parse('nul').error?) + } + + t.test('Parsing strings') fn (t) { + t.equal(parse('"foo"'), Result.Ok(Json.String('foo'))) + t.equal(parse('"foo bar"'), Result.Ok(Json.String('foo bar'))) + t.equal(parse('"foo\nbar"'), Result.Ok(Json.String("foo\nbar"))) + t.equal(parse('"foo\tbar"'), Result.Ok(Json.String("foo\tbar"))) + t.equal(parse('"foo\rbar"'), Result.Ok(Json.String("foo\rbar"))) + t.equal(parse('"foo\bbar"'), Result.Ok(Json.String("foo\u{0008}bar"))) + t.equal(parse('"foo\fbar"'), Result.Ok(Json.String("foo\u{000C}bar"))) + t.equal(parse('"foo\\"bar"'), Result.Ok(Json.String('foo"bar'))) + t.equal(parse('"foo\\/bar"'), Result.Ok(Json.String('foo/bar'))) + t.equal(parse('"foo\\\\bar"'), Result.Ok(Json.String("foo\\bar"))) + t.equal(parse('"foo\u005Cbar"'), Result.Ok(Json.String('foo\\bar'))) + t.equal( + parse('"foo\\u001Fbar"'), + Result.Ok(Json.String("foo\u{001F}bar")) + ) + t.equal(parse('"\uD834\uDD1E"'), Result.Ok(Json.String("\u{1D11E}"))) + t.equal( + parse('"\uE000\uE000"'), + Result.Ok(Json.String("\u{E000}\u{E000}")) + ) + + t.true(parse("\"\0\"").error?) + t.true(parse("\"\n\"").error?) + t.true(parse("\"\t\"").error?) + t.true(parse("\"\r\"").error?) + t.true(parse("\"\u{8}\"").error?) # \b + t.true(parse("\"\u{c}\"").error?) # \f + + t.true(parse('"\x42"').error?) + t.true(parse('"\u1"').error?) + t.true(parse('"\u12"').error?) + t.true(parse('"\u123"').error?) + t.true(parse('"\u{XXXX}"').error?) + t.true(parse('"\uD834\uE000"').error?) + t.true(parse('"\uD834\uZZZZ"').error?) + t.true(parse('"\uDFFF\uDFFF"').error?) + + { + let parser = Parser.new('"foo"') + + parser.max_string_size = 2 + t.true(parser.parse.error?) + } + + t.equal( + parse_invalid('"a'), + Option.Some( + 'One or more tokens are required, but we ran out of input, \ + on line 1 at byte offset 2' + ) + ) + } + + t.test('Parsing objects') fn (t) { + let map1 = Map.new + let map2 = Map.new + let map3 = Map.new + let map4 = Map.new + let map5 = Map.new + + map2.set('a', Json.Int(10)) + map3.set('a', Json.Int(20)) + map4.set('a', Json.Int(10)) + map4.set('b', Json.Int(20)) + map5.set('a', Json.Int(10)) + map5.set('b', Json.Int(20)) + + t.equal(parse('{}'), Result.Ok(Json.Object(map1))) + t.equal(parse('{ "a": 10 }'), Result.Ok(Json.Object(map2))) + t.equal(parse('{"a": 10, "a": 20}'), Result.Ok(Json.Object(map3))) + t.equal(parse('{"a": 10, "b": 20}'), Result.Ok(Json.Object(map4))) + t.equal( + parse('{ + "a": 10, + "b": 20 + }' + ), + Result.Ok(Json.Object(map5)) + ) + + t.true(parse('{').error?) + t.true(parse('}').error?) + t.true(parse('{{}}').error?) + t.true(parse('{"a"}').error?) + t.true(parse('{"a":}').error?) + t.true(parse('{"a":10,}').error?) + t.true(parse('{},').error?) + t.true(parse('{"a": true} "x"').error?) + + { + let parser = Parser.new('{"a": {"b": {"c": 10}}}') + + parser.max_depth = 2 + t.true(parser.parse.error?) + } + + t.equal( + parse_invalid('{"a"}'), + Option.Some("The character '}' is unexpected, on line 1 at byte offset 4") + ) + } + + t.test('Parsing Unicode BOMs') fn (t) { + t.true(parse("\u{FEFF}10").error?) + t.true(parse("\u{FFFE}10").error?) + t.true(parse("\u{EF}\u{BB}\u{BF}10").error?) + } + + t.test('json.parse') fn (t) { + t.equal(json.parse('[10]'), Result.Ok(Json.Array([Json.Int(10)]))) + } +} diff --git a/libstd/test/std/test_map.inko b/std/test/std/test_map.inko similarity index 59% rename from libstd/test/std/test_map.inko rename to std/test/std/test_map.inko index 5f9494425..20115e0fb 100644 --- a/libstd/test/std/test_map.inko +++ b/std/test/std/test_map.inko @@ -1,12 +1,12 @@ import helpers::(fmt, hash) -import std::map::(DefaultHasher, Hasher) +import std::hash::Hasher import std::test::Tests fn pub tests(t: mut Tests) { t.test('Entry.key') fn (t) { let map = Map.new - map['name'] = 'Alice' + map.set('name', 'Alice') let entry = map.iter.next.unwrap @@ -16,23 +16,13 @@ fn pub tests(t: mut Tests) { t.test('Entry.value') fn (t) { let map = Map.new - map['name'] = 'Alice' + map.set('name', 'Alice') let entry = map.iter.next.unwrap t.equal(entry.value, 'Alice') } - t.test('DefaultHasher.hash') fn (t) { - let hasher1: Hasher = DefaultHasher.new - let hasher2: Hasher = DefaultHasher.new - - 'foo'.hash(hasher1) - 'foo'.hash(hasher2) - - t.equal(hasher1.hash, hasher2.hash) - } - t.test('Map.with_capacity') fn (t) { let map1: Map[Int, Int] = Map.new let map2: Map[Int, Int] = Map.with_capacity(0) @@ -50,10 +40,10 @@ fn pub tests(t: mut Tests) { t.test('Map.remove') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' - map['foo'] = 'bar' - map['bar'] = 'baz' + map.set('name', 'Alice') + map.set('city', 'Bla') + map.set('foo', 'bar') + map.set('bar', 'baz') t.equal(map.remove('city'), Option.Some('Bla')) t.equal(map.remove('city'), Option.None) @@ -64,101 +54,101 @@ fn pub tests(t: mut Tests) { t.test('Map.iter') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' + map.set('name', 'Alice') + map.set('city', 'Bla') let entries = map.iter.to_array t.equal(entries.length, 2) - t.equal(entries[0].key, 'name') - t.equal(entries[0].value, 'Alice') - t.equal(entries[1].key, 'city') - t.equal(entries[1].value, 'Bla') + t.equal(entries.get(0).key, 'name') + t.equal(entries.get(0).value, 'Alice') + t.equal(entries.get(1).key, 'city') + t.equal(entries.get(1).value, 'Bla') } t.test('Map.iter_mut') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' + map.set('name', 'Alice') + map.set('city', 'Bla') let entries = map.iter_mut.to_array t.equal(entries.length, 2) - t.equal(entries[0].key, 'name') - t.equal(entries[0].value, 'Alice') - t.equal(entries[1].key, 'city') - t.equal(entries[1].value, 'Bla') + t.equal(entries.get(0).key, 'name') + t.equal(entries.get(0).value, 'Alice') + t.equal(entries.get(1).key, 'city') + t.equal(entries.get(1).value, 'Bla') } t.test('Map.into_iter') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' + map.set('name', 'Alice') + map.set('city', 'Bla') let entries = map.into_iter.to_array t.equal(entries.length, 2) - t.equal(entries[0].key, 'name') - t.equal(entries[0].value, 'Alice') - t.equal(entries[1].key, 'city') - t.equal(entries[1].value, 'Bla') + t.equal(entries.get(0).key, 'name') + t.equal(entries.get(0).value, 'Alice') + t.equal(entries.get(1).key, 'city') + t.equal(entries.get(1).value, 'Bla') } t.test('Map.keys') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' + map.set('name', 'Alice') + map.set('city', 'Bla') let keys = map.keys.to_array t.equal(keys.length, 2) - t.true(keys[0] == 'name' or keys[0] == 'city') + t.true(keys.get(0) == 'name' or keys.get(0) == 'city') } t.test('Map.values') fn (t) { let map = Map.new - map['name'] = 'Alice' - map['city'] = 'Bla' + map.set('name', 'Alice') + map.set('city', 'Bla') let values = map.values.to_array t.equal(values.length, 2) - t.true(values[0] == 'Alice' or values[0] == 'Bla') + t.true(values.get(0) == 'Alice' or values.get(0) == 'Bla') } - t.test('Map.get') fn (t) { + t.test('Map.opt') fn (t) { let map = Map.new - map['name'] = 'Alice' + map.set('name', 'Alice') - t.equal(map.get('name'), Option.Some(ref 'Alice')) - t.equal(map.get('city'), Option.None) + t.equal(map.opt('name'), Option.Some(ref 'Alice')) + t.equal(map.opt('city'), Option.None) } - t.test('Map.get_mut') fn (t) { + t.test('Map.opt_mut') fn (t) { let map = Map.new - map['name'] = 'Alice' + map.set('name', 'Alice') - t.equal(map.get_mut('name'), Option.Some(mut 'Alice')) - t.equal(map.get_mut('city'), Option.None) + t.equal(map.opt_mut('name'), Option.Some(mut 'Alice')) + t.equal(map.opt_mut('city'), Option.None) } t.test('Map.merge') fn (t) { let map1 = Map.new let map2 = Map.new - map1['name'] = 'Alice' - map2['city'] = 'Bla' + map1.set('name', 'Alice') + map2.set('city', 'Bla') map1.merge(map2) - t.equal(map1['name'], 'Alice') - t.equal(map1['city'], 'Bla') + t.equal(map1.get('name'), 'Alice') + t.equal(map1.get('city'), 'Bla') } t.test('Map.length') fn (t) { @@ -166,7 +156,7 @@ fn pub tests(t: mut Tests) { t.equal(map.length, 0) - map['name'] = 'Alice' + map.set('name', 'Alice') t.equal(map.length, 1) } @@ -179,15 +169,15 @@ fn pub tests(t: mut Tests) { let map5 = Map.new let map6 = Map.new - map1['name'] = 'Alice' - map2['name'] = 'Alice' - map4['foo'] = 'bar' + map1.set('name', 'Alice') + map2.set('name', 'Alice') + map4.set('foo', 'bar') - map5['foo'] = 'bar' - map5['bar'] = 'baz' + map5.set('foo', 'bar') + map5.set('bar', 'baz') - map6['bar'] = 'baz' - map6['foo'] = 'bar' + map6.set('bar', 'baz') + map6.set('foo', 'bar') t.equal(map1, map2) t.equal(map5, map6) @@ -199,17 +189,17 @@ fn pub tests(t: mut Tests) { let map = Map.new let alias = ref map - map['name'] = 'Alice' + map.set('name', 'Alice') - t.equal(alias['name'], 'Alice') + t.equal(alias.get('name'), 'Alice') } t.test('Map.index_mut') fn (t) { let map = Map.new - map['name'] = 'Alice' + map.set('name', 'Alice') - t.equal(map['name'], 'Alice') + t.equal(map.get('name'), 'Alice') } t.test('Map.hash') fn (t) { @@ -217,8 +207,8 @@ fn pub tests(t: mut Tests) { let map2 = Map.new let map3: Map[String, String] = Map.new - map1['name'] = 'Alice' - map2['name'] = 'Alice' + map1.set('name', 'Alice') + map2.set('name', 'Alice') t.equal(hash(map1), hash(map2)) t.not_equal(hash(map1), hash(map3)) @@ -228,7 +218,7 @@ fn pub tests(t: mut Tests) { let map1 = Map.new let map2: Map[String, String] = Map.new - map1['name'] = 'Alice' + map1.set('name', 'Alice') t.true(map1.contains?('name')) t.false(map2.contains?('name')) @@ -239,9 +229,9 @@ fn pub tests(t: mut Tests) { let map2 = Map.new let map3 = Map.new - map2['name'] = 'Alice' - map3['name'] = 'Alice' - map3['city'] = 'Bla' + map2.set('name', 'Alice') + map3.set('name', 'Alice') + map3.set('city', 'Bla') t.equal(fmt(map1), '{}') t.equal(fmt(map2), '{"name": "Alice"}') diff --git a/libstd/test/std/test_nil.inko b/std/test/std/test_nil.inko similarity index 100% rename from libstd/test/std/test_nil.inko rename to std/test/std/test_nil.inko diff --git a/libstd/test/std/test_option.inko b/std/test/std/test_option.inko similarity index 85% rename from libstd/test/std/test_option.inko rename to std/test/std/test_option.inko index f4649df0b..0db7792e1 100644 --- a/libstd/test/std/test_option.inko +++ b/std/test/std/test_option.inko @@ -1,4 +1,4 @@ -import helpers::(fmt, Script) +import helpers::(fmt) import std::test::Tests fn pub tests(t: mut Tests) { @@ -18,14 +18,24 @@ fn pub tests(t: mut Tests) { t.equal(b.as_mut, Option.None) } - t.test('Option.unwrap') fn (t) { + t.test('Option.unwrap with a Some') fn (t) { t.equal(Option.Some(42).unwrap, 42) - t.true( - Script - .new(id: t.id, code: '(Option.None as Option[Int]).unwrap') - .run - .contains?("A None can't be unwrapped") - ) + } + + t.test('Option.expect with a Some') fn (t) { + t.equal(Option.Some(42).expect('foo'), 42) + } + + t.panic('Option.unwrap with a None') fn { + let opt: Option[Int] = Option.None + + opt.unwrap + } + + t.panic('Option.expect with a None') fn { + let opt: Option[Int] = Option.None + + opt.expect('foo') } t.test('Option.unwrap_or') fn (t) { diff --git a/std/test/std/test_process.inko b/std/test/std/test_process.inko new file mode 100644 index 000000000..df3157a06 --- /dev/null +++ b/std/test/std/test_process.inko @@ -0,0 +1,12 @@ +import std::process +import std::test::Tests +import std::time::(Duration, Instant) + +fn pub tests(t: mut Tests) { + t.test('process.sleep') fn (t) { + let start = Instant.new + + process.sleep(Duration.from_millis(10)) + t.true(start.elapsed.to_millis >= 10) + } +} diff --git a/libstd/test/std/test_rand.inko b/std/test/std/test_rand.inko similarity index 100% rename from libstd/test/std/test_rand.inko rename to std/test/std/test_rand.inko diff --git a/libstd/test/std/test_range.inko b/std/test/std/test_range.inko similarity index 100% rename from libstd/test/std/test_range.inko rename to std/test/std/test_range.inko diff --git a/std/test/std/test_result.inko b/std/test/std/test_result.inko new file mode 100644 index 000000000..711558d51 --- /dev/null +++ b/std/test/std/test_result.inko @@ -0,0 +1,132 @@ +import helpers::(fmt) +import std::test::Tests + +fn pub tests(t: mut Tests) { + t.test('Result.ok?') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.true(foo.ok?) + t.false(bar.ok?) + } + + t.test('Result.error?') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.false(foo.error?) + t.true(bar.error?) + } + + t.test('Result.ok') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.ok, Option.Some(42)) + t.equal(bar.ok, Option.None) + } + + t.test('Result.error') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.error, Option.None) + t.equal(bar.error, Option.Some('oops!')) + } + + t.test('Result.unwrap with an Ok') fn (t) { + let res: Result[Int, String] = Result.Ok(42) + + t.equal(res.unwrap, 42) + } + + t.panic('Result.unwrap with an Error') fn { + let res: Result[Int, Int] = Result.Error(0) + + res.unwrap + } + + t.test('Result.expect with an Ok') fn (t) { + let res: Result[Int, String] = Result.Ok(42) + + t.equal(res.expect('foo'), 42) + } + + t.panic('Result.expect with an Error') fn { + let res: Result[Int, Int] = Result.Error(0) + + res.expect('foo') + } + + t.test('Result.unwrap_or') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.unwrap_or(0), 42) + t.equal(bar.unwrap_or(0), 0) + } + + t.test('Result.unwrap_or_else') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.unwrap_or_else fn { 0 }, 42) + t.equal(bar.unwrap_or_else fn { 0 }, 0) + } + + t.test('Result.map') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.map fn (v) { v.to_string }, Result.Ok('42')) + t.equal(bar.map fn (v) { v.to_string }, Result.Error('oops!')) + } + + t.test('Result.map_error') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.map_error fn (v) { v.to_upper }, Result.Ok(42)) + t.equal(bar.map_error fn (v) { v.to_upper }, Result.Error('OOPS!')) + } + + t.test('Result.then') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.then fn (v) { Result.Ok(v.to_string) }, Result.Ok('42')) + t.equal(bar.then fn (v) { Result.Ok(v.to_string) }, Result.Error('oops!')) + } + + t.test('Result.else') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.else fn (v) { Result.Error(v.to_upper) }, Result.Ok(42)) + t.equal(bar.else fn (v) { Result.Error(v.to_upper) }, Result.Error('OOPS!')) + } + + t.test('Result.clone') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo.clone, Result.Ok(42)) + t.equal(bar.clone, Result.Error('oops!')) + } + + t.test('Result.==') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(foo, Result.Ok(42)) + t.equal(bar, Result.Error('oops!')) + } + + t.test('Result.fmt') fn (t) { + let foo: Result[Int, String] = Result.Ok(42) + let bar: Result[Int, String] = Result.Error('oops!') + + t.equal(fmt(foo), 'Ok(42)') + t.equal(fmt(bar), 'Error("oops!")') + } +} diff --git a/libstd/test/std/test_set.inko b/std/test/std/test_set.inko similarity index 100% rename from libstd/test/std/test_set.inko rename to std/test/std/test_set.inko diff --git a/std/test/std/test_stdio.inko b/std/test/std/test_stdio.inko new file mode 100644 index 000000000..fa2ec9474 --- /dev/null +++ b/std/test/std/test_stdio.inko @@ -0,0 +1,87 @@ +import std::test::Tests +import std::stdio::(STDERR, STDIN, STDOUT) + +fn pub tests(t: mut Tests) { + t.fork( + 'STDIN.read', + child: fn { + let out = STDOUT.new + let in = STDIN.new + let bytes = ByteArray.new + let _ = in.read_all(bytes) + let _ = out.write_bytes(bytes) + }, + test: fn (test, process) { + process.stdin('hello') + test.equal(process.spawn.stdout, 'hello') + } + ) + + t.fork( + 'STDOUT.write_bytes', + child: fn { + let _ = STDOUT.new.write_bytes('hello'.to_byte_array) + }, + test: fn (test, process) { test.equal(process.spawn.stdout, 'hello') } + ) + + t.fork( + 'STDOUT.write_string', + child: fn { + let _ = STDOUT.new.write_string('hello') + }, + test: fn (test, process) { test.equal(process.spawn.stdout, 'hello') } + ) + + t.fork( + 'STDOUT.print', + child: fn { + let _ = STDOUT.new.print('hello') + }, + test: fn (test, process) { test.equal(process.spawn.stdout, "hello\n") } + ) + + t.fork( + 'STDOUT.flush', + child: fn { + let out = STDOUT.new + let _ = out.write_string('hello') + let _ = out.flush + }, + test: fn (test, process) { test.equal(process.spawn.stdout, 'hello') } + ) + + t.fork( + 'STDERR.write_bytes', + child: fn { + let _ = STDERR.new.write_bytes('hello'.to_byte_array) + }, + test: fn (test, process) { test.equal(process.spawn.stderr, 'hello') } + ) + + t.fork( + 'STDERR.write_string', + child: fn { + let _ = STDERR.new.write_string('hello') + }, + test: fn (test, process) { test.equal(process.spawn.stderr, 'hello') } + ) + + t.fork( + 'STDERR.print', + child: fn { + let _ = STDERR.new.print('hello') + }, + test: fn (test, process) { test.equal(process.spawn.stderr, "hello\n") } + ) + + t.fork( + 'STDERR.flush', + child: fn { + let out = STDERR.new + let _ = out.write_string('hello') + let _ = out.flush + }, + test: fn (test, process) { test.equal(process.spawn.stderr, 'hello') } + ) +} diff --git a/libstd/test/std/test_string.inko b/std/test/std/test_string.inko similarity index 97% rename from libstd/test/std/test_string.inko rename to std/test/std/test_string.inko index e0f192b6c..23464d40c 100644 --- a/libstd/test/std/test_string.inko +++ b/std/test/std/test_string.inko @@ -326,11 +326,11 @@ fn pub tests(t: mut Tests) { let iter = string.bytes let buff = ByteArray.new - t.equal(iter.read(into: buff, size: 2), 2) + t.equal(iter.read(into: buff, size: 2), Result.Ok(2)) t.equal(buff.to_string, 'fo') - t.equal(iter.read(into: buff, size: 2), 1) + t.equal(iter.read(into: buff, size: 2), Result.Ok(1)) t.equal(buff.to_string, 'foo') - t.equal(iter.read(into: buff, size: 2), 0) + t.equal(iter.read(into: buff, size: 2), Result.Ok(0)) t.equal(buff.to_string, 'foo') } @@ -339,8 +339,8 @@ fn pub tests(t: mut Tests) { let iter = string.bytes let buff = ByteArray.new - t.equal(iter.read_all(buff), 3) - t.equal(iter.read_all(buff), 0) + t.equal(iter.read_all(buff), Result.Ok(3)) + t.equal(iter.read_all(buff), Result.Ok(0)) t.equal(buff.to_string, 'foo') } } diff --git a/libstd/test/std/test_sys.inko b/std/test/std/test_sys.inko similarity index 71% rename from libstd/test/std/test_sys.inko rename to std/test/std/test_sys.inko index fd391c3af..0c5991ffb 100644 --- a/libstd/test/std/test_sys.inko +++ b/std/test/std/test_sys.inko @@ -1,28 +1,18 @@ -import helpers::Script +import helpers::(compiler_path) import std::env import std::sys::(self, Command, ExitStatus, Stream) import std::test::Tests fn pub tests(t: mut Tests) { - t.test('sys.unix?') fn (t) { - t.equal(sys.unix?, sys.linux? or sys.mac?) - t.not_equal(sys.unix?, sys.windows?) - } - t.test('sys.cpu_cores') fn (t) { t.true(sys.cpu_cores > 0) } - t.test('sys.exit') fn (t) { - let code = ' - sys.exit(4) - STDOUT.new.print("hello") - ' - - let output = Script.new(t.id, code).import('std::sys').run - - t.false(output.contains?('hello')) - } + t.fork( + 'sys.exit', + child: fn { sys.exit(4) }, + test: fn (test, process) { test.equal(process.spawn.status.to_int, 4) } + ) t.test('Stream.to_int') fn (t) { t.equal(Stream.Null.to_int, 0) @@ -63,42 +53,38 @@ fn pub tests(t: mut Tests) { t.equal(cmd.current_variables, Map.new) cmd.variable('TEST', 'foo') - t.equal(cmd.current_variables['TEST'], 'foo') + t.equal(cmd.current_variables.get('TEST'), 'foo') } t.test('Command.variables') fn (t) { let cmd = Command.new('ls') let vars = Map.new - vars['TEST'] = 'foo' + vars.set('TEST', 'foo') t.equal(cmd.current_variables, Map.new) cmd.variables(vars) - t.equal(cmd.current_variables['TEST'], 'foo') + t.equal(cmd.current_variables.get('TEST'), 'foo') } t.test('Command.spawn with a valid command') fn (t) { - let exe = try! env.executable - let cmd = Command.new(exe) + let cmd = Command.new(compiler_path) cmd.stdin(Stream.Null) cmd.stderr(Stream.Null) cmd.stdout(Stream.Piped) cmd.argument('--help') - let child = try! cmd.spawn - let status = try! child.wait + let child = cmd.spawn.unwrap + let status = child.wait.unwrap let bytes = ByteArray.new - try! child.stdout.read_all(bytes) - + child.stdout.read_all(bytes).unwrap t.true(bytes.into_string.contains?('Usage: inko')) } t.test('Command.spawn with an invalid command') fn (t) { - let cmd = Command.new('inko-test-invalid') - - t.throw fn { try cmd.spawn } + t.true(Command.new('inko-test-invalid').spawn.error?) } t.test('ExitStatus.to_int') fn (t) { diff --git a/libstd/test/std/test_test.inko b/std/test/std/test_test.inko similarity index 70% rename from libstd/test/std/test_test.inko rename to std/test/std/test_test.inko index ad3589bba..08db786ae 100644 --- a/libstd/test/std/test_test.inko +++ b/std/test/std/test_test.inko @@ -1,32 +1,36 @@ +import helpers::(fmt) +import std::env import std::fs::path::Path import std::io::Write import std::sys -import std::test::(Plain, Test, Tests) +import std::test::(Filter, Plain, Test, Tests) import std::time::Duration class Buffer { let @bytes: mut ByteArray - fn static new(bytes: mut ByteArray) -> Self { - Self { @bytes = bytes} + fn static new(bytes: mut ByteArray) -> Buffer { + Buffer { @bytes = bytes} } } impl Write for Buffer { - fn pub mut write_string(string: String) -> Int { + fn pub mut write_string(string: String) -> Result[Int, Never] { let bytes = string.to_byte_array let size = bytes.length @bytes.append(bytes) - size + Result.Ok(size) } - fn pub mut write_bytes(bytes: ref ByteArray) -> Int { + fn pub mut write_bytes(bytes: ref ByteArray) -> Result[Int, Never] { @bytes.append(bytes.clone) - bytes.length + Result.Ok(bytes.length) } - fn pub mut flush {} + fn pub mut flush -> Result[Nil, Never] { + Result.Ok(nil) + } } fn pub tests(t: mut Tests) { @@ -132,6 +136,32 @@ fn pub tests(t: mut Tests) { let tests = Tests.new t.equal(tests.concurrency, sys.cpu_cores) - t.true(tests.pattern.none?) + } + + t.test('Filter.from_string') fn (t) { + let exe = env.executable.unwrap + + t.equal(Filter.from_string(''), Filter.None) + t.equal(Filter.from_string('foo'), Filter.Pattern('foo')) + t.equal(Filter.from_string(exe.to_string), Filter.Location(exe.clone)) + } + + t.test('Filter.==') fn (t) { + t.equal(Filter.None, Filter.None) + t.equal(Filter.Pattern('foo'), Filter.Pattern('foo')) + t.equal(Filter.Location(Path.new('foo')), Filter.Location(Path.new('foo'))) + + t.not_equal(Filter.None, Filter.Pattern('foo')) + t.not_equal(Filter.Pattern('foo'), Filter.Pattern('bar')) + t.not_equal( + Filter.Location(Path.new('foo')), + Filter.Location(Path.new('bar')) + ) + } + + t.test('Filter.fmt') fn (t) { + t.equal(fmt(Filter.None), 'None') + t.equal(fmt(Filter.Pattern('foo')), 'Pattern("foo")') + t.equal(fmt(Filter.Location(Path.new('foo'))), 'Location("foo")') } } diff --git a/libstd/test/std/test_time.inko b/std/test/std/test_time.inko similarity index 91% rename from libstd/test/std/test_time.inko rename to std/test/std/test_time.inko index af6c32d93..5a79bb921 100644 --- a/libstd/test/std/test_time.inko +++ b/std/test/std/test_time.inko @@ -1,4 +1,4 @@ -import helpers::(Script, fmt) +import helpers::(fmt) import std::cmp::Ordering import std::process::(sleep) import std::test::Tests @@ -39,17 +39,10 @@ fn pub tests(t: mut Tests) { Duration.from_secs(-1) + Duration.from_secs(2), Duration.from_secs(1) ) + } - let code = 'Duration.from_nanos(9_223_372_036_854_775_807) + - Duration.from_nanos(1)' - - t.true( - Script - .new(id: t.id, code: code) - .import('std::time::Duration') - .run - .contains?('overflow') - ) + t.panic('Duration.+ with an argument that overflows') fn { + Duration.from_nanos(9_223_372_036_854_775_807) + Duration.from_nanos(1) } t.test('Duration.-') fn (t) { @@ -62,17 +55,10 @@ fn pub tests(t: mut Tests) { Duration.from_secs(-1) - Duration.from_secs(2), Duration.from_secs(-3) ) + } - let code = 'Duration.from_nanos(-9_223_372_036_854_775_808) - - Duration.from_nanos(1)' - - t.true( - Script - .new(id: t.id, code: code) - .import('std::time::Duration') - .run - .contains?('overflow') - ) + t.panic('Duration.- with an argument that overflows') fn { + Duration.from_nanos(-9_223_372_036_854_775_808) - Duration.from_nanos(1) } t.test('Duration.cmp') fn (t) { @@ -354,16 +340,10 @@ fn pub tests(t: mut Tests) { let t2 = t1 + Duration.from_secs(2) t.equal(t2.to_int, t1.to_int + 2_000_000_000) - t.true( - Script - .new( - id: t.id, - code: 'Instant.new + Duration.from_nanos(0 - (Instant.new.to_int * 2))' - ) - .import('std::time::(Duration, Instant)') - .run - .contains?('represent a negative time') - ) + } + + t.panic('Instant.+ with an argument that overflows') fn { + Instant.new + Duration.from_nanos(0 - (Instant.new.to_int * 2)) } t.test('Instant.-') fn (t) { @@ -372,16 +352,10 @@ fn pub tests(t: mut Tests) { let t2 = t1 - Duration.from_secs(1) t.equal(t2.to_int, base.to_int + 1_000_000_000) - t.true( - Script - .new( - id: t.id, - code: 'Instant.new - Duration.from_nanos(Instant.new.to_int * 2)' - ) - .import('std::time::(Duration, Instant)') - .run - .contains?('represent a negative time') - ) + } + + t.panic('Instant.- with an argument that overflows') fn { + Instant.new - Duration.from_nanos(Instant.new.to_int * 2) } t.test('Instant.cmp') fn (t) { diff --git a/libstd/test/std/test_tuple.inko b/std/test/std/test_tuple.inko similarity index 100% rename from libstd/test/std/test_tuple.inko rename to std/test/std/test_tuple.inko diff --git a/libstd/test/std/test_utf8.inko b/std/test/std/test_utf8.inko similarity index 100% rename from libstd/test/std/test_utf8.inko rename to std/test/std/test_utf8.inko diff --git a/types/Cargo.toml b/types/Cargo.toml index c79db9f81..c5c5cab58 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -7,6 +7,3 @@ license = "MPL-2.0" [lib] doctest = false - -[dependencies] -bytecode = { path = "../bytecode" } diff --git a/types/src/check.rs b/types/src/check.rs new file mode 100644 index 000000000..83a1b22c3 --- /dev/null +++ b/types/src/check.rs @@ -0,0 +1,1948 @@ +//! Type checking of types. +use crate::{ + Arguments, ClassInstance, Database, MethodId, TraitInstance, TypeArguments, + TypeBounds, TypeId, TypeParameterId, TypePlaceholderId, TypeRef, +}; +use std::collections::HashSet; + +#[derive(Copy, Clone)] +struct Rules { + /// When set to `true`, subtyping of types through traits is allowed. + subtyping: bool, + + /// When set to `true`, owned types can be type checked against reference + /// types. + relaxed_ownership: bool, + + /// When encountering an Infer() type, turn it into a rigid type. + infer_as_rigid: bool, +} + +impl Rules { + fn new() -> Rules { + Rules { + subtyping: true, + relaxed_ownership: false, + infer_as_rigid: false, + } + } + + fn no_subtyping(mut self) -> Rules { + self.subtyping = false; + self + } + + fn infer_as_rigid(mut self) -> Rules { + self.infer_as_rigid = true; + self + } + + fn dont_infer_as_rigid(mut self) -> Rules { + self.infer_as_rigid = false; + self + } + + fn relaxed(mut self) -> Rules { + self.relaxed_ownership = true; + self + } + + fn strict(mut self) -> Rules { + self.relaxed_ownership = false; + self + } +} + +/// The type-checking environment. +/// +/// This structure contains the type arguments to expose to types that are +/// checked. +#[derive(Clone)] +pub struct Environment { + /// The type arguments to expose to types on the left-hand side of the + /// check. + pub left: TypeArguments, + + /// The type arguments to expose to types on the right-hand side of the + /// check. + pub right: TypeArguments, +} + +impl Environment { + pub fn for_types( + db: &Database, + left: TypeRef, + right: TypeRef, + ) -> Environment { + Environment::new(left.type_arguments(db), right.type_arguments(db)) + } + + pub fn new( + left_arguments: TypeArguments, + right_arguments: TypeArguments, + ) -> Environment { + Environment { left: left_arguments, right: right_arguments } + } + + fn with_left_as_right(&self) -> Environment { + Environment { left: self.left.clone(), right: self.left.clone() } + } +} + +/// A type for checking if two types are compatible with each other. +pub struct TypeChecker<'a> { + db: &'a Database, + checked: HashSet<(TypeRef, TypeRef)>, +} + +impl<'a> TypeChecker<'a> { + pub fn check(db: &'a Database, left: TypeRef, right: TypeRef) -> bool { + let mut env = + Environment::new(left.type_arguments(db), right.type_arguments(db)); + + TypeChecker::new(db).run(left, right, &mut env) + } + + pub fn new(db: &'a Database) -> TypeChecker<'a> { + TypeChecker { db, checked: HashSet::new() } + } + + pub fn run( + mut self, + left: TypeRef, + right: TypeRef, + env: &mut Environment, + ) -> bool { + self.check_type_ref(left, right, env, Rules::new()) + } + + pub fn check_method( + mut self, + left: MethodId, + right: MethodId, + env: &mut Environment, + ) -> bool { + let rules = Rules::new(); + let lhs = left.get(self.db); + let rhs = right.get(self.db); + + if lhs.kind != rhs.kind { + return false; + } + + if lhs.visibility != rhs.visibility { + return false; + } + + if lhs.name != rhs.name { + return false; + } + + if lhs.type_parameters.len() != rhs.type_parameters.len() { + return false; + } + + let param_rules = rules.no_subtyping(); + + lhs.type_parameters + .values() + .iter() + .zip(rhs.type_parameters.values().iter()) + .all(|(&lhs, &rhs)| { + self.check_parameters(lhs, rhs, env, param_rules) + }) + && self.check_arguments( + &lhs.arguments, + &rhs.arguments, + env, + rules, + true, + ) + && self.check_type_ref(lhs.return_type, rhs.return_type, env, rules) + } + + pub fn check_bounds( + &mut self, + bounds: &TypeBounds, + env: &mut Environment, + ) -> bool { + let rules = Rules::new(); + + // When verifying bounds, the type on the right-hand side is the bound. + // This bound may indirectly refer to type parameters from the type on + // the left (e.g. through a required trait). As such we must expose + // whatever values are assigned to such type parameters to the + // right-hand side arguments. + // + // We do this by storing the assignment in the left-hand side arguments, + // then expose those arguments as the right-hand side arguments. This + // ensures that we de don't overwrite any assignments in the right-hand + // side, as that could mess up type-checking (as these arguments are + // provided by the user, instead of always being derived from the type + // on the left). + bounds.iter().all(|(¶m, &bound)| { + let val = env.left.get(param).unwrap(); + + env.left.assign(bound, val); + + let mut env = env.with_left_as_right(); + let rules = rules.relaxed(); + + if bound.is_mutable(self.db) && !val.is_mutable(self.db) { + return false; + } + + bound.requirements(self.db).into_iter().all(|r| { + self.check_type_ref_with_trait(val, r, &mut env, rules) + }) + }) + } + + fn check_type_ref( + &mut self, + left: TypeRef, + right: TypeRef, + env: &mut Environment, + rules: Rules, + ) -> bool { + if !self.checked.insert((left, right)) { + return true; + } + + // Relaxed ownership only applies to the check we perform below, not any + // sub checks. The approach we take here means we only need to "reset" + // this once for the sub checks. + let relaxed = rules.relaxed_ownership; + let rules = rules.strict(); + + // Resolve any assigned type parameters/placeholders to the types + // they're assigned to. + let left = self.resolve(left, &env.left, rules); + + // We only apply the "infer as rigid" rule to the type on the left, + // otherwise we may end up comparing e.g. a class instance to the rigid + // type parameter on the right, which would always fail. + // + // This is OK because in practise, Infer() only shows up on the left in + // a select few cases. + let rules = rules.dont_infer_as_rigid(); + let right = self.resolve(right, &env.right, rules); + + // If at this point we encounter a type placeholder, it means the + // placeholder is yet to be assigned a value. + match left { + TypeRef::Any => match right { + TypeRef::Any | TypeRef::Error => true, + TypeRef::Placeholder(id) => { + id.assign(self.db, left); + id.required(self.db) + .map_or(true, |p| p.requirements(self.db).is_empty()) + } + _ => false, + }, + // A `Never` can't be passed around because it, well, would never + // happen. We allow the comparison so code such as `try else panic` + // (where `panic` returns `Never`) is valid. + TypeRef::Never => match right { + TypeRef::Placeholder(id) => { + id.assign(self.db, left); + true + } + _ => true, + }, + // Type errors are compatible with all other types to prevent a + // cascade of type errors. + TypeRef::Error => match right { + TypeRef::Placeholder(id) => { + id.assign(self.db, left); + true + } + _ => true, + }, + // Rigid values are more restrictive when it comes to ownership, as + // at compile-time we can't always know the exact ownership (i.e. + // the parameter may be a ref at runtime). + TypeRef::Owned(left_id @ TypeId::RigidTypeParameter(lhs)) => { + match right { + TypeRef::Owned(TypeId::RigidTypeParameter(rhs)) => { + lhs == rhs + } + TypeRef::Infer(right_id) => { + self.check_rigid_with_type_id(lhs, right_id, env, rules) + } + TypeRef::Placeholder(id) => self + .check_type_id_with_placeholder( + left, left_id, id, env, rules, + ), + TypeRef::Any | TypeRef::Error => true, + _ => false, + } + } + TypeRef::Owned(left_id) => match right { + TypeRef::Owned(right_id) | TypeRef::Infer(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Ref(right_id) + | TypeRef::Mut(right_id) + | TypeRef::Uni(right_id) + if left.is_value_type(self.db) || relaxed => + { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Placeholder(id) => self + .check_type_id_with_placeholder( + left, left_id, id, env, rules, + ), + TypeRef::Any => { + if let TypeId::ClassInstance(ins) = left_id { + if ins.instance_of().kind(self.db).is_extern() { + return false; + } + } + + true + } + TypeRef::Error => true, + _ => false, + }, + TypeRef::Uni(left_id) => match right { + TypeRef::Owned(right_id) + | TypeRef::Infer(right_id) + | TypeRef::Uni(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Ref(right_id) | TypeRef::Mut(right_id) + if left.is_value_type(self.db) => + { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Placeholder(id) => self + .check_type_id_with_placeholder( + left, left_id, id, env, rules, + ), + TypeRef::Any => { + if let TypeId::ClassInstance(ins) = left_id { + if ins.instance_of().kind(self.db).is_extern() { + return false; + } + } + + true + } + TypeRef::Error => true, + _ => false, + }, + TypeRef::Infer(left_id) => match right { + // Mut and Owned are not allowed because we don't know the + // runtime ownership of our value. Ref is fine, because we can + // always turn an Owned/Ref/Mut/etc into a Ref. + TypeRef::Infer(right_id) | TypeRef::Ref(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Placeholder(id) => self + .check_type_id_with_placeholder( + left, left_id, id, env, rules, + ), + TypeRef::Any | TypeRef::Error => true, + _ => false, + }, + TypeRef::Ref(left_id) => match right { + TypeRef::Infer(TypeId::TypeParameter(pid)) + if pid.is_mutable(self.db) + && !left.is_value_type(self.db) => + { + false + } + TypeRef::Ref(right_id) | TypeRef::Infer(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Owned(right_id) + | TypeRef::Uni(right_id) + | TypeRef::Mut(right_id) + if left.is_value_type(self.db) => + { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Placeholder(id) => { + if let Some(req) = id.required(self.db) { + if req.is_mutable(self.db) + && !left.is_value_type(self.db) + { + return false; + } + } + + self.check_type_id_with_placeholder( + left, left_id, id, env, rules, + ) + } + TypeRef::Any | TypeRef::Error => true, + _ => false, + }, + TypeRef::Mut(left_id) => match right { + TypeRef::Ref(right_id) | TypeRef::Infer(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Mut(right_id) => self.check_type_id( + left_id, + right_id, + env, + rules.no_subtyping(), + ), + TypeRef::Owned(right_id) | TypeRef::Uni(right_id) + if left.is_value_type(self.db) => + { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Placeholder(id) => self + .check_type_id_with_placeholder( + left, left_id, id, env, rules, + ), + TypeRef::Any | TypeRef::Error => true, + _ => false, + }, + TypeRef::RefUni(left_id) => match right { + TypeRef::RefUni(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Error => true, + _ => false, + }, + TypeRef::MutUni(left_id) => match right { + TypeRef::MutUni(right_id) => { + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Error => true, + _ => false, + }, + TypeRef::Placeholder(left_id) => { + // If we reach this point it means the placeholder isn't + // assigned a value. + left_id.assign(self.db, right); + true + } + _ => false, + } + } + + fn check_type_id( + &mut self, + left_id: TypeId, + right_id: TypeId, + env: &mut Environment, + rules: Rules, + ) -> bool { + match left_id { + TypeId::Class(_) | TypeId::Trait(_) | TypeId::Module(_) => { + // Classes, traits and modules themselves aren't treated as + // types and thus can't be passed around, mostly because this + // just isn't useful. To further reinforce this, these types + // aren't compatible with anything. + false + } + TypeId::ClassInstance(lhs) => match right_id { + TypeId::ClassInstance(rhs) => { + if lhs.instance_of != rhs.instance_of { + return false; + } + + if !lhs.instance_of.is_generic(self.db) { + return true; + } + + let lhs_args = lhs.type_arguments(self.db); + let rhs_args = rhs.type_arguments(self.db); + + lhs.instance_of.type_parameters(self.db).into_iter().all( + |param| { + let lhs = lhs_args.get(param).unwrap(); + let rhs = rhs_args.get(param).unwrap(); + + self.check_type_ref(lhs, rhs, env, rules) + }, + ) + } + TypeId::TraitInstance(rhs) + if !lhs.instance_of().kind(self.db).is_extern() => + { + self.check_class_with_trait(lhs, rhs, env, rules) + } + TypeId::TypeParameter(rhs) + if !lhs.instance_of().kind(self.db).is_extern() => + { + rhs.requirements(self.db).into_iter().all(|req| { + self.check_class_with_trait(lhs, req, env, rules) + }) + } + _ => false, + }, + TypeId::TraitInstance(lhs) => match right_id { + TypeId::TraitInstance(rhs) => { + self.check_traits(lhs, rhs, env, rules) + } + TypeId::TypeParameter(rhs) => rhs + .requirements(self.db) + .into_iter() + .all(|req| self.check_traits(lhs, req, env, rules)), + _ => false, + }, + TypeId::TypeParameter(lhs) => match right_id { + TypeId::TraitInstance(rhs) => { + self.check_parameter_with_trait(lhs, rhs, env, rules) + } + TypeId::TypeParameter(rhs) => { + self.check_parameters(lhs, rhs, env, rules) + } + _ => false, + }, + TypeId::RigidTypeParameter(lhs) => { + self.check_rigid_with_type_id(lhs, right_id, env, rules) + } + TypeId::Closure(lhs) => match right_id { + TypeId::Closure(rhs) => { + let lhs_obj = lhs.get(self.db); + let rhs_obj = rhs.get(self.db); + + self.check_arguments( + &lhs_obj.arguments, + &rhs_obj.arguments, + env, + rules, + false, + ) && self.check_type_ref( + lhs_obj.return_type, + rhs_obj.return_type, + env, + rules, + ) + } + TypeId::TypeParameter(rhs) + if rhs.requirements(self.db).is_empty() => + { + // Closures can't implement traits, so they're only + // compatible with type parameters that don't have any + // requirements. + true + } + _ => false, + }, + } + } + + fn check_rigid_with_type_id( + &mut self, + left: TypeParameterId, + right: TypeId, + env: &mut Environment, + rules: Rules, + ) -> bool { + match right { + TypeId::RigidTypeParameter(rhs) => left == rhs, + TypeId::TraitInstance(rhs) => { + self.check_parameter_with_trait(left, rhs, env, rules) + } + TypeId::TypeParameter(rhs) => { + if left == rhs { + return true; + } + + rhs.requirements(self.db).into_iter().all(|req| { + self.check_parameter_with_trait(left, req, env, rules) + }) + } + _ => false, + } + } + + fn check_type_id_with_placeholder( + &mut self, + left: TypeRef, + left_id: TypeId, + placeholder: TypePlaceholderId, + env: &mut Environment, + rules: Rules, + ) -> bool { + // By assigning the placeholder first, recursive checks against the same + // placeholder don't keep recursing into this method, instead checking + // against the value on the left. + placeholder.assign(self.db, left); + + let req = if let Some(req) = placeholder.required(self.db) { + req + } else { + return true; + }; + + let reqs = req.requirements(self.db); + + if reqs.is_empty() { + return true; + } + + let res = match left_id { + TypeId::ClassInstance(lhs) => reqs + .into_iter() + .all(|req| self.check_class_with_trait(lhs, req, env, rules)), + TypeId::TraitInstance(lhs) => reqs + .into_iter() + .all(|req| self.check_traits(lhs, req, env, rules)), + TypeId::TypeParameter(lhs) | TypeId::RigidTypeParameter(lhs) => { + reqs.into_iter().all(|req| { + self.check_parameter_with_trait(lhs, req, env, rules) + }) + } + _ => false, + }; + + // If we keep the assignment in case of a type error, formatted type + // errors may be confusing as they would report the left-hand side as + // the expected value, rather than the underlying type parameter. + if !res { + placeholder.assign(self.db, TypeRef::Unknown); + } + + res + } + + pub fn class_implements_trait( + &mut self, + left: ClassInstance, + right: TraitInstance, + ) -> bool { + let mut env = Environment::new( + TypeArguments::for_class(self.db, left), + TypeArguments::for_trait(self.db, right), + ); + + self.check_class_with_trait(left, right, &mut env, Rules::new()) + } + + fn check_class_with_trait( + &mut self, + left: ClassInstance, + right: TraitInstance, + env: &mut Environment, + rules: Rules, + ) -> bool { + // `Array[Cat]` isn't compatible with `mut Array[Animal]`, as that could + // result in a `Dog` being added to the Array. + if !rules.subtyping { + return false; + } + + let imp = if let Some(found) = + left.instance_of.trait_implementation(self.db, right.instance_of) + { + found + } else { + return false; + }; + + // Trait implementations may be over owned values (e.g. + // `impl Equal[Foo] for Foo`). We allow such implementations to be + // compatible with those over references (e.g. `Equal[ref Foo]`), as + // otherwise the type system becomes to painful to work with. + // + // The reason this is sound is as follows: + // + // In the trait's methods, we can only refer to conrete types (in which + // case the type arguments of the implementation are irrelevant), or to + // the type parameters (if any) of the trait. These type parameters + // either have their ownership inferred, or use whatever ownership is + // explicitly provided. + // + // If any arguments need e.g. a reference then this is handled at the + // call site. If a reference needs to be produced (e.g. as the return + // value), it's up to the implementation of the method to do so. + // References in turn can be created from both owned values and other + // references. + let rules = rules.relaxed(); + + if left.instance_of.is_generic(self.db) { + // The implemented trait may refer to type parameters of the + // implementing class, so we need to expose those using a new scope. + let mut sub_scope = env.clone(); + + left.type_arguments(self.db).copy_into(&mut sub_scope.left); + + self.check_bounds(&imp.bounds, &mut sub_scope) + && self.check_traits(imp.instance, right, &mut sub_scope, rules) + } else { + self.check_bounds(&imp.bounds, env) + && self.check_traits(imp.instance, right, env, rules) + } + } + + fn check_type_ref_with_trait( + &mut self, + left: TypeRef, + right: TraitInstance, + env: &mut Environment, + rules: Rules, + ) -> bool { + match left { + TypeRef::Owned(id) + | TypeRef::Uni(id) + | TypeRef::Ref(id) + | TypeRef::Mut(id) + | TypeRef::RefUni(id) + | TypeRef::MutUni(id) + | TypeRef::Infer(id) => match id { + TypeId::ClassInstance(lhs) => { + self.check_class_with_trait(lhs, right, env, rules) + } + TypeId::TraitInstance(lhs) => { + self.check_traits(lhs, right, env, rules) + } + TypeId::TypeParameter(lhs) + | TypeId::RigidTypeParameter(lhs) => { + self.check_parameter_with_trait(lhs, right, env, rules) + } + _ => false, + }, + TypeRef::Placeholder(id) => match id.value(self.db) { + Some(typ) => { + self.check_type_ref_with_trait(typ, right, env, rules) + } + // When the placeholder isn't assigned a value, the comparison + // is treated as valid but we don't assign a type. This is + // because in this scenario we can't reliably guess what the + // type is, and what its ownership should be. + _ => true, + }, + TypeRef::Never => true, + _ => false, + } + } + + fn check_parameter_with_trait( + &mut self, + left: TypeParameterId, + right: TraitInstance, + env: &mut Environment, + rules: Rules, + ) -> bool { + left.requirements(self.db) + .into_iter() + .any(|left| self.check_traits(left, right, env, rules)) + } + + fn check_parameters( + &mut self, + left: TypeParameterId, + right: TypeParameterId, + env: &mut Environment, + rules: Rules, + ) -> bool { + if left == right { + return true; + } + + right + .requirements(self.db) + .into_iter() + .all(|req| self.check_parameter_with_trait(left, req, env, rules)) + } + + fn check_traits( + &mut self, + left: TraitInstance, + right: TraitInstance, + env: &mut Environment, + rules: Rules, + ) -> bool { + if left == right { + return true; + } + + if left.instance_of != right.instance_of { + return if rules.subtyping { + left.instance_of + .required_traits(self.db) + .into_iter() + .any(|lhs| self.check_traits(lhs, right, env, rules)) + } else { + false + }; + } + + if !left.instance_of.is_generic(self.db) { + return true; + } + + let lhs_args = left.type_arguments(self.db); + let rhs_args = right.type_arguments(self.db); + + left.instance_of.type_parameters(self.db).into_iter().all(|param| { + let rules = rules.infer_as_rigid(); + let lhs = lhs_args.get(param).unwrap(); + let rhs = rhs_args.get(param).unwrap(); + + self.check_type_ref(lhs, rhs, env, rules) + }) + } + + fn check_arguments( + &mut self, + left: &Arguments, + right: &Arguments, + env: &mut Environment, + rules: Rules, + same_name: bool, + ) -> bool { + if left.len() != right.len() { + return false; + } + + left.mapping.values().iter().zip(right.mapping.values().iter()).all( + |(ours, theirs)| { + if same_name && ours.name != theirs.name { + return false; + } + + self.check_type_ref( + ours.value_type, + theirs.value_type, + env, + rules, + ) + }, + ) + } + + fn resolve( + &self, + typ: TypeRef, + arguments: &TypeArguments, + rules: Rules, + ) -> TypeRef { + let result = match typ { + TypeRef::Owned(TypeId::TypeParameter(id)) + | TypeRef::Uni(TypeId::TypeParameter(id)) + | TypeRef::Infer(TypeId::TypeParameter(id)) => { + self.resolve_type_parameter(typ, id, arguments, rules) + } + TypeRef::Ref(TypeId::TypeParameter(id)) => self + .resolve_type_parameter(typ, id, arguments, rules) + .as_ref(self.db), + TypeRef::Mut(TypeId::TypeParameter(id)) => self + .resolve_type_parameter(typ, id, arguments, rules) + .as_mut(self.db), + TypeRef::Placeholder(id) => id + .value(self.db) + .map_or(typ, |v| self.resolve(v, arguments, rules)), + _ => typ, + }; + + match result { + TypeRef::Infer(TypeId::TypeParameter(id)) + if rules.infer_as_rigid => + { + TypeRef::Owned(TypeId::RigidTypeParameter(id)) + } + _ => result, + } + } + + fn resolve_type_parameter( + &self, + typ: TypeRef, + id: TypeParameterId, + arguments: &TypeArguments, + rules: Rules, + ) -> TypeRef { + match arguments.get(id) { + Some(arg @ TypeRef::Placeholder(id)) => id + .value(self.db) + .map(|v| self.resolve(v, arguments, rules)) + .unwrap_or(arg), + Some(arg) => arg, + _ => typ, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::format::format_type; + use crate::test::{ + closure, generic_instance_id, generic_trait_instance, + generic_trait_instance_id, immutable, implement, infer, instance, + mutable, new_class, new_extern_class, new_parameter, new_trait, owned, + parameter, placeholder, rigid, trait_instance, trait_instance_id, + type_arguments, type_bounds, uni, + }; + use crate::{ + Block, ClassId, Closure, TraitImplementation, TypePlaceholder, + }; + + #[track_caller] + fn check_ok(db: &Database, left: TypeRef, right: TypeRef) { + if !TypeChecker::check(db, left, right) { + panic!( + "Expected {} to be compatible with {}", + format_type(db, left), + format_type(db, right) + ); + } + } + + #[track_caller] + fn check_err(db: &Database, left: TypeRef, right: TypeRef) { + assert!( + !TypeChecker::check(db, left, right), + "Expected {} to not be compatible with {}", + format_type(db, left), + format_type(db, right) + ); + } + + #[test] + fn test_any() { + let mut db = Database::new(); + let to_string = new_trait(&mut db, "ToString"); + let param1 = new_parameter(&mut db, "T"); + let param2 = new_parameter(&mut db, "T"); + let var1 = TypePlaceholder::alloc(&mut db, None); + let var2 = TypePlaceholder::alloc(&mut db, Some(param1)); + let var3 = TypePlaceholder::alloc(&mut db, Some(param2)); + + param2.add_requirements(&mut db, vec![trait_instance(to_string)]); + + check_ok(&db, TypeRef::Any, TypeRef::Any); + check_ok(&db, TypeRef::Any, TypeRef::Error); + check_ok(&db, TypeRef::Any, placeholder(var1)); + check_ok(&db, TypeRef::Any, placeholder(var2)); + + check_err(&db, TypeRef::Any, placeholder(var3)); + check_err(&db, TypeRef::Any, TypeRef::Never); + } + + #[test] + fn test_never() { + let mut db = Database::new(); + let param = new_parameter(&mut db, "T"); + let to_string = new_trait(&mut db, "ToString"); + let var1 = TypePlaceholder::alloc(&mut db, None); + let var2 = TypePlaceholder::alloc(&mut db, Some(param)); + + param.add_requirements(&mut db, vec![trait_instance(to_string)]); + + check_ok(&db, TypeRef::Never, placeholder(var1)); + check_ok(&db, TypeRef::Never, placeholder(var2)); + check_ok(&db, TypeRef::Never, TypeRef::Any); + check_ok(&db, TypeRef::Never, TypeRef::Never); + } + + #[test] + fn test_owned_class_instance() { + let mut db = Database::new(); + let foo = new_class(&mut db, "Foo"); + let bar = new_class(&mut db, "Bar"); + let int = ClassId::int(); + let var1 = TypePlaceholder::alloc(&mut db, None); + let to_string = new_trait(&mut db, "ToString"); + let param = new_parameter(&mut db, "T"); + let var2 = TypePlaceholder::alloc(&mut db, Some(param)); + let var3 = TypePlaceholder::alloc(&mut db, Some(param)); + + param.add_requirements(&mut db, vec![trait_instance(to_string)]); + implement(&mut db, trait_instance(to_string), bar); + + check_ok(&db, owned(instance(foo)), owned(instance(foo))); + check_ok(&db, owned(instance(foo)), infer(instance(foo))); + + // This placeholder doesn't have any requirements + check_ok(&db, owned(instance(foo)), placeholder(var1)); + assert_eq!(var1.value(&db), Some(owned(instance(foo)))); + + // The placeholder is now assigned to Foo, so Bar shouldn't be + // compatible with it. + check_err(&db, owned(instance(bar)), placeholder(var1)); + + // Foo doesn't implement ToString, so the check fails. + check_err(&db, owned(instance(foo)), placeholder(var2)); + assert!(var2.value(&db).is_none()); + + // Bar implements ToString, so this _does_ check and assigns the + // placeholder. + check_ok(&db, owned(instance(bar)), placeholder(var3)); + assert_eq!(var3.value(&db), Some(owned(instance(bar)))); + + // Value types can be passed to a reference/unique values. + check_ok(&db, owned(instance(int)), immutable(instance(int))); + check_ok(&db, owned(instance(int)), mutable(instance(int))); + check_ok(&db, owned(instance(int)), uni(instance(int))); + + check_ok(&db, owned(instance(foo)), TypeRef::Any); + check_ok(&db, owned(instance(foo)), TypeRef::Error); + + check_err(&db, owned(instance(foo)), immutable(instance(foo))); + check_err(&db, owned(instance(foo)), mutable(instance(foo))); + check_err(&db, owned(instance(foo)), owned(instance(bar))); + check_err(&db, owned(instance(foo)), TypeRef::Never); + } + + #[test] + fn test_extern_class_instance() { + let mut db = Database::new(); + let foo = new_extern_class(&mut db, "Foo"); + let bar = new_extern_class(&mut db, "Bar"); + let param = new_parameter(&mut db, "T"); + + check_ok(&db, owned(instance(foo)), owned(instance(foo))); + + check_err(&db, owned(instance(foo)), owned(instance(bar))); + check_err(&db, owned(instance(foo)), TypeRef::Any); + check_err(&db, owned(instance(foo)), owned(parameter(param))); + check_err(&db, uni(instance(foo)), owned(parameter(param))); + } + + #[test] + fn test_owned_generic_class_instance() { + let mut db = Database::new(); + let array = new_class(&mut db, "Array"); + let thing = new_class(&mut db, "Thing"); + let to_string = new_trait(&mut db, "ToString"); + let length = new_trait(&mut db, "Length"); + let equal = new_trait(&mut db, "Equal"); + + equal.new_type_parameter(&mut db, "X".to_string()); + + let array_param = array.new_type_parameter(&mut db, "T".to_string()); + let var = TypePlaceholder::alloc(&mut db, None); + + // V: Equal[V] + let v_param = new_parameter(&mut db, "V"); + + { + let req = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(v_param))], + ); + + v_param.add_requirements(&mut db, vec![req]); + } + + { + let bound = new_parameter(&mut db, "Tbound"); + + bound.add_requirements(&mut db, vec![trait_instance(to_string)]); + + let trait_impl = TraitImplementation { + instance: trait_instance(to_string), + bounds: type_bounds(vec![(array_param, bound)]), + }; + + // impl ToString for Array if T: ToString + array.add_trait_implementation(&mut db, trait_impl); + } + + // impl Length for Array + array.add_trait_implementation( + &mut db, + TraitImplementation { + instance: trait_instance(length), + bounds: TypeBounds::new(), + }, + ); + + // impl ToString for Thing + thing.add_trait_implementation( + &mut db, + TraitImplementation { + instance: trait_instance(to_string), + bounds: TypeBounds::new(), + }, + ); + + // impl Equal[Thing] for Thing + { + let eq = generic_trait_instance( + &mut db, + equal, + vec![owned(instance(thing))], + ); + + thing.add_trait_implementation( + &mut db, + TraitImplementation { instance: eq, bounds: TypeBounds::new() }, + ); + } + + // impl Equal[Array[T]] for Array if T: Equal[T] + { + let bound = new_parameter(&mut db, "Tbound"); + let bound_eq = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(bound))], + ); + + bound.add_requirements(&mut db, vec![bound_eq]); + + let array_t = owned(generic_instance_id( + &mut db, + array, + vec![infer(parameter(bound))], + )); + + let impl_ins = + generic_trait_instance(&mut db, equal, vec![array_t]); + let trait_impl = TraitImplementation { + instance: impl_ins, + bounds: type_bounds(vec![(array_param, bound)]), + }; + + array.add_trait_implementation(&mut db, trait_impl); + } + + let things1 = + generic_instance_id(&mut db, array, vec![owned(instance(thing))]); + let things2 = + generic_instance_id(&mut db, array, vec![owned(instance(thing))]); + let thing_refs = generic_instance_id( + &mut db, + array, + vec![immutable(instance(thing))], + ); + let floats = + generic_instance_id(&mut db, array, vec![TypeRef::float()]); + let vars = generic_instance_id(&mut db, array, vec![placeholder(var)]); + let eq_things = + generic_trait_instance_id(&mut db, equal, vec![owned(things1)]); + + check_ok(&db, owned(things1), owned(things1)); + check_ok(&db, owned(things1), owned(things2)); + check_ok(&db, owned(things1), owned(trait_instance_id(length))); + check_ok(&db, owned(floats), owned(trait_instance_id(length))); + check_ok(&db, owned(things1), owned(trait_instance_id(to_string))); + + check_ok(&db, owned(vars), owned(trait_instance_id(to_string))); + assert!(var.value(&db).is_none()); + + check_ok(&db, owned(things1), owned(eq_things)); + check_ok(&db, owned(things1), infer(parameter(v_param))); + check_ok(&db, owned(thing_refs), owned(parameter(v_param))); + + check_err(&db, owned(things1), owned(floats)); + check_err(&db, owned(floats), owned(trait_instance_id(to_string))); + check_err(&db, owned(floats), infer(parameter(v_param))); + } + + #[test] + fn test_uni_class_instance() { + let mut db = Database::new(); + let foo = new_class(&mut db, "Foo"); + let bar = new_class(&mut db, "Bar"); + let int = ClassId::int(); + let var1 = TypePlaceholder::alloc(&mut db, None); + let to_string = new_trait(&mut db, "ToString"); + let param = new_parameter(&mut db, "T"); + let var2 = TypePlaceholder::alloc(&mut db, Some(param)); + let var3 = TypePlaceholder::alloc(&mut db, Some(param)); + + param.add_requirements(&mut db, vec![trait_instance(to_string)]); + implement(&mut db, trait_instance(to_string), bar); + + check_ok(&db, uni(instance(foo)), uni(instance(foo))); + check_ok(&db, uni(instance(foo)), owned(instance(foo))); + check_ok(&db, uni(instance(foo)), infer(instance(foo))); + + // This placeholder doesn't have any requirements + check_ok(&db, uni(instance(foo)), placeholder(var1)); + assert_eq!(var1.value(&db), Some(uni(instance(foo)))); + + // The placeholder is now assigned to Foo, so Bar shouldn't be + // compatible with it. + check_err(&db, uni(instance(bar)), placeholder(var1)); + + // Foo doesn't implement ToString, so the check fails. + check_err(&db, uni(instance(foo)), placeholder(var2)); + assert!(var2.value(&db).is_none()); + + // Bar implements ToString, so this _does_ check and assigns the + // placeholder. + check_ok(&db, uni(instance(bar)), placeholder(var3)); + assert_eq!(var3.value(&db), Some(uni(instance(bar)))); + + // Value types can be passed to a reference. + check_ok(&db, uni(instance(int)), immutable(instance(int))); + check_ok(&db, uni(instance(int)), mutable(instance(int))); + + check_ok(&db, uni(instance(foo)), TypeRef::Any); + check_ok(&db, uni(instance(foo)), TypeRef::Error); + + check_err(&db, uni(instance(foo)), immutable(instance(foo))); + check_err(&db, uni(instance(foo)), mutable(instance(foo))); + check_err(&db, uni(instance(foo)), uni(instance(bar))); + check_err(&db, uni(instance(foo)), TypeRef::Never); + } + + #[test] + fn test_uni_generic_class_instance() { + let mut db = Database::new(); + let array = new_class(&mut db, "Array"); + let thing = new_class(&mut db, "Thing"); + let to_string = new_trait(&mut db, "ToString"); + let length = new_trait(&mut db, "Length"); + let equal = new_trait(&mut db, "Equal"); + + equal.new_type_parameter(&mut db, "X".to_string()); + + let array_param = array.new_type_parameter(&mut db, "T".to_string()); + let var = TypePlaceholder::alloc(&mut db, None); + + // V: Equal[V] + let v_param = new_parameter(&mut db, "V"); + + { + let req = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(v_param))], + ); + + v_param.add_requirements(&mut db, vec![req]); + } + + { + let bound = new_parameter(&mut db, "Tbound"); + + bound.add_requirements(&mut db, vec![trait_instance(to_string)]); + + let trait_impl = TraitImplementation { + instance: trait_instance(to_string), + bounds: type_bounds(vec![(array_param, bound)]), + }; + + // impl ToString for Array if T: ToString + array.add_trait_implementation(&mut db, trait_impl); + } + + // impl Length for Array + array.add_trait_implementation( + &mut db, + TraitImplementation { + instance: trait_instance(length), + bounds: TypeBounds::new(), + }, + ); + + // impl ToString for Thing + thing.add_trait_implementation( + &mut db, + TraitImplementation { + instance: trait_instance(to_string), + bounds: TypeBounds::new(), + }, + ); + + // impl Equal[uni Thing] for Thing + { + let eq = generic_trait_instance( + &mut db, + equal, + vec![uni(instance(thing))], + ); + + thing.add_trait_implementation( + &mut db, + TraitImplementation { instance: eq, bounds: TypeBounds::new() }, + ); + } + + // impl Equal[uni Array[T]] for Array if T: Equal[T] + { + let bound = new_parameter(&mut db, "Tbound"); + let bound_eq = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(bound))], + ); + + bound.add_requirements(&mut db, vec![bound_eq]); + + let array_t = uni(generic_instance_id( + &mut db, + array, + vec![infer(parameter(bound))], + )); + + let impl_ins = + generic_trait_instance(&mut db, equal, vec![array_t]); + let trait_impl = TraitImplementation { + instance: impl_ins, + bounds: type_bounds(vec![(array_param, bound)]), + }; + + array.add_trait_implementation(&mut db, trait_impl); + } + + let things1 = + generic_instance_id(&mut db, array, vec![uni(instance(thing))]); + let things2 = + generic_instance_id(&mut db, array, vec![uni(instance(thing))]); + let thing_refs = generic_instance_id( + &mut db, + array, + vec![immutable(instance(thing))], + ); + let floats = + generic_instance_id(&mut db, array, vec![TypeRef::float()]); + let vars = generic_instance_id(&mut db, array, vec![placeholder(var)]); + let eq_things = + generic_trait_instance_id(&mut db, equal, vec![uni(things1)]); + + check_ok(&db, uni(things1), uni(things1)); + check_ok(&db, uni(things1), uni(things2)); + check_ok(&db, uni(things1), uni(trait_instance_id(length))); + check_ok(&db, uni(floats), uni(trait_instance_id(length))); + check_ok(&db, uni(things1), uni(trait_instance_id(to_string))); + + check_ok(&db, uni(vars), uni(trait_instance_id(to_string))); + assert!(var.value(&db).is_none()); + + check_ok(&db, uni(things1), uni(eq_things)); + check_ok(&db, uni(things1), infer(parameter(v_param))); + check_ok(&db, uni(things1), uni(parameter(v_param))); + + check_err(&db, uni(thing_refs), uni(parameter(v_param))); + check_err(&db, uni(things1), uni(floats)); + check_err(&db, uni(floats), uni(trait_instance_id(to_string))); + check_err(&db, uni(floats), infer(parameter(v_param))); + } + + #[test] + fn test_infer() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok(&db, infer(parameter(param1)), infer(parameter(param2))); + check_ok(&db, infer(parameter(param1)), immutable(parameter(param2))); + check_ok(&db, infer(parameter(param1)), TypeRef::Any); + check_ok(&db, infer(parameter(param1)), TypeRef::Error); + + check_ok(&db, infer(parameter(param1)), placeholder(var)); + assert_eq!(var.value(&db), Some(infer(parameter(param1)))); + + check_err(&db, infer(parameter(param1)), owned(parameter(param2))); + check_err(&db, infer(parameter(param1)), uni(parameter(param2))); + check_err(&db, infer(parameter(param1)), mutable(parameter(param2))); + } + + #[test] + fn test_ref() { + let mut db = Database::new(); + let thing = new_class(&mut db, "Thing"); + let int = ClassId::int(); + let var = TypePlaceholder::alloc(&mut db, None); + let param = new_parameter(&mut db, "T"); + let mutable_var = TypePlaceholder::alloc(&mut db, Some(param)); + + param.set_mutable(&mut db); + + check_ok(&db, immutable(instance(thing)), immutable(instance(thing))); + check_ok(&db, immutable(instance(thing)), infer(instance(thing))); + + // Value types can be passed around this way. + check_ok(&db, immutable(instance(int)), mutable(instance(int))); + check_ok(&db, immutable(instance(int)), owned(instance(int))); + check_ok(&db, immutable(instance(int)), uni(instance(int))); + + check_ok(&db, immutable(instance(thing)), placeholder(var)); + assert_eq!(var.value(&db), Some(immutable(instance(thing)))); + + check_ok(&db, immutable(instance(thing)), TypeRef::Any); + check_ok(&db, immutable(instance(thing)), TypeRef::Error); + check_ok(&db, immutable(instance(int)), infer(parameter(param))); + check_ok(&db, immutable(instance(int)), placeholder(mutable_var)); + + check_err(&db, immutable(instance(thing)), mutable(instance(thing))); + check_err(&db, immutable(instance(thing)), owned(instance(thing))); + check_err(&db, immutable(instance(thing)), infer(parameter(param))); + check_err(&db, immutable(instance(thing)), placeholder(mutable_var)); + } + + #[test] + fn test_mut() { + let mut db = Database::new(); + let thing = new_class(&mut db, "Thing"); + let int = ClassId::int(); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok(&db, mutable(instance(thing)), immutable(instance(thing))); + check_ok(&db, mutable(instance(thing)), mutable(instance(thing))); + check_ok(&db, mutable(instance(thing)), infer(instance(thing))); + + // Value types can be passed around this way. + check_ok(&db, mutable(instance(int)), owned(instance(int))); + check_ok(&db, mutable(instance(int)), uni(instance(int))); + + check_ok(&db, mutable(instance(thing)), placeholder(var)); + assert_eq!(var.value(&db), Some(mutable(instance(thing)))); + + check_ok(&db, mutable(instance(thing)), TypeRef::Any); + check_ok(&db, mutable(instance(thing)), TypeRef::Error); + + check_err(&db, mutable(instance(thing)), owned(instance(thing))); + check_err(&db, mutable(instance(thing)), uni(instance(thing))); + } + + #[test] + fn test_ref_uni() { + let mut db = Database::new(); + let thing = new_class(&mut db, "Thing"); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok( + &db, + TypeRef::RefUni(instance(thing)), + TypeRef::RefUni(instance(thing)), + ); + check_ok(&db, TypeRef::RefUni(instance(thing)), TypeRef::Error); + + check_err( + &db, + TypeRef::RefUni(instance(thing)), + TypeRef::MutUni(instance(thing)), + ); + check_err(&db, TypeRef::RefUni(instance(thing)), placeholder(var)); + check_err(&db, TypeRef::RefUni(instance(thing)), TypeRef::Any); + } + + #[test] + fn test_mut_uni() { + let mut db = Database::new(); + let thing = new_class(&mut db, "Thing"); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok( + &db, + TypeRef::MutUni(instance(thing)), + TypeRef::MutUni(instance(thing)), + ); + check_ok(&db, TypeRef::MutUni(instance(thing)), TypeRef::Error); + + check_err( + &db, + TypeRef::MutUni(instance(thing)), + TypeRef::RefUni(instance(thing)), + ); + check_err(&db, TypeRef::MutUni(instance(thing)), placeholder(var)); + check_err(&db, TypeRef::MutUni(instance(thing)), TypeRef::Any); + } + + #[test] + fn test_placeholder() { + let mut db = Database::new(); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok(&db, placeholder(var), TypeRef::int()); + assert_eq!(var.value(&db), Some(TypeRef::int())); + } + + #[test] + fn test_class_with_trait() { + let mut db = Database::new(); + let animal = new_trait(&mut db, "Animal"); + let cat = new_class(&mut db, "Cat"); + let array = ClassId::array(); + + array.new_type_parameter(&mut db, "T".to_string()); + implement(&mut db, trait_instance(animal), cat); + + // Array[Cat] + let cats = owned(generic_instance_id( + &mut db, + array, + vec![owned(instance(cat))], + )); + + // ref Array[Cat] + let ref_cats = immutable(generic_instance_id( + &mut db, + array, + vec![owned(instance(cat))], + )); + + // mut Array[Cat] + let mut_cats = mutable(generic_instance_id( + &mut db, + array, + vec![owned(instance(cat))], + )); + + // Array[Animal] + let animals = owned(generic_instance_id( + &mut db, + array, + vec![owned(trait_instance_id(animal))], + )); + + // ref Array[Animal] + let ref_animals = immutable(generic_instance_id( + &mut db, + array, + vec![owned(trait_instance_id(animal))], + )); + + // mut Array[Animal] + let mut_animals = mutable(generic_instance_id( + &mut db, + array, + vec![owned(trait_instance_id(animal))], + )); + + check_ok(&db, cats, animals); + check_ok(&db, ref_cats, ref_animals); + check_ok(&db, mut_cats, ref_animals); + + // This isn't OK as this could result in a Dog being added to the Array. + check_err(&db, mut_cats, mut_animals); + } + + #[test] + fn test_traits() { + let mut db = Database::new(); + let to_string = new_trait(&mut db, "ToString"); + let display = new_trait(&mut db, "Display"); + let debug = new_trait(&mut db, "Debug"); + + display.add_required_trait(&mut db, trait_instance(to_string)); + debug.add_required_trait(&mut db, trait_instance(display)); + + check_ok( + &db, + owned(trait_instance_id(to_string)), + owned(trait_instance_id(to_string)), + ); + check_ok( + &db, + owned(trait_instance_id(display)), + owned(trait_instance_id(to_string)), + ); + check_ok( + &db, + owned(trait_instance_id(debug)), + owned(trait_instance_id(to_string)), + ); + check_err( + &db, + owned(trait_instance_id(to_string)), + owned(trait_instance_id(display)), + ); + } + + #[test] + fn test_generic_traits() { + let mut db = Database::new(); + let equal = new_trait(&mut db, "Equal"); + let thing = new_class(&mut db, "Thing"); + + equal.new_type_parameter(&mut db, "T".to_string()); + + // Equal[Thing] + let eq_thing = owned(generic_trait_instance_id( + &mut db, + equal, + vec![owned(instance(thing))], + )); + let eq_ref_thing = owned(generic_trait_instance_id( + &mut db, + equal, + vec![immutable(instance(thing))], + )); + let eq_mut_thing = owned(generic_trait_instance_id( + &mut db, + equal, + vec![mutable(instance(thing))], + )); + + check_ok(&db, eq_thing, eq_thing); + check_ok(&db, eq_ref_thing, eq_ref_thing); + check_ok(&db, eq_mut_thing, eq_mut_thing); + check_err(&db, eq_thing, eq_ref_thing); + check_err(&db, eq_thing, eq_mut_thing); + } + + #[test] + fn test_type_parameter_with_trait() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let param3 = new_parameter(&mut db, "C"); + let equal = new_trait(&mut db, "Equal"); + let to_string = new_trait(&mut db, "ToString"); + + param1.add_requirements(&mut db, vec![trait_instance(equal)]); + param3.add_requirements(&mut db, vec![trait_instance(equal)]); + param3.add_requirements(&mut db, vec![trait_instance(to_string)]); + + check_ok( + &db, + owned(parameter(param1)), + owned(trait_instance_id(equal)), + ); + check_ok( + &db, + owned(parameter(param3)), + owned(trait_instance_id(equal)), + ); + check_ok( + &db, + owned(parameter(param3)), + owned(trait_instance_id(to_string)), + ); + check_err( + &db, + owned(parameter(param2)), + owned(trait_instance_id(equal)), + ); + } + + #[test] + fn test_trait_with_parameter() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let equal = new_trait(&mut db, "Equal"); + let foo = new_trait(&mut db, "Foo"); + let to_string = new_trait(&mut db, "ToString"); + + foo.add_required_trait(&mut db, trait_instance(equal)); + foo.add_required_trait(&mut db, trait_instance(to_string)); + + param1.add_requirements(&mut db, vec![trait_instance(equal)]); + param2.add_requirements(&mut db, vec![trait_instance(equal)]); + param2.add_requirements(&mut db, vec![trait_instance(to_string)]); + + check_ok( + &db, + owned(trait_instance_id(equal)), + owned(parameter(param1)), + ); + check_ok(&db, owned(trait_instance_id(foo)), owned(parameter(param2))); + check_err( + &db, + owned(trait_instance_id(to_string)), + owned(parameter(param1)), + ); + } + + #[test] + fn test_parameters() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let param3 = new_parameter(&mut db, "C"); + let param4 = new_parameter(&mut db, "D"); + let equal = new_trait(&mut db, "Equal"); + let test = new_trait(&mut db, "Test"); + + test.add_required_trait(&mut db, trait_instance(equal)); + param3.add_requirements(&mut db, vec![trait_instance(equal)]); + param4.add_requirements(&mut db, vec![trait_instance(test)]); + + check_ok(&db, owned(parameter(param1)), owned(parameter(param2))); + check_ok(&db, owned(parameter(param4)), owned(parameter(param3))); + check_err(&db, owned(parameter(param3)), owned(parameter(param4))); + } + + #[test] + fn test_type_parameter_ref_assigned_to_owned() { + let mut db = Database::new(); + let param = new_parameter(&mut db, "A"); + let thing = new_class(&mut db, "Thing"); + let args = type_arguments(vec![(param, owned(instance(thing)))]); + let mut env = Environment::new(args.clone(), args); + let res = TypeChecker::new(&db).run( + immutable(instance(thing)), + immutable(parameter(param)), + &mut env, + ); + + assert!(res); + } + + #[test] + fn test_rigid() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let var = TypePlaceholder::alloc(&mut db, None); + + check_ok(&db, owned(rigid(param1)), TypeRef::Any); + check_ok(&db, owned(rigid(param1)), TypeRef::Error); + check_ok(&db, immutable(rigid(param1)), immutable(rigid(param1))); + check_ok(&db, owned(rigid(param1)), owned(rigid(param1))); + check_ok(&db, owned(rigid(param1)), infer(rigid(param1))); + check_ok(&db, owned(rigid(param1)), infer(parameter(param1))); + check_ok(&db, immutable(rigid(param1)), immutable(parameter(param1))); + + check_ok(&db, owned(rigid(param1)), placeholder(var)); + assert_eq!(var.value(&db), Some(owned(rigid(param1)))); + + check_err(&db, owned(rigid(param1)), owned(rigid(param2))); + check_err(&db, immutable(rigid(param1)), immutable(rigid(param2))); + check_err(&db, owned(rigid(param1)), owned(parameter(param1))); + } + + #[test] + fn test_rigid_with_trait() { + let mut db = Database::new(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let to_string = new_trait(&mut db, "ToString"); + let equal = new_trait(&mut db, "Equal"); + + param1.add_requirements(&mut db, vec![trait_instance(to_string)]); + + check_ok( + &db, + immutable(rigid(param1)), + immutable(trait_instance_id(to_string)), + ); + check_ok(&db, owned(rigid(param1)), infer(parameter(param2))); + + // A doesn't implement Equal + check_err( + &db, + immutable(rigid(param1)), + immutable(trait_instance_id(equal)), + ); + } + + #[test] + fn test_simple_closures() { + let mut db = Database::new(); + let fun1 = Closure::alloc(&mut db, false); + let fun2 = Closure::alloc(&mut db, false); + + fun1.set_return_type(&mut db, TypeRef::Any); + fun2.set_return_type(&mut db, TypeRef::Any); + + check_ok(&db, owned(closure(fun1)), owned(closure(fun2))); + } + + #[test] + fn test_closures_with_arguments() { + let mut db = Database::new(); + let fun1 = Closure::alloc(&mut db, false); + let fun2 = Closure::alloc(&mut db, false); + let fun3 = Closure::alloc(&mut db, false); + let fun4 = Closure::alloc(&mut db, false); + let int = TypeRef::int(); + let float = TypeRef::float(); + + fun1.new_argument(&mut db, "a".to_string(), int, int); + fun2.new_argument(&mut db, "b".to_string(), int, int); + fun4.new_argument(&mut db, "a".to_string(), float, float); + fun1.set_return_type(&mut db, TypeRef::Any); + fun2.set_return_type(&mut db, TypeRef::Any); + fun3.set_return_type(&mut db, TypeRef::Any); + fun4.set_return_type(&mut db, TypeRef::Any); + + check_ok(&db, owned(closure(fun1)), owned(closure(fun2))); + check_err(&db, owned(closure(fun3)), owned(closure(fun2))); + check_err(&db, owned(closure(fun1)), owned(closure(fun4))); + } + + #[test] + fn test_closures_with_return_types() { + let mut db = Database::new(); + let fun1 = Closure::alloc(&mut db, false); + let fun2 = Closure::alloc(&mut db, false); + let fun3 = Closure::alloc(&mut db, false); + let int = TypeRef::int(); + let float = TypeRef::float(); + + fun1.set_return_type(&mut db, int); + fun2.set_return_type(&mut db, int); + fun3.set_return_type(&mut db, float); + + check_ok(&db, owned(closure(fun1)), owned(closure(fun2))); + check_err(&db, owned(closure(fun1)), owned(closure(fun3))); + } + + #[test] + fn test_closure_with_parameter() { + let mut db = Database::new(); + let fun = Closure::alloc(&mut db, false); + let equal = new_trait(&mut db, "Equal"); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + + param2.add_requirements(&mut db, vec![trait_instance(equal)]); + + check_ok(&db, owned(closure(fun)), owned(parameter(param1))); + check_err(&db, owned(closure(fun)), owned(parameter(param2))); + } + + #[test] + fn test_closure_with_placeholder() { + let mut db = Database::new(); + let fun = Closure::alloc(&mut db, false); + let param = new_parameter(&mut db, "A"); + let var = TypePlaceholder::alloc(&mut db, Some(param)); + + check_ok(&db, owned(closure(fun)), placeholder(var)); + } + + #[test] + fn test_recursive_type() { + let mut db = Database::new(); + let array = ClassId::array(); + let var = TypePlaceholder::alloc(&mut db, None); + + array.new_type_parameter(&mut db, "T".to_string()); + + let given = + owned(generic_instance_id(&mut db, array, vec![placeholder(var)])); + let ints = + owned(generic_instance_id(&mut db, array, vec![TypeRef::int()])); + let exp = owned(generic_instance_id(&mut db, array, vec![ints])); + + var.assign(&db, given); + check_err(&db, given, exp); + } + + #[test] + fn test_mutable_bounds() { + let mut db = Database::new(); + let array = ClassId::array(); + let thing = new_class(&mut db, "Thing"); + let update = new_trait(&mut db, "Update"); + let param = array.new_type_parameter(&mut db, "T".to_string()); + let bound = new_parameter(&mut db, "T"); + + bound.set_mutable(&mut db); + array.add_trait_implementation( + &mut db, + TraitImplementation { + instance: trait_instance(update), + bounds: type_bounds(vec![(param, bound)]), + }, + ); + + // Array[Thing] + let owned_things = owned(generic_instance_id( + &mut db, + array, + vec![owned(instance(thing))], + )); + + // Array[ref Thing] + let ref_things = owned(generic_instance_id( + &mut db, + array, + vec![immutable(instance(thing))], + )); + + check_ok(&db, owned_things, owned(trait_instance_id(update))); + + // `ref Thing` isn't mutable, so this check should fail. + check_err(&db, ref_things, owned(trait_instance_id(update))); + } + + #[test] + fn test_array_of_generic_classes_with_traits() { + let mut db = Database::new(); + let iter = new_trait(&mut db, "Iter"); + let array = ClassId::array(); + + array.new_type_parameter(&mut db, "ArrayT".to_string()); + iter.new_type_parameter(&mut db, "IterT".to_string()); + + let iterator = new_class(&mut db, "Iterator"); + let iterator_param = + iterator.new_type_parameter(&mut db, "IteratorT".to_string()); + + // impl Iter[T] for Iterator + let iter_impl = TraitImplementation { + instance: generic_trait_instance( + &mut db, + iter, + vec![infer(parameter(iterator_param))], + ), + bounds: TypeBounds::new(), + }; + + iterator.add_trait_implementation(&mut db, iter_impl); + + let int_iterator = + owned(generic_instance_id(&mut db, iterator, vec![TypeRef::int()])); + + let int_iter = owned(generic_trait_instance_id( + &mut db, + iter, + vec![TypeRef::int()], + )); + + // Array[Iterator[Int]] + let lhs = + owned(generic_instance_id(&mut db, array, vec![int_iterator])); + + // Array[Iter[Int]] + let rhs = owned(generic_instance_id(&mut db, array, vec![int_iter])); + + check_ok(&db, lhs, rhs); + } + + #[test] + fn test_rigid_type_parameter() { + let mut db = Database::new(); + let thing = new_class(&mut db, "Thing"); + let param = new_parameter(&mut db, "T"); + let args = type_arguments(vec![(param, owned(instance(thing)))]); + let mut env = Environment::new(args.clone(), args); + let res = TypeChecker::new(&db).run( + owned(instance(thing)), + owned(rigid(param)), + &mut env, + ); + + assert!(!res); + check_ok(&db, owned(rigid(param)), infer(parameter(param))); + } + + #[test] + fn test_rigid_type_parameter_with_requirements_with_placeholder() { + let mut db = Database::new(); + let equal = new_trait(&mut db, "Equal"); + let param1 = new_parameter(&mut db, "T"); + let param2 = new_parameter(&mut db, "T"); + let var = TypePlaceholder::alloc(&mut db, Some(param2)); + + equal.new_type_parameter(&mut db, "T".to_string()); + + let param1_req = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(param1))], + ); + + let param2_req = generic_trait_instance( + &mut db, + equal, + vec![infer(parameter(param2))], + ); + + param1.add_requirements(&mut db, vec![param1_req]); + param2.add_requirements(&mut db, vec![param2_req]); + + let args = type_arguments(vec![(param2, placeholder(var))]); + let mut env = Environment::new(TypeArguments::new(), args); + let res = TypeChecker::new(&db).run( + owned(rigid(param1)), + infer(parameter(param2)), + &mut env, + ); + + assert!(res); + } +} diff --git a/types/src/either.rs b/types/src/either.rs new file mode 100644 index 000000000..8cde6ae4c --- /dev/null +++ b/types/src/either.rs @@ -0,0 +1,5 @@ +/// A type that is either something of type A, or something of type B. +pub(crate) enum Either { + Left(L), + Right(R), +} diff --git a/types/src/format.rs b/types/src/format.rs new file mode 100644 index 000000000..f423f34cc --- /dev/null +++ b/types/src/format.rs @@ -0,0 +1,815 @@ +//! Formatting of types. +use crate::{ + Arguments, ClassId, ClassInstance, ClassKind, ClosureId, Database, + MethodId, MethodKind, ModuleId, TraitId, TraitInstance, TypeArguments, + TypeId, TypeParameterId, TypePlaceholderId, TypeRef, Visibility, +}; + +const MAX_FORMATTING_DEPTH: usize = 8; + +pub fn format_type(db: &Database, typ: T) -> String { + TypeFormatter::new(db, None).format(typ) +} + +pub fn format_type_with_arguments( + db: &Database, + arguments: &TypeArguments, + typ: T, +) -> String { + TypeFormatter::new(db, Some(arguments)).format(typ) +} + +/// A buffer for formatting type names. +/// +/// We use a simple wrapper around a String so we can more easily change the +/// implementation in the future if necessary. +pub struct TypeFormatter<'a> { + db: &'a Database, + type_arguments: Option<&'a TypeArguments>, + buffer: String, + depth: usize, +} + +impl<'a> TypeFormatter<'a> { + pub fn new( + db: &'a Database, + type_arguments: Option<&'a TypeArguments>, + ) -> Self { + Self { db, type_arguments, buffer: String::new(), depth: 0 } + } + + pub fn verbose( + db: &'a Database, + type_arguments: Option<&'a TypeArguments>, + ) -> Self { + Self { db, type_arguments, buffer: String::new(), depth: 0 } + } + + pub fn format(mut self, typ: T) -> String { + typ.format_type(&mut self); + self.buffer + } + + pub(crate) fn descend(&mut self, block: F) { + if self.depth == MAX_FORMATTING_DEPTH { + self.write("..."); + } else { + self.depth += 1; + + block(self); + + self.depth -= 1; + } + } + + pub(crate) fn write(&mut self, thing: &str) { + self.buffer.push_str(thing); + } + + /// If a uni/ref/mut value wraps a type parameter, and that parameter is + /// assigned another value with ownership, you can end up with e.g. + /// `ref mut T` or `uni uni T`. This method provides a simple way of + /// preventing this from happening, without complicating the type formatting + /// process. + pub(crate) fn write_ownership(&mut self, thing: &str) { + if !self.buffer.ends_with(thing) { + self.write(thing); + } + } + + pub(crate) fn type_arguments( + &mut self, + parameters: &[TypeParameterId], + arguments: &TypeArguments, + ) { + for (index, ¶m) in parameters.iter().enumerate() { + if index > 0 { + self.write(", "); + } + + match arguments.get(param) { + Some(TypeRef::Placeholder(id)) + if id.value(self.db).is_none() => + { + // Placeholders without values aren't useful to show to the + // developer, so we show the type parameter instead. + // + // The parameter itself may be assigned a value through the + // type context (e.g. when a type is nested such as + // `Array[Array[T]]`), and we don't want to display that + // assignment as it's only to be used for the outer most + // type. As such, we don't use format_type() here. + param.format_type_without_argument(self); + } + Some(typ) => typ.format_type(self), + _ => param.format_type(self), + } + } + } + + pub(crate) fn arguments( + &mut self, + arguments: &Arguments, + include_name: bool, + ) { + if arguments.len() == 0 { + return; + } + + self.write(" ("); + + for (index, arg) in arguments.iter().enumerate() { + if index > 0 { + self.write(", "); + } + + if include_name { + self.write(&arg.name); + self.write(": "); + } + + arg.value_type.format_type(self); + } + + self.write(")"); + } + + pub(crate) fn return_type(&mut self, typ: TypeRef) { + match typ { + TypeRef::Placeholder(id) if id.value(self.db).is_none() => {} + _ if typ == TypeRef::nil() => {} + _ => { + self.write(" -> "); + typ.format_type(self); + } + } + } +} + +/// A type of which the name can be formatted into something human-readable. +pub trait FormatType { + fn format_type(&self, buffer: &mut TypeFormatter); +} + +impl FormatType for TypePlaceholderId { + fn format_type(&self, buffer: &mut TypeFormatter) { + if let Some(value) = self.value(buffer.db) { + value.format_type(buffer); + } else { + buffer.write("?"); + } + } +} + +impl TypeParameterId { + fn format_type_without_argument(&self, buffer: &mut TypeFormatter) { + let param = self.get(buffer.db); + + buffer.write(¶m.name); + + if param.mutable { + buffer.write(": mut"); + } + } +} + +impl FormatType for TypeParameterId { + fn format_type(&self, buffer: &mut TypeFormatter) { + // Formatting type parameters is a bit tricky, as they may be assigned + // to themselves directly or through a placeholder. The below code isn't + // going to win any awards, but it should ensure we don't blow the stack + // when trying to format recursive type parameters, such as + // `T -> placeholder -> T`. + + if let Some(arg) = buffer.type_arguments.and_then(|a| a.get(*self)) { + if let TypeRef::Placeholder(p) = arg { + match p.value(buffer.db) { + Some(t) if t.as_type_parameter() == Some(*self) => { + self.format_type_without_argument(buffer) + } + Some(t) => t.format_type(buffer), + None => self.format_type_without_argument(buffer), + } + + return; + } + + if arg.as_type_parameter() == Some(*self) { + self.format_type_without_argument(buffer); + return; + } + + arg.format_type(buffer); + } else { + self.format_type_without_argument(buffer); + }; + } +} + +impl FormatType for TraitId { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.write(&self.get(buffer.db).name); + } +} + +impl FormatType for TraitInstance { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.descend(|buffer| { + let ins_of = self.instance_of.get(buffer.db); + + buffer.write(&ins_of.name); + + if ins_of.type_parameters.len() > 0 { + let params = ins_of.type_parameters.values(); + let args = self.type_arguments(buffer.db); + + buffer.write("["); + buffer.type_arguments(params, args); + buffer.write("]"); + } + }); + } +} + +impl FormatType for ClassId { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.write(&self.get(buffer.db).name); + } +} + +impl FormatType for ClassInstance { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.descend(|buffer| { + let ins_of = self.instance_of.get(buffer.db); + + if ins_of.kind != ClassKind::Tuple { + buffer.write(&ins_of.name); + } + + if ins_of.type_parameters.len() > 0 { + let (open, close) = if ins_of.kind == ClassKind::Tuple { + ("(", ")") + } else { + ("[", "]") + }; + + let params = ins_of.type_parameters.values(); + let args = self.type_arguments(buffer.db); + + buffer.write(open); + buffer.type_arguments(params, args); + buffer.write(close); + } + }); + } +} + +impl FormatType for MethodId { + fn format_type(&self, buffer: &mut TypeFormatter) { + let block = self.get(buffer.db); + + buffer.write("fn "); + + if block.visibility == Visibility::Public { + buffer.write("pub "); + } + + match block.kind { + MethodKind::Async => buffer.write("async "), + MethodKind::AsyncMutable => buffer.write("async mut "), + MethodKind::Static => buffer.write("static "), + MethodKind::Moving => buffer.write("move "), + MethodKind::Mutable | MethodKind::Destructor => { + buffer.write("mut ") + } + _ => {} + } + + buffer.write(&block.name); + + if block.type_parameters.len() > 0 { + buffer.write(" ["); + + for (index, param) in + block.type_parameters.values().iter().enumerate() + { + if index > 0 { + buffer.write(", "); + } + + param.format_type(buffer); + } + + buffer.write("]"); + } + + buffer.arguments(&block.arguments, true); + buffer.return_type(block.return_type); + } +} + +impl FormatType for ModuleId { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.write(&self.get(buffer.db).name.to_string()); + } +} + +impl FormatType for ClosureId { + fn format_type(&self, buffer: &mut TypeFormatter) { + buffer.descend(|buffer| { + let fun = self.get(buffer.db); + + if fun.moving { + buffer.write("fn move"); + } else { + buffer.write("fn"); + } + + buffer.arguments(&fun.arguments, false); + buffer.return_type(fun.return_type); + }); + } +} + +impl FormatType for TypeRef { + fn format_type(&self, buffer: &mut TypeFormatter) { + match self { + TypeRef::Owned(id) => id.format_type(buffer), + TypeRef::Infer(id) => id.format_type(buffer), + TypeRef::Uni(id) => { + buffer.write_ownership("uni "); + id.format_type(buffer); + } + TypeRef::RefUni(id) => { + buffer.write_ownership("ref uni "); + id.format_type(buffer); + } + TypeRef::MutUni(id) => { + buffer.write_ownership("mut uni "); + id.format_type(buffer); + } + TypeRef::Ref(id) => { + buffer.write_ownership("ref "); + id.format_type(buffer); + } + TypeRef::Mut(id) => { + buffer.write_ownership("mut "); + id.format_type(buffer); + } + TypeRef::Never => buffer.write("Never"), + TypeRef::Any => buffer.write("Any"), + TypeRef::RefAny => buffer.write("ref Any"), + TypeRef::Error => buffer.write(""), + TypeRef::Unknown => buffer.write(""), + TypeRef::Placeholder(id) => id.format_type(buffer), + }; + } +} + +impl FormatType for TypeId { + fn format_type(&self, buffer: &mut TypeFormatter) { + match self { + TypeId::Class(id) => id.format_type(buffer), + TypeId::Trait(id) => id.format_type(buffer), + TypeId::Module(id) => id.format_type(buffer), + TypeId::ClassInstance(ins) => ins.format_type(buffer), + TypeId::TraitInstance(id) => id.format_type(buffer), + TypeId::TypeParameter(id) => id.format_type(buffer), + TypeId::RigidTypeParameter(id) => { + id.format_type_without_argument(buffer); + } + TypeId::Closure(id) => id.format_type(buffer), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + Block, Class, ClassInstance, ClassKind, Closure, Database, Method, + MethodKind, Module, ModuleId, ModuleName, Trait, TraitInstance, + TypeArguments, TypeId, TypeParameter, TypeRef, Visibility, + }; + + #[test] + fn test_trait_instance_format_type_with_regular_trait() { + let mut db = Database::new(); + let trait_id = Trait::alloc( + &mut db, + "A".to_string(), + ModuleId(0), + Visibility::Private, + ); + let trait_ins = TraitInstance::new(trait_id); + + assert_eq!(format_type(&db, trait_ins), "A".to_string()); + } + + #[test] + fn test_trait_instance_format_type_with_generic_trait() { + let mut db = Database::new(); + let trait_id = Trait::alloc( + &mut db, + "ToString".to_string(), + ModuleId(0), + Visibility::Private, + ); + let param1 = trait_id.new_type_parameter(&mut db, "A".to_string()); + + trait_id.new_type_parameter(&mut db, "B".to_string()); + + let mut targs = TypeArguments::new(); + + targs.assign(param1, TypeRef::Any); + + let trait_ins = TraitInstance::generic(&mut db, trait_id, targs); + + assert_eq!(format_type(&db, trait_ins), "ToString[Any, B]"); + } + + #[test] + fn test_method_id_format_type_with_instance_method() { + let mut db = Database::new(); + let class_a = Class::alloc( + &mut db, + "A".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let class_b = Class::alloc( + &mut db, + "B".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let class_d = Class::alloc( + &mut db, + "D".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let block = Method::alloc( + &mut db, + ModuleId(0), + "foo".to_string(), + Visibility::Private, + MethodKind::Instance, + ); + + let ins_a = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_a))); + + let ins_b = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_b))); + + let ins_d = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_d))); + + block.new_argument(&mut db, "a".to_string(), ins_a, ins_a); + block.new_argument(&mut db, "b".to_string(), ins_b, ins_b); + block.set_return_type(&mut db, ins_d); + + assert_eq!(format_type(&db, block), "fn foo (a: A, b: B) -> D"); + } + + #[test] + fn test_method_id_format_type_with_moving_method() { + let mut db = Database::new(); + let block = Method::alloc( + &mut db, + ModuleId(0), + "foo".to_string(), + Visibility::Private, + MethodKind::Moving, + ); + + block.set_return_type(&mut db, TypeRef::Any); + + assert_eq!(format_type(&db, block), "fn move foo -> Any"); + } + + #[test] + fn test_method_id_format_type_with_type_parameters() { + let mut db = Database::new(); + let block = Method::alloc( + &mut db, + ModuleId(0), + "foo".to_string(), + Visibility::Private, + MethodKind::Static, + ); + + block.new_type_parameter(&mut db, "A".to_string()); + block.new_type_parameter(&mut db, "B".to_string()); + block.set_return_type(&mut db, TypeRef::Any); + + assert_eq!(format_type(&db, block), "fn static foo [A, B] -> Any"); + } + + #[test] + fn test_method_id_format_type_with_static_method() { + let mut db = Database::new(); + let block = Method::alloc( + &mut db, + ModuleId(0), + "foo".to_string(), + Visibility::Private, + MethodKind::Static, + ); + + block.new_argument( + &mut db, + "a".to_string(), + TypeRef::Any, + TypeRef::Any, + ); + block.set_return_type(&mut db, TypeRef::Any); + + assert_eq!(format_type(&db, block), "fn static foo (a: Any) -> Any"); + } + + #[test] + fn test_method_id_format_type_with_async_method() { + let mut db = Database::new(); + let block = Method::alloc( + &mut db, + ModuleId(0), + "foo".to_string(), + Visibility::Private, + MethodKind::Async, + ); + + block.new_argument( + &mut db, + "a".to_string(), + TypeRef::Any, + TypeRef::Any, + ); + block.set_return_type(&mut db, TypeRef::Any); + + assert_eq!(format_type(&db, block), "fn async foo (a: Any) -> Any"); + } + + #[test] + fn test_closure_id_format_type_never_returns() { + let mut db = Database::new(); + let block = Closure::alloc(&mut db, false); + + block.set_return_type(&mut db, TypeRef::Never); + + assert_eq!(format_type(&db, block), "fn -> Never"); + } + + #[test] + fn test_type_id_format_type_with_class() { + let mut db = Database::new(); + let id = TypeId::Class(Class::alloc( + &mut db, + "String".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + )); + + assert_eq!(format_type(&db, id), "String"); + } + + #[test] + fn test_type_id_format_type_with_trait() { + let mut db = Database::new(); + let id = TypeId::Trait(Trait::alloc( + &mut db, + "ToString".to_string(), + ModuleId(0), + Visibility::Private, + )); + + assert_eq!(format_type(&db, id), "ToString"); + } + + #[test] + fn test_type_id_format_type_with_module() { + let mut db = Database::new(); + let id = TypeId::Module(Module::alloc( + &mut db, + ModuleName::new("foo::bar"), + "foo/bar.inko".into(), + )); + + assert_eq!(format_type(&db, id), "foo::bar"); + } + + #[test] + fn test_type_id_format_type_with_class_instance() { + let mut db = Database::new(); + let id = Class::alloc( + &mut db, + "String".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let ins = TypeId::ClassInstance(ClassInstance::new(id)); + + assert_eq!(format_type(&db, ins), "String"); + } + + #[test] + fn test_type_id_format_type_with_tuple_instance() { + let mut db = Database::new(); + let id = Class::alloc( + &mut db, + "MyTuple".to_string(), + ClassKind::Tuple, + Visibility::Private, + ModuleId(0), + ); + let param1 = id.new_type_parameter(&mut db, "A".to_string()); + let param2 = id.new_type_parameter(&mut db, "B".to_string()); + let mut args = TypeArguments::new(); + + args.assign(param1, TypeRef::Any); + args.assign(param2, TypeRef::Never); + + let ins = + TypeId::ClassInstance(ClassInstance::generic(&mut db, id, args)); + + assert_eq!(format_type(&db, ins), "(Any, Never)"); + } + + #[test] + fn test_type_id_format_type_with_trait_instance() { + let mut db = Database::new(); + let id = Trait::alloc( + &mut db, + "ToString".to_string(), + ModuleId(0), + Visibility::Private, + ); + let ins = TypeId::TraitInstance(TraitInstance::new(id)); + + assert_eq!(format_type(&db, ins), "ToString"); + } + + #[test] + fn test_type_id_format_type_with_generic_class_instance() { + let mut db = Database::new(); + let id = Class::alloc( + &mut db, + "Thing".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let param1 = id.new_type_parameter(&mut db, "T".to_string()); + + id.new_type_parameter(&mut db, "E".to_string()); + + let mut targs = TypeArguments::new(); + + targs.assign(param1, TypeRef::Any); + + let ins = + TypeId::ClassInstance(ClassInstance::generic(&mut db, id, targs)); + + assert_eq!(format_type(&db, ins), "Thing[Any, E]"); + } + + #[test] + fn test_type_id_format_type_with_generic_trait_instance() { + let mut db = Database::new(); + let id = Trait::alloc( + &mut db, + "ToFoo".to_string(), + ModuleId(0), + Visibility::Private, + ); + let param1 = id.new_type_parameter(&mut db, "T".to_string()); + + id.new_type_parameter(&mut db, "E".to_string()); + + let mut targs = TypeArguments::new(); + + targs.assign(param1, TypeRef::Any); + + let ins = + TypeId::TraitInstance(TraitInstance::generic(&mut db, id, targs)); + + assert_eq!(format_type(&db, ins), "ToFoo[Any, E]"); + } + + #[test] + fn test_type_id_format_type_with_type_parameter() { + let mut db = Database::new(); + let param = TypeParameter::alloc(&mut db, "T".to_string()); + let to_string = Trait::alloc( + &mut db, + "ToString".to_string(), + ModuleId(0), + Visibility::Private, + ); + let param_ins = TypeId::TypeParameter(param); + let to_string_ins = TraitInstance::new(to_string); + + param.add_requirements(&mut db, vec![to_string_ins]); + + assert_eq!(format_type(&db, param_ins), "T"); + } + + #[test] + fn test_type_id_format_type_with_rigid_type_parameter() { + let mut db = Database::new(); + let param = TypeParameter::alloc(&mut db, "T".to_string()); + let to_string = Trait::alloc( + &mut db, + "ToString".to_string(), + ModuleId(0), + Visibility::Private, + ); + let param_ins = TypeId::RigidTypeParameter(param); + let to_string_ins = TraitInstance::new(to_string); + + param.add_requirements(&mut db, vec![to_string_ins]); + + assert_eq!(format_type(&db, param_ins), "T"); + } + + #[test] + fn test_type_id_format_type_with_closure() { + let mut db = Database::new(); + let class_a = Class::alloc( + &mut db, + "A".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let class_b = Class::alloc( + &mut db, + "B".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let class_d = Class::alloc( + &mut db, + "D".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let block = Closure::alloc(&mut db, true); + + let ins_a = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_a))); + + let ins_b = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_b))); + + let ins_d = + TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_d))); + + block.new_argument(&mut db, "a".to_string(), ins_a, ins_a); + block.new_argument(&mut db, "b".to_string(), ins_b, ins_b); + block.set_return_type(&mut db, ins_d); + + let block_ins = TypeId::Closure(block); + + assert_eq!(format_type(&db, block_ins), "fn move (A, B) -> D"); + } + + #[test] + fn test_type_ref_type_name() { + let mut db = Database::new(); + let string = Class::alloc( + &mut db, + "String".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + ); + let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); + let param = TypeId::TypeParameter(TypeParameter::alloc( + &mut db, + "T".to_string(), + )); + + assert_eq!( + format_type(&db, TypeRef::Owned(string_ins)), + "String".to_string() + ); + assert_eq!(format_type(&db, TypeRef::Infer(param)), "T".to_string()); + assert_eq!( + format_type(&db, TypeRef::Ref(string_ins)), + "ref String".to_string() + ); + assert_eq!(format_type(&db, TypeRef::Never), "Never".to_string()); + assert_eq!(format_type(&db, TypeRef::Any), "Any".to_string()); + assert_eq!(format_type(&db, TypeRef::Error), "".to_string()); + assert_eq!(format_type(&db, TypeRef::Unknown), "".to_string()); + } +} diff --git a/types/src/lib.rs b/types/src/lib.rs index 02ab679c2..9674731d7 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -2,24 +2,33 @@ #![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::len_without_is_empty))] +#[cfg(test)] +pub mod test; + +pub mod check; pub mod collections; +pub mod either; +pub mod format; pub mod module_name; +pub mod resolve; use crate::collections::IndexMap; use crate::module_name::ModuleName; -use bytecode::{BuiltinFunction as BIF, Opcode}; +use crate::resolve::TypeResolver; use std::cell::Cell; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; +// The IDs of these built-in types must match the order of the fields in the +// State type. pub const INT_ID: u32 = 0; pub const FLOAT_ID: u32 = 1; pub const STRING_ID: u32 = 2; -const ARRAY_ID: u32 = 3; +pub const ARRAY_ID: u32 = 3; pub const BOOLEAN_ID: u32 = 4; -const NIL_ID: u32 = 5; -const BYTE_ARRAY_ID: u32 = 6; -const FUTURE_ID: u32 = 7; +pub const NIL_ID: u32 = 5; +pub const BYTE_ARRAY_ID: u32 = 6; +pub const CHANNEL_ID: u32 = 7; const TUPLE1_ID: u32 = 8; const TUPLE2_ID: u32 = 9; @@ -29,8 +38,9 @@ const TUPLE5_ID: u32 = 12; const TUPLE6_ID: u32 = 13; const TUPLE7_ID: u32 = 14; const TUPLE8_ID: u32 = 15; +const RESULT_ID: u32 = 16; -pub const FIRST_USER_CLASS_ID: u32 = TUPLE8_ID + 1; +pub const FIRST_USER_CLASS_ID: u32 = RESULT_ID + 1; /// The default module ID to assign to builtin types. /// @@ -44,8 +54,7 @@ const ARRAY_NAME: &str = "Array"; const BOOLEAN_NAME: &str = "Bool"; const NIL_NAME: &str = "Nil"; const BYTE_ARRAY_NAME: &str = "ByteArray"; -const FUTURE_NAME: &str = "Future"; - +const CHANNEL_NAME: &str = "Channel"; const TUPLE1_NAME: &str = "Tuple1"; const TUPLE2_NAME: &str = "Tuple2"; const TUPLE3_NAME: &str = "Tuple3"; @@ -54,25 +63,27 @@ const TUPLE5_NAME: &str = "Tuple5"; const TUPLE6_NAME: &str = "Tuple6"; const TUPLE7_NAME: &str = "Tuple7"; const TUPLE8_NAME: &str = "Tuple8"; +const RESULT_NAME: &str = "Result"; pub const STRING_MODULE: &str = "std::string"; pub const TO_STRING_TRAIT: &str = "ToString"; pub const TO_STRING_METHOD: &str = "to_string"; - pub const CALL_METHOD: &str = "call"; pub const MAIN_CLASS: &str = "Main"; pub const MAIN_METHOD: &str = "main"; - pub const DROP_MODULE: &str = "std::drop"; pub const DROP_TRAIT: &str = "Drop"; - pub const DROP_METHOD: &str = "drop"; pub const DROPPER_METHOD: &str = "$dropper"; pub const ASYNC_DROPPER_METHOD: &str = "$async_dropper"; - -pub const CLONE_MODULE: &str = "std::clone"; -pub const CLONE_TRAIT: &str = "Clone"; -pub const CLONE_METHOD: &str = "clone"; +pub const OPTION_MODULE: &str = "std::option"; +pub const OPTION_CLASS: &str = "Option"; +pub const RESULT_MODULE: &str = "std::result"; +pub const RESULT_CLASS: &str = "Result"; +pub const OPTION_SOME: &str = "Some"; +pub const OPTION_NONE: &str = "None"; +pub const RESULT_OK: &str = "Ok"; +pub const RESULT_ERROR: &str = "Error"; pub const ENUM_TAG_FIELD: &str = "tag"; pub const ENUM_TAG_INDEX: usize = 0; @@ -83,205 +94,27 @@ pub const VARIANTS_LIMIT: usize = u16::MAX as usize; /// The maximum number of fields a class can define. pub const FIELDS_LIMIT: usize = u8::MAX as usize; -const MAX_FORMATTING_DEPTH: usize = 8; - -/// The maximum recursion/depth to restrict ourselves to when inferring types or -/// checking if they are inferred. +/// A type inference placeholder. /// -/// In certain cases we may end up with cyclic types, where the cycles are -/// non-trivial (e.g. `A -> B -> C -> D -> A`). To prevent runaway recursion we -/// limit such operations to a certain depth. +/// A type placeholder reprents a value of which the exact type isn't +/// immediately known, and is to be inferred based on how the value is used. +/// Take this code for example: /// -/// The depth here is sufficiently large that no sane program should run into -/// it, but we also won't blow the stack. -const MAX_TYPE_DEPTH: usize = 64; - -pub fn format_type(db: &Database, typ: T) -> String { - TypeFormatter::new(db, None, None).format(typ) -} - -pub fn format_type_with_self( - db: &Database, - self_type: TypeId, - typ: T, -) -> String { - TypeFormatter::new(db, Some(self_type), None).format(typ) -} - -pub fn format_type_with_context( - db: &Database, - context: &TypeContext, - typ: T, -) -> String { - TypeFormatter::new( - db, - Some(context.self_type), - Some(&context.type_arguments), - ) - .format(typ) -} - -#[derive(Copy, Clone)] -pub enum CompilerMacro { - FutureGet, - FutureGetFor, - StringClone, - Moved, - PanicThrown, - Strings, -} - -impl CompilerMacro { - pub fn name(self) -> &'static str { - match self { - CompilerMacro::FutureGet => "future_get", - CompilerMacro::FutureGetFor => "future_get_for", - CompilerMacro::StringClone => "string_clone", - CompilerMacro::Moved => "moved", - CompilerMacro::PanicThrown => "panic_thrown", - CompilerMacro::Strings => "strings", - } - } -} - -/// A buffer for formatting type names. +/// let vals = [] /// -/// We use a simple wrapper around a String so we can more easily change the -/// implementation in the future if necessary. -pub struct TypeFormatter<'a> { - db: &'a Database, - self_type: Option, - type_arguments: Option<&'a TypeArguments>, - buffer: String, - depth: usize, -} - -impl<'a> TypeFormatter<'a> { - pub fn new( - db: &'a Database, - self_type: Option, - type_arguments: Option<&'a TypeArguments>, - ) -> Self { - Self { db, self_type, type_arguments, buffer: String::new(), depth: 0 } - } - - pub fn format(mut self, typ: T) -> String { - typ.format_type(&mut self); - self.buffer - } - - fn descend(&mut self, block: F) { - if self.depth == MAX_FORMATTING_DEPTH { - self.write("..."); - } else { - self.depth += 1; - - block(self); - - self.depth -= 1; - } - } - - fn write(&mut self, thing: &str) { - self.buffer.push_str(thing); - } - - /// If a uni/ref/mut value wraps a type parameter, and that parameter is - /// assigned another value with ownership, you can end up with e.g. - /// `ref mut T` or `uni uni T`. This method provides a simple way of - /// preventing this from happening, without complicating the type formatting - /// process. - fn write_ownership(&mut self, thing: &str) { - if !self.buffer.ends_with(thing) { - self.write(thing); - } - } - - fn type_arguments( - &mut self, - parameters: &[TypeParameterId], - arguments: &TypeArguments, - ) { - for (index, ¶m) in parameters.iter().enumerate() { - if index > 0 { - self.write(", "); - } - - match arguments.get(param) { - Some(TypeRef::Placeholder(id)) - if id.value(self.db).is_none() => - { - // Placeholders without values aren't useful to show to the - // developer, so we show the type parameter instead. - // - // The parameter itself may be assigned a value through the - // type context (e.g. when a type is nested such as - // `Array[Array[T]]`), and we don't want to display that - // assignment as it's only to be used for the outer most - // type. As such, we don't use format_type() here. - param.format_type_without_argument(self); - } - Some(typ) => typ.format_type(self), - _ => param.format_type(self), - } - } - } - - fn arguments(&mut self, arguments: &Arguments, include_name: bool) { - if arguments.len() == 0 { - return; - } - - self.write(" ("); - - for (index, arg) in arguments.iter().enumerate() { - if index > 0 { - self.write(", "); - } - - if include_name { - self.write(&arg.name); - self.write(": "); - } - - arg.value_type.format_type(self); - } - - self.write(")"); - } - - fn throw_type(&mut self, typ: TypeRef) { - if typ.is_never(self.db) { - return; - } - - match typ { - TypeRef::Placeholder(id) if id.value(self.db).is_none() => {} - _ => { - self.write(" !! "); - typ.format_type(self); - } - } - } - - fn return_type(&mut self, typ: TypeRef) { - match typ { - TypeRef::Placeholder(id) if id.value(self.db).is_none() => {} - _ if typ == TypeRef::nil() => {} - _ => { - self.write(" -> "); - typ.format_type(self); - } - } - } -} - -/// A type of which the name can be formatted into something human-readable. -pub trait FormatType { - fn format_type(&self, buffer: &mut TypeFormatter); -} - -/// A placeholder for a type that has yet to be inferred. +/// While we know that `vals` is an array, we don't know the type of the values +/// in the array. In this case we use a type placeholder, meaning that `vals` is +/// of type `Array[V₁]` where V₁ is a type placeholder. +/// +/// At some point we may push a value into the array, for example: +/// +/// vals.push(42) +/// +/// In this case V₁ is assigned to `Int`, and we end up with `vals` inferred as +/// `Array[Int]`. +/// +/// The concept of type placeholder is taken from the Hindley-Milner type +/// system. pub struct TypePlaceholder { /// The value assigned to this placeholder. /// @@ -292,24 +125,19 @@ pub struct TypePlaceholder { /// fields). value: Cell, - /// When `self` is assigned a value, these placeholders are assigned the - /// same value. - /// - /// One place where this is needed is array literals with multiple values: - /// only the first placeholder/value is stored in the Array type, so further - /// inferring of that type doesn't affect values at index 1, 2, etc. By - /// recording `ours` here, updating `id` also updates `ours`, without - /// introducing a placeholder cycle. - depending: Vec, + /// The type parameter a type must be compatible with before it can be + /// assigned to this type variable. + required: Option, } impl TypePlaceholder { - fn alloc(db: &mut Database) -> TypePlaceholderId { + fn alloc( + db: &mut Database, + required: Option, + ) -> TypePlaceholderId { let id = db.type_placeholders.len(); - let typ = TypePlaceholder { - value: Cell::new(TypeRef::Unknown), - depending: Vec::new(), - }; + let typ = + TypePlaceholder { value: Cell::new(TypeRef::Unknown), required }; db.type_placeholders.push(typ); TypePlaceholderId(id) @@ -317,23 +145,30 @@ impl TypePlaceholder { } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub struct TypePlaceholderId(usize); +pub struct TypePlaceholderId(pub(crate) usize); impl TypePlaceholderId { pub fn value(self, db: &Database) -> Option { - match self.get(db).value.get() { + // Chains of type variables are very rare in practise, but they _can_ + // occur and thus must be handled. Because they are so rare and unlikely + // to be more than 2-3 levels deep, we just use recursion here instead + // of a loop. + let typ = self.get(db).value.get(); + + match typ { + TypeRef::Placeholder(id) => id.value(db), TypeRef::Unknown => None, - value => Some(value), + _ => Some(typ), } } - fn add_depending(self, db: &mut Database, placeholder: TypePlaceholderId) { - self.get_mut(db).depending.push(placeholder); + fn required(self, db: &Database) -> Option { + self.get(db).required } fn assign(self, db: &Database, value: TypeRef) { - // Assigning placeholders to themselves creates cycles that aren't - // useful, so we ignore those. + // Assigning placeholders to themselves isn't useful and results in + // resolve() getting stuck. if let TypeRef::Placeholder(id) = value { if id.0 == self.0 { return; @@ -341,92 +176,15 @@ impl TypePlaceholderId { } self.get(db).value.set(value); - - for &id in &self.get(db).depending { - id.get(db).value.set(value); - } } fn get(self, db: &Database) -> &TypePlaceholder { &db.type_placeholders[self.0] } - - fn get_mut(self, db: &mut Database) -> &mut TypePlaceholder { - &mut db.type_placeholders[self.0] - } -} - -impl FormatType for TypePlaceholderId { - fn format_type(&self, buffer: &mut TypeFormatter) { - if let Some(value) = self.value(buffer.db) { - value.format_type(buffer); - } else { - buffer.write("?"); - } - } -} - -/// A collection of values needed when checking and substituting types. -#[derive(Clone)] -pub struct TypeContext { - /// The type of `Self`. - /// - /// This isn't the same type as `self`: `Self` is a new instance of a type, - /// whereas `self` is the receiver. Consider this example: - /// - /// class A { - /// fn foo -> Self {} - /// } - /// - /// Within `foo`, the type of `self` is `ref A`, but the type of `Self` is - /// `A`. - pub self_type: TypeId, - - /// The type arguments available to this context. - /// - /// When type-checking a method call, this table contains the type - /// parameters and values of both the receiver and the method itself. - pub type_arguments: TypeArguments, - - /// The nesting/recursion depth when e.g. inferring a type. - /// - /// This value is used to prevent runaway recursion that can occur when - /// dealing with (complex) cyclic types. - depth: usize, -} - -impl TypeContext { - pub fn new(self_type_id: TypeId) -> Self { - Self { - self_type: self_type_id, - type_arguments: TypeArguments::new(), - depth: 0, - } - } - - pub fn for_class_instance( - db: &Database, - self_type: TypeId, - instance: ClassInstance, - ) -> Self { - let type_arguments = if instance.instance_of().is_generic(db) { - instance.type_arguments(db).clone() - } else { - TypeArguments::new() - }; - - Self { self_type, type_arguments, depth: 0 } - } - - pub fn with_arguments( - self_type_id: TypeId, - type_arguments: TypeArguments, - ) -> Self { - Self { self_type: self_type_id, type_arguments, depth: 0 } - } } /// A type parameter for a method or class. +#[derive(Clone)] pub struct TypeParameter { /// The name of the type parameter. name: String, @@ -434,24 +192,34 @@ pub struct TypeParameter { /// The traits that must be implemented before a type can be assigned to /// this type parameter. requirements: Vec, + + /// If mutable references to this type parameter are allowed. + mutable: bool, + + /// The ID of the original type parameter in case the current one is a + /// parameter introduced through additional type bounds. + original: Option, } impl TypeParameter { pub fn alloc(db: &mut Database, name: String) -> TypeParameterId { + TypeParameter::add(db, TypeParameter::new(name)) + } + + fn add(db: &mut Database, parameter: TypeParameter) -> TypeParameterId { let id = db.type_parameters.len(); - let typ = TypeParameter::new(name); - db.type_parameters.push(typ); + db.type_parameters.push(parameter); TypeParameterId(id) } fn new(name: String) -> Self { - Self { name, requirements: Vec::new() } + Self { name, requirements: Vec::new(), mutable: false, original: None } } } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub struct TypeParameterId(usize); +pub struct TypeParameterId(pub usize); impl TypeParameterId { pub fn name(self, db: &Database) -> &String { @@ -482,126 +250,44 @@ impl TypeParameterId { None } - fn all_requirements_met( - self, - db: &mut Database, - mut func: impl FnMut(&mut Database, TraitInstance) -> bool, - ) -> bool { - self.get(db).requirements.clone().into_iter().all(|r| func(db, r)) + pub fn set_original(self, db: &mut Database, parameter: TypeParameterId) { + self.get_mut(db).original = Some(parameter); } - fn get(self, db: &Database) -> &TypeParameter { - &db.type_parameters[self.0] + pub fn original(self, db: &Database) -> Option { + self.get(db).original } - fn get_mut(self, db: &mut Database) -> &mut TypeParameter { - &mut db.type_parameters[self.0] + pub fn set_mutable(self, db: &mut Database) { + self.get_mut(db).mutable = true; } - fn type_check( - self, - db: &mut Database, - with: TypeId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - match with { - TypeId::TraitInstance(theirs) => self - .type_check_with_trait_instance(db, theirs, context, subtyping), - TypeId::TypeParameter(theirs) => self - .type_check_with_type_parameter(db, theirs, context, subtyping), - _ => false, - } + pub fn is_mutable(self, db: &Database) -> bool { + self.get(db).mutable } - fn type_check_with_type_parameter( - self, - db: &mut Database, - with: TypeParameterId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - with.all_requirements_met(db, |db, req| { - self.type_check_with_trait_instance(db, req, context, subtyping) - }) + pub fn as_immutable(self, db: &mut Database) -> TypeParameterId { + let mut copy = self.get(db).clone(); + + copy.mutable = false; + TypeParameter::add(db, copy) } - fn type_check_with_trait_instance( - self, - db: &mut Database, - instance: TraitInstance, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - self.get(db).requirements.clone().into_iter().any(|req| { - req.type_check_with_trait_instance( - db, instance, None, context, subtyping, - ) - }) + fn get(self, db: &Database) -> &TypeParameter { + &db.type_parameters[self.0] } - fn as_rigid_type(self, bounds: &TypeBounds) -> TypeId { - TypeId::RigidTypeParameter(bounds.get(self).unwrap_or(self)) + fn get_mut(self, db: &mut Database) -> &mut TypeParameter { + &mut db.type_parameters[self.0] } fn as_owned_rigid(self) -> TypeRef { TypeRef::Owned(TypeId::RigidTypeParameter(self)) } - - fn format_type_without_argument(&self, buffer: &mut TypeFormatter) { - let param = self.get(buffer.db); - - buffer.write(¶m.name); - - if !param.requirements.is_empty() { - buffer.write(": "); - - for (index, req) in param.requirements.iter().enumerate() { - if index > 0 { - buffer.write(" + "); - } - - req.format_type(buffer); - } - } - } -} - -impl FormatType for TypeParameterId { - fn format_type(&self, buffer: &mut TypeFormatter) { - // Formatting type parameters is a bit tricky, as they may be assigned - // to themselves directly or through a placeholder. The below code isn't - // going to win any awards, but it should ensure we don't blow the stack - // when trying to format recursive type parameters, such as - // `T -> placeholder -> T`. - - if let Some(arg) = buffer.type_arguments.and_then(|a| a.get(*self)) { - if let TypeRef::Placeholder(p) = arg { - match p.value(buffer.db) { - Some(t) if t.as_type_parameter() == Some(*self) => { - self.format_type_without_argument(buffer) - } - Some(t) => t.format_type(buffer), - None => self.format_type_without_argument(buffer), - } - - return; - } - - if arg.as_type_parameter() == Some(*self) { - self.format_type_without_argument(buffer); - return; - } - - arg.format_type(buffer); - } else { - self.format_type_without_argument(buffer); - }; - } } /// Type parameters and the types assigned to them. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct TypeArguments { /// We use a HashMap as parameters can be assigned in any order, and some /// may not be assigned at all. @@ -609,14 +295,20 @@ pub struct TypeArguments { } impl TypeArguments { - fn rigid(db: &mut Database, index: u32, bounds: &TypeBounds) -> Self { - let mut new_args = Self::new(); - - for (param, value) in db.type_arguments[index as usize].pairs() { - new_args.assign(param, value.as_rigid_type(db, bounds)); + pub fn for_class(db: &Database, instance: ClassInstance) -> TypeArguments { + if instance.instance_of().is_generic(db) { + instance.type_arguments(db).clone() + } else { + TypeArguments::new() } + } - new_args + pub fn for_trait(db: &Database, instance: TraitInstance) -> TypeArguments { + if instance.instance_of().is_generic(db) { + instance.type_arguments(db).clone() + } else { + TypeArguments::new() + } } pub fn new() -> Self { @@ -658,24 +350,6 @@ impl TypeArguments { } } } - - fn assigned_or_placeholders( - &self, - db: &mut Database, - parameters: Vec, - ) -> Self { - let mut new_args = Self::new(); - - for param in parameters { - if let Some(val) = self.get(param) { - new_args.assign(param, val); - } else { - new_args.assign(param, TypeRef::placeholder(db)); - } - } - - new_args - } } /// An Inko trait. @@ -800,6 +474,10 @@ impl TraitId { self_typ.required_traits.push(requirement); } + pub fn implemented_by(self, db: &Database) -> &Vec { + &self.get(db).implemented_by + } + pub fn method_exists(self, db: &Database, name: &str) -> bool { self.get(db).default_methods.contains_key(name) || self.get(db).required_methods.contains_key(name) @@ -894,12 +572,6 @@ impl TraitId { } } -impl FormatType for TraitId { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.write(&self.get(buffer.db).name); - } -} - /// An instance of a trait, along with its type arguments in case the trait is /// generic. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -916,9 +588,11 @@ pub struct TraitInstance { } impl TraitInstance { - /// Returns an instance to use as the type of `Self` in required and default - /// methods. - pub fn for_self_type( + pub fn new(instance_of: TraitId) -> Self { + Self { instance_of, type_arguments: 0 } + } + + pub fn rigid( db: &mut Database, instance_of: TraitId, bounds: &TypeBounds, @@ -939,10 +613,6 @@ impl TraitInstance { } } - pub fn new(instance_of: TraitId) -> Self { - Self { instance_of, type_arguments: 0 } - } - pub fn generic( db: &mut Database, instance_of: TraitId, @@ -995,180 +665,19 @@ impl TraitInstance { self.instance_of.method(db, name) } - fn type_check( - self, - db: &mut Database, - with: TypeId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - match with { - TypeId::TraitInstance(ins) => self.type_check_with_trait_instance( - db, ins, None, context, subtyping, - ), - TypeId::TypeParameter(id) => { - id.all_requirements_met(db, |db, req| { - self.type_check_with_trait_instance( - db, req, None, context, subtyping, - ) - }) - } - _ => false, - } - } - - fn type_check_with_trait_instance( - self, - db: &mut Database, - instance: TraitInstance, - arguments: Option<&TypeArguments>, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - if self.instance_of != instance.instance_of { - return if subtyping { - self.instance_of - .get(db) - .required_traits - .clone() - .into_iter() - .any(|req| { - req.type_check_with_trait_instance( - db, instance, None, context, subtyping, - ) - }) - } else { - false - }; - } - - let our_trait = self.instance_of.get(db); - - if !our_trait.is_generic() { - return true; - } - - let our_args = self.type_arguments(db).clone(); - let their_args = instance.type_arguments(db).clone(); - - // If additional type arguments are given (e.g. when comparing a generic - // class instance to a trait), we need to remap the implementation - // arguments accordingly. This way if `Box[T]` implements `Iter[T]`, for - // a `Box[Int]` we produce a `Iter[Int]` rather than an `Iter[T]`. - if let Some(args) = arguments { - our_trait.type_parameters.values().clone().into_iter().all( - |param| { - our_args - .get(param) - .zip(their_args.get(param)) - .map(|(ours, theirs)| { - ours.as_type_parameter() - .and_then(|id| args.get(id)) - .unwrap_or(ours) - .type_check(db, theirs, context, subtyping) - }) - .unwrap_or(false) - }, - ) - } else { - our_trait.type_parameters.values().clone().into_iter().all( - |param| { - our_args - .get(param) - .zip(their_args.get(param)) - .map(|(ours, theirs)| { - ours.type_check(db, theirs, context, subtyping) - }) - .unwrap_or(false) - }, - ) - } - } - fn named_type(self, db: &Database, name: &str) -> Option { self.instance_of.named_type(db, name) } +} - fn implements_trait_instance( - self, - db: &mut Database, - instance: TraitInstance, - context: &mut TypeContext, - ) -> bool { - self.instance_of.get(db).required_traits.clone().into_iter().any( - |req| { - req.type_check_with_trait_instance( - db, instance, None, context, true, - ) - }, - ) - } - - fn implements_trait_id(self, db: &Database, trait_id: TraitId) -> bool { - self.instance_of - .get(db) - .required_traits - .iter() - .any(|req| req.implements_trait_id(db, trait_id)) - } - - fn as_rigid_type(self, db: &mut Database, bounds: &TypeBounds) -> Self { - if !self.instance_of.get(db).is_generic() { - return self; - } - - let new_args = TypeArguments::rigid(db, self.type_arguments, bounds); - - TraitInstance::generic(db, self.instance_of, new_args) - } - - fn inferred( - self, - db: &mut Database, - context: &mut TypeContext, - immutable: bool, - ) -> Self { - if !self.instance_of.is_generic(db) { - return self; - } - - let mut new_args = TypeArguments::new(); - - for (arg, val) in self.type_arguments(db).pairs() { - new_args.assign(arg, val.inferred(db, context, immutable)); - } - - Self::generic(db, self.instance_of, new_args) - } -} - -impl FormatType for TraitInstance { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.descend(|buffer| { - let ins_of = self.instance_of.get(buffer.db); - - buffer.write(&ins_of.name); - - if ins_of.type_parameters.len() > 0 { - let params = ins_of.type_parameters.values(); - let args = self.type_arguments(buffer.db); - - buffer.write("["); - buffer.type_arguments(params, args); - buffer.write("]"); - } - }); - } -} - -/// A field for a class. -pub struct Field { - index: usize, - name: String, - value_type: TypeRef, - visibility: Visibility, - module: ModuleId, -} +/// A field for a class. +pub struct Field { + index: usize, + name: String, + value_type: TypeRef, + visibility: Visibility, + module: ModuleId, +} impl Field { pub fn alloc( @@ -1228,17 +737,13 @@ impl FieldId { /// Additional requirements for type parameters inside a trait implementation of /// method. /// -/// Additional bounds are set using the `when` keyword like so: -/// -/// impl Debug[T] for Array when T: X { ... } -/// /// This structure maps the original type parameters (`T` in this case) to type /// parameters created for the bounds. These new type parameters have their /// requirements set to the union of the original type parameter's requirements, /// and the requirements specified in the bounds. In other words, if the /// original parameter is defined as `T: A` and the bounds specify `T: B`, this /// structure maps `T: A` to `T: A + B`. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct TypeBounds { mapping: HashMap, } @@ -1255,6 +760,38 @@ impl TypeBounds { pub fn get(&self, parameter: TypeParameterId) -> Option { self.mapping.get(¶meter).cloned() } + + pub fn iter( + &self, + ) -> impl Iterator { + self.mapping.iter() + } + + pub fn values_mut(&mut self) -> impl Iterator { + self.mapping.values_mut() + } + + pub fn is_empty(&self) -> bool { + self.mapping.is_empty() + } + + pub fn union(&self, with: &TypeBounds) -> TypeBounds { + let mut union = self.clone(); + + for (&key, &val) in &with.mapping { + union.set(key, val); + } + + union + } + + pub fn make_immutable(&mut self, db: &mut Database) { + for bound in self.mapping.values_mut() { + if bound.is_mutable(db) { + *bound = bound.as_immutable(db); + } + } + } } /// An implementation of a trait, with (optionally) additional bounds for the @@ -1324,6 +861,9 @@ pub enum ClassKind { Enum, Regular, Tuple, + Closure, + Module, + Extern, } impl ClassKind { @@ -1342,12 +882,30 @@ impl ClassKind { pub fn is_tuple(self) -> bool { matches!(self, ClassKind::Tuple) } + + pub fn is_closure(self) -> bool { + matches!(self, ClassKind::Closure) + } + + pub fn is_module(self) -> bool { + matches!(self, ClassKind::Module) + } + + pub fn is_extern(self) -> bool { + matches!(self, ClassKind::Extern) + } + + pub fn allow_pattern_matching(self) -> bool { + matches!(self, ClassKind::Regular | ClassKind::Extern) + } } /// An Inko class as declared using the `class` keyword. pub struct Class { kind: ClassKind, name: String, + atomic: bool, + value_type: bool, // A flag indicating the presence of a custom destructor. // // We store a flag for this so we can check for the presence of a destructor @@ -1390,6 +948,8 @@ impl Class { kind, visibility, destructor: false, + atomic: kind.is_async(), + value_type: matches!(kind, ClassKind::Async | ClassKind::Extern), fields: IndexMap::new(), type_parameters: IndexMap::new(), methods: HashMap::new(), @@ -1408,6 +968,40 @@ impl Class { ) } + fn external(name: String) -> Self { + Self::new( + name, + ClassKind::Extern, + Visibility::Public, + ModuleId(DEFAULT_BUILTIN_MODULE_ID), + ) + } + + fn value_type(name: String) -> Self { + let mut class = Self::new( + name, + ClassKind::Regular, + Visibility::Public, + ModuleId(DEFAULT_BUILTIN_MODULE_ID), + ); + + class.value_type = true; + class + } + + fn atomic(name: String) -> Self { + let mut class = Self::new( + name, + ClassKind::Regular, + Visibility::Public, + ModuleId(DEFAULT_BUILTIN_MODULE_ID), + ); + + class.atomic = true; + class.value_type = true; + class + } + fn tuple(name: String) -> Self { Self::new( name, @@ -1446,8 +1040,8 @@ impl ClassId { ClassId(NIL_ID) } - pub fn future() -> ClassId { - ClassId(FUTURE_ID) + pub fn channel() -> ClassId { + ClassId(CHANNEL_ID) } pub fn array() -> ClassId { @@ -1504,6 +1098,10 @@ impl ClassId { } } + pub fn result() -> ClassId { + ClassId(RESULT_ID) + } + pub fn name(self, db: &Database) -> &String { &self.get(db).name } @@ -1657,6 +1255,10 @@ impl ClassId { !self.is_public(db) } + pub fn is_atomic(self, db: &Database) -> bool { + self.get(db).atomic + } + pub fn set_module(self, db: &mut Database, module: ModuleId) { self.get_mut(db).module = module; } @@ -1692,6 +1294,10 @@ impl ClassId { self.get(db).destructor } + pub fn is_builtin(self) -> bool { + self.0 <= CHANNEL_ID + } + fn get(self, db: &Database) -> &Class { &db.classes[self.0 as usize] } @@ -1701,12 +1307,6 @@ impl ClassId { } } -impl FormatType for ClassId { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.write(&self.get(buffer.db).name); - } -} - /// An instance of a class, along with its type arguments in case the class is /// generic. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -1723,9 +1323,11 @@ pub struct ClassInstance { } impl ClassInstance { - /// Returns a class instance to use as the type of `Self` in instance - /// methods. - pub fn for_instance_self_type( + pub fn new(instance_of: ClassId) -> Self { + Self { instance_of, type_arguments: 0 } + } + + pub fn rigid( db: &mut Database, instance_of: ClassId, bounds: &TypeBounds, @@ -1746,28 +1348,6 @@ impl ClassInstance { } } - /// Returns a class instance to use as the type of `Self` in static methods. - pub fn for_static_self_type( - db: &mut Database, - instance_of: ClassId, - ) -> Self { - if instance_of.is_generic(db) { - let mut arguments = TypeArguments::new(); - - for param in instance_of.type_parameters(db) { - arguments.assign(param, param.as_owned_rigid()); - } - - Self::generic(db, instance_of, arguments) - } else { - Self::new(instance_of) - } - } - - pub fn new(instance_of: ClassId) -> Self { - Self { instance_of, type_arguments: 0 } - } - pub fn generic( db: &mut Database, instance_of: ClassId, @@ -1781,39 +1361,38 @@ impl ClassInstance { ClassInstance { instance_of, type_arguments: args_id } } - pub fn generic_with_types( + pub fn with_types( db: &mut Database, - instance_of: ClassId, - types: Vec, + class: ClassId, + arguments: Vec, ) -> Self { let mut args = TypeArguments::new(); - for (index, param) in - instance_of.type_parameters(db).into_iter().enumerate() + for (index, param) in class.type_parameters(db).into_iter().enumerate() { - args.assign( - param, - types - .get(index) - .cloned() - .unwrap_or_else(|| TypeRef::placeholder(db)), - ); + let val = arguments + .get(index) + .cloned() + .unwrap_or_else(|| TypeRef::placeholder(db, Some(param))); + + args.assign(param, val); } - Self::generic(db, instance_of, args) + Self::generic(db, class, args) } - pub fn generic_with_placeholders( - db: &mut Database, - instance_of: ClassId, - ) -> Self { + pub fn empty(db: &mut Database, class: ClassId) -> Self { + if !class.is_generic(db) { + return Self::new(class); + } + let mut args = TypeArguments::new(); - for param in instance_of.type_parameters(db) { - args.assign(param, TypeRef::placeholder(db)); + for param in class.type_parameters(db) { + args.assign(param, TypeRef::placeholder(db, Some(param))); } - Self::generic(db, instance_of, args) + Self::generic(db, class, args) } pub fn instance_of(self) -> ClassId { @@ -1859,71 +1438,6 @@ impl ClassInstance { self.type_arguments(db).copy_into(target); } - pub fn type_check_with_trait_instance( - self, - db: &mut Database, - instance: TraitInstance, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - if !subtyping { - return false; - } - - let trait_impl = if let Some(found) = self - .instance_of - .trait_implementation(db, instance.instance_of) - .cloned() - { - found - } else { - return false; - }; - - let mut trait_instance = trait_impl.instance; - - if self.instance_of.is_generic(db) - && trait_instance.instance_of.is_generic(db) - { - // The generic trait implementation may refer to (or contain a type - // that refers) to a type parameter defined in our class. If we end - // up comparing such a type parameter, we must compare its assigned - // value instead if there is any. - // - // To achieve this we must first expose the type parameter - // assignments in the context, then infer the trait instance into a - // type that uses those assignments (if needed). - self.type_arguments(db).copy_into(&mut context.type_arguments); - trait_instance = trait_instance.inferred(db, context, false); - } - - let args = if self.instance_of.is_generic(db) { - let args = self.type_arguments(db).clone(); - let available = - trait_impl.bounds.mapping.into_iter().all(|(orig, bound)| { - args.get(orig).map_or(false, |t| { - t.is_compatible_with_type_parameter(db, bound, context) - }) - }); - - if !available { - return false; - } - - Some(args) - } else { - None - }; - - trait_instance.type_check_with_trait_instance( - db, - instance, - args.as_ref(), - context, - subtyping, - ) - } - pub fn method(self, db: &Database, name: &str) -> Option { self.instance_of.method(db, name) } @@ -1938,126 +1452,9 @@ impl ClassInstance { .collect() } - fn type_check( - self, - db: &mut Database, - with: TypeId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - match with { - TypeId::ClassInstance(ins) => { - if self.instance_of != ins.instance_of { - return false; - } - - let our_class = self.instance_of.get(db); - - if !our_class.is_generic() { - return true; - } - - let our_args = self.type_arguments(db).clone(); - let their_args = ins.type_arguments(db).clone(); - - if our_args.mapping.is_empty() { - // Empty types are compatible with those that do have - // assigned parameters. For example, an empty Array that has - // not yet been mutated (and thus its parameter is - // unassigned) can be safely passed to something that - // expects e.g. `Array[Int]`. - return true; - } - - our_class.type_parameters.values().clone().into_iter().all( - |param| { - our_args - .get(param) - .zip(their_args.get(param)) - .map(|(ours, theirs)| { - ours.type_check(db, theirs, context, subtyping) - }) - .unwrap_or(false) - }, - ) - } - TypeId::TraitInstance(ins) => { - self.type_check_with_trait_instance(db, ins, context, subtyping) - } - TypeId::TypeParameter(id) => { - id.all_requirements_met(db, |db, req| { - self.type_check_with_trait_instance( - db, req, context, subtyping, - ) - }) - } - _ => false, - } - } - - fn implements_trait_id(self, db: &Database, trait_id: TraitId) -> bool { - self.instance_of.trait_implementation(db, trait_id).is_some() - } - fn named_type(self, db: &Database, name: &str) -> Option { self.instance_of.named_type(db, name) } - - fn as_rigid_type(self, db: &mut Database, bounds: &TypeBounds) -> Self { - if !self.instance_of.get(db).is_generic() { - return self; - } - - let new_args = TypeArguments::rigid(db, self.type_arguments, bounds); - - ClassInstance::generic(db, self.instance_of, new_args) - } - - fn inferred( - self, - db: &mut Database, - context: &mut TypeContext, - immutable: bool, - ) -> Self { - if !self.instance_of.is_generic(db) { - return self; - } - - let mut new_args = TypeArguments::new(); - - for (param, val) in self.type_arguments(db).pairs() { - new_args.assign(param, val.inferred(db, context, immutable)); - } - - Self::generic(db, self.instance_of, new_args) - } -} - -impl FormatType for ClassInstance { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.descend(|buffer| { - let ins_of = self.instance_of.get(buffer.db); - - if ins_of.kind != ClassKind::Tuple { - buffer.write(&ins_of.name); - } - - if ins_of.type_parameters.len() > 0 { - let (open, close) = if ins_of.kind == ClassKind::Tuple { - ("(", ")") - } else { - ("[", "]") - }; - - let params = ins_of.type_parameters.values(); - let args = self.type_arguments(buffer.db); - - buffer.write(open); - buffer.type_arguments(params, args); - buffer.write(close); - } - }); - } } /// A collection of arguments. @@ -2094,37 +1491,6 @@ impl Arguments { fn len(&self) -> usize { self.mapping.len() } - - fn type_check( - &self, - db: &mut Database, - with: &Arguments, - context: &mut TypeContext, - same_name: bool, - ) -> bool { - if self.len() != with.len() { - return false; - } - - for (ours, theirs) in - self.mapping.values().iter().zip(with.mapping.values().iter()) - { - if same_name && ours.name != theirs.name { - return false; - } - - if !ours.value_type.type_check( - db, - theirs.value_type, - context, - false, - ) { - return false; - } - } - - true - } } /// An argument defined in a method or closure. @@ -2145,8 +1511,6 @@ pub trait Block { variable_type: TypeRef, argument_type: TypeRef, ) -> VariableId; - fn throw_type(&self, db: &Database) -> TypeRef; - fn set_throw_type(&self, db: &mut Database, typ: TypeRef); fn return_type(&self, db: &Database) -> TypeRef; fn set_return_type(&self, db: &mut Database, typ: TypeRef); } @@ -2177,59 +1541,903 @@ impl Visibility { } } -#[derive(Copy, Clone)] -pub enum BuiltinFunctionKind { - Function(BIF), - Instruction(Opcode), - Macro(CompilerMacro), -} - -/// A function built into the compiler or VM. -pub struct BuiltinFunction { - kind: BuiltinFunctionKind, - return_type: TypeRef, - throw_type: TypeRef, +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum BuiltinFunction { + ArrayCapacity, + ArrayClear, + ArrayDrop, + ArrayGet, + ArrayLength, + ArrayPop, + ArrayPush, + ArrayRemove, + ArrayReserve, + ArraySet, + ByteArrayAppend, + ByteArrayClear, + ByteArrayClone, + ByteArrayCopyFrom, + ByteArrayDrainToString, + ByteArrayDrop, + ByteArrayEq, + ByteArrayGet, + ByteArrayLength, + ByteArrayNew, + ByteArrayPop, + ByteArrayPush, + ByteArrayRemove, + ByteArrayResize, + ByteArraySet, + ByteArraySlice, + ByteArrayToString, + ChannelDrop, + ChannelNew, + ChannelReceive, + ChannelReceiveUntil, + ChannelSend, + ChannelTryReceive, + ChannelWait, + ChildProcessDrop, + ChildProcessSpawn, + ChildProcessStderrClose, + ChildProcessStderrRead, + ChildProcessStdinClose, + ChildProcessStdinFlush, + ChildProcessStdinWriteBytes, + ChildProcessStdinWriteString, + ChildProcessStdoutClose, + ChildProcessStdoutRead, + ChildProcessTryWait, + ChildProcessWait, + CpuCores, + DirectoryCreate, + DirectoryCreateRecursive, + DirectoryList, + DirectoryRemove, + DirectoryRemoveRecursive, + EnvArguments, + EnvExecutable, + EnvGet, + EnvGetWorkingDirectory, + EnvHomeDirectory, + EnvSetWorkingDirectory, + EnvTempDirectory, + EnvVariables, + Exit, + FileCopy, + FileDrop, + FileFlush, + FileOpen, + FileRead, + FileRemove, + FileSeek, + FileSize, + FileWriteBytes, + FileWriteString, + FloatAdd, + FloatCeil, + FloatDiv, + FloatEq, + FloatFloor, + FloatFromBits, + FloatGe, + FloatGt, + FloatIsInf, + FloatIsNan, + FloatLe, + FloatLt, + FloatMod, + FloatMul, + FloatRound, + FloatSub, + FloatToBits, + FloatToInt, + FloatToString, + IntAdd, + IntBitAnd, + IntBitNot, + IntBitOr, + IntBitXor, + IntDiv, + IntEq, + IntGe, + IntGt, + IntLe, + IntLt, + IntMul, + IntPow, + IntRem, + IntRotateLeft, + IntRotateRight, + IntShl, + IntShr, + IntSub, + IntToFloat, + IntToString, + IntUnsignedShr, + IntWrappingAdd, + IntWrappingMul, + IntWrappingSub, + Moved, + ObjectEq, + Panic, + PathAccessedAt, + PathCreatedAt, + PathExists, + PathIsDirectory, + PathIsFile, + PathModifiedAt, + PathExpand, + ProcessStackFrameLine, + ProcessStackFrameName, + ProcessStackFramePath, + ProcessStacktrace, + ProcessStacktraceDrop, + ProcessStacktraceLength, + ProcessSuspend, + RandomBytes, + RandomDrop, + RandomFloat, + RandomFloatRange, + RandomFromInt, + RandomInt, + RandomIntRange, + RandomNew, + SocketAccept, + SocketAddressPairAddress, + SocketAddressPairDrop, + SocketAddressPairPort, + SocketBind, + SocketConnect, + SocketDrop, + SocketListen, + SocketLocalAddress, + SocketNew, + SocketPeerAddress, + SocketRead, + SocketReceiveFrom, + SocketSendBytesTo, + SocketSendStringTo, + SocketSetBroadcast, + SocketSetKeepalive, + SocketSetLinger, + SocketSetNodelay, + SocketSetOnlyV6, + SocketSetRecvSize, + SocketSetReuseAddress, + SocketSetReusePort, + SocketSetSendSize, + SocketSetTtl, + SocketShutdownRead, + SocketShutdownReadWrite, + SocketShutdownWrite, + SocketTryClone, + SocketWriteBytes, + SocketWriteString, + StderrFlush, + StderrWriteBytes, + StderrWriteString, + StdinRead, + StdoutFlush, + StdoutWriteBytes, + StdoutWriteString, + StringByte, + StringCharacters, + StringCharactersDrop, + StringCharactersNext, + StringConcat, + StringConcatArray, + StringDrop, + StringEq, + StringSize, + StringSliceBytes, + StringToByteArray, + StringToFloat, + StringToInt, + StringToLower, + StringToUpper, + TimeMonotonic, + TimeSystem, + TimeSystemOffset, + HashKey0, + HashKey1, } impl BuiltinFunction { - pub fn alloc( - db: &mut Database, - kind: BuiltinFunctionKind, - name: &str, - return_type: TypeRef, - throw_type: TypeRef, - ) -> BuiltinFunctionId { - let func = Self { kind, return_type, throw_type }; - - Self::add(db, name.to_string(), func) + pub fn mapping() -> HashMap { + vec![ + BuiltinFunction::ArrayCapacity, + BuiltinFunction::ArrayClear, + BuiltinFunction::ArrayDrop, + BuiltinFunction::ArrayGet, + BuiltinFunction::ArrayLength, + BuiltinFunction::ArrayPop, + BuiltinFunction::ArrayPush, + BuiltinFunction::ArrayRemove, + BuiltinFunction::ArrayReserve, + BuiltinFunction::ArraySet, + BuiltinFunction::ByteArrayNew, + BuiltinFunction::ByteArrayAppend, + BuiltinFunction::ByteArrayClear, + BuiltinFunction::ByteArrayClone, + BuiltinFunction::ByteArrayCopyFrom, + BuiltinFunction::ByteArrayDrainToString, + BuiltinFunction::ByteArrayDrop, + BuiltinFunction::ByteArrayEq, + BuiltinFunction::ByteArrayGet, + BuiltinFunction::ByteArrayLength, + BuiltinFunction::ByteArrayPop, + BuiltinFunction::ByteArrayPush, + BuiltinFunction::ByteArrayRemove, + BuiltinFunction::ByteArrayResize, + BuiltinFunction::ByteArraySet, + BuiltinFunction::ByteArraySlice, + BuiltinFunction::ByteArrayToString, + BuiltinFunction::ChildProcessDrop, + BuiltinFunction::ChildProcessSpawn, + BuiltinFunction::ChildProcessStderrClose, + BuiltinFunction::ChildProcessStderrRead, + BuiltinFunction::ChildProcessStdinClose, + BuiltinFunction::ChildProcessStdinFlush, + BuiltinFunction::ChildProcessStdinWriteBytes, + BuiltinFunction::ChildProcessStdinWriteString, + BuiltinFunction::ChildProcessStdoutClose, + BuiltinFunction::ChildProcessStdoutRead, + BuiltinFunction::ChildProcessTryWait, + BuiltinFunction::ChildProcessWait, + BuiltinFunction::CpuCores, + BuiltinFunction::DirectoryCreate, + BuiltinFunction::DirectoryCreateRecursive, + BuiltinFunction::DirectoryList, + BuiltinFunction::DirectoryRemove, + BuiltinFunction::DirectoryRemoveRecursive, + BuiltinFunction::EnvArguments, + BuiltinFunction::EnvExecutable, + BuiltinFunction::EnvGet, + BuiltinFunction::EnvGetWorkingDirectory, + BuiltinFunction::EnvHomeDirectory, + BuiltinFunction::EnvSetWorkingDirectory, + BuiltinFunction::EnvTempDirectory, + BuiltinFunction::EnvVariables, + BuiltinFunction::Exit, + BuiltinFunction::FileCopy, + BuiltinFunction::FileDrop, + BuiltinFunction::FileFlush, + BuiltinFunction::FileOpen, + BuiltinFunction::FileRead, + BuiltinFunction::FileRemove, + BuiltinFunction::FileSeek, + BuiltinFunction::FileSize, + BuiltinFunction::FileWriteBytes, + BuiltinFunction::FileWriteString, + BuiltinFunction::FloatAdd, + BuiltinFunction::FloatCeil, + BuiltinFunction::FloatDiv, + BuiltinFunction::FloatEq, + BuiltinFunction::FloatFloor, + BuiltinFunction::FloatFromBits, + BuiltinFunction::FloatGe, + BuiltinFunction::FloatGt, + BuiltinFunction::FloatIsInf, + BuiltinFunction::FloatIsNan, + BuiltinFunction::FloatLe, + BuiltinFunction::FloatLt, + BuiltinFunction::FloatMod, + BuiltinFunction::FloatMul, + BuiltinFunction::FloatRound, + BuiltinFunction::FloatSub, + BuiltinFunction::FloatToBits, + BuiltinFunction::FloatToInt, + BuiltinFunction::FloatToString, + BuiltinFunction::ChannelDrop, + BuiltinFunction::ChannelNew, + BuiltinFunction::ChannelReceive, + BuiltinFunction::ChannelReceiveUntil, + BuiltinFunction::ChannelSend, + BuiltinFunction::ChannelTryReceive, + BuiltinFunction::ChannelWait, + BuiltinFunction::IntAdd, + BuiltinFunction::IntBitAnd, + BuiltinFunction::IntBitNot, + BuiltinFunction::IntBitOr, + BuiltinFunction::IntBitXor, + BuiltinFunction::IntDiv, + BuiltinFunction::IntEq, + BuiltinFunction::IntGe, + BuiltinFunction::IntGt, + BuiltinFunction::IntLe, + BuiltinFunction::IntLt, + BuiltinFunction::IntRem, + BuiltinFunction::IntMul, + BuiltinFunction::IntPow, + BuiltinFunction::IntRotateLeft, + BuiltinFunction::IntRotateRight, + BuiltinFunction::IntShl, + BuiltinFunction::IntShr, + BuiltinFunction::IntSub, + BuiltinFunction::IntToFloat, + BuiltinFunction::IntToString, + BuiltinFunction::IntUnsignedShr, + BuiltinFunction::IntWrappingAdd, + BuiltinFunction::IntWrappingMul, + BuiltinFunction::IntWrappingSub, + BuiltinFunction::Moved, + BuiltinFunction::ObjectEq, + BuiltinFunction::Panic, + BuiltinFunction::PathAccessedAt, + BuiltinFunction::PathCreatedAt, + BuiltinFunction::PathExists, + BuiltinFunction::PathIsDirectory, + BuiltinFunction::PathIsFile, + BuiltinFunction::PathModifiedAt, + BuiltinFunction::PathExpand, + BuiltinFunction::ProcessStackFrameLine, + BuiltinFunction::ProcessStackFrameName, + BuiltinFunction::ProcessStackFramePath, + BuiltinFunction::ProcessStacktrace, + BuiltinFunction::ProcessStacktraceDrop, + BuiltinFunction::ProcessStacktraceLength, + BuiltinFunction::ProcessSuspend, + BuiltinFunction::RandomBytes, + BuiltinFunction::RandomDrop, + BuiltinFunction::RandomFloat, + BuiltinFunction::RandomFloatRange, + BuiltinFunction::RandomFromInt, + BuiltinFunction::RandomInt, + BuiltinFunction::RandomIntRange, + BuiltinFunction::RandomNew, + BuiltinFunction::SocketAccept, + BuiltinFunction::SocketAddressPairAddress, + BuiltinFunction::SocketAddressPairDrop, + BuiltinFunction::SocketAddressPairPort, + BuiltinFunction::SocketNew, + BuiltinFunction::SocketBind, + BuiltinFunction::SocketConnect, + BuiltinFunction::SocketDrop, + BuiltinFunction::SocketListen, + BuiltinFunction::SocketLocalAddress, + BuiltinFunction::SocketPeerAddress, + BuiltinFunction::SocketRead, + BuiltinFunction::SocketReceiveFrom, + BuiltinFunction::SocketSendBytesTo, + BuiltinFunction::SocketSendStringTo, + BuiltinFunction::SocketSetBroadcast, + BuiltinFunction::SocketSetKeepalive, + BuiltinFunction::SocketSetLinger, + BuiltinFunction::SocketSetNodelay, + BuiltinFunction::SocketSetOnlyV6, + BuiltinFunction::SocketSetRecvSize, + BuiltinFunction::SocketSetReuseAddress, + BuiltinFunction::SocketSetReusePort, + BuiltinFunction::SocketSetSendSize, + BuiltinFunction::SocketSetTtl, + BuiltinFunction::SocketShutdownRead, + BuiltinFunction::SocketShutdownReadWrite, + BuiltinFunction::SocketShutdownWrite, + BuiltinFunction::SocketTryClone, + BuiltinFunction::SocketWriteBytes, + BuiltinFunction::SocketWriteString, + BuiltinFunction::StderrFlush, + BuiltinFunction::StderrWriteBytes, + BuiltinFunction::StderrWriteString, + BuiltinFunction::StdinRead, + BuiltinFunction::StdoutFlush, + BuiltinFunction::StdoutWriteBytes, + BuiltinFunction::StdoutWriteString, + BuiltinFunction::StringByte, + BuiltinFunction::StringCharacters, + BuiltinFunction::StringCharactersDrop, + BuiltinFunction::StringCharactersNext, + BuiltinFunction::StringConcat, + BuiltinFunction::StringConcatArray, + BuiltinFunction::StringDrop, + BuiltinFunction::StringEq, + BuiltinFunction::StringSize, + BuiltinFunction::StringSliceBytes, + BuiltinFunction::StringToByteArray, + BuiltinFunction::StringToFloat, + BuiltinFunction::StringToInt, + BuiltinFunction::StringToLower, + BuiltinFunction::StringToUpper, + BuiltinFunction::TimeMonotonic, + BuiltinFunction::TimeSystem, + BuiltinFunction::TimeSystemOffset, + BuiltinFunction::HashKey0, + BuiltinFunction::HashKey1, + ] + .into_iter() + .fold(HashMap::new(), |mut map, func| { + map.insert(func.name().to_string(), func); + map + }) } - fn add(db: &mut Database, name: String, func: Self) -> BuiltinFunctionId { - let id = db.builtin_functions.len(); + pub fn name(self) -> &'static str { + match self { + BuiltinFunction::ArrayCapacity => "array_capacity", + BuiltinFunction::ArrayClear => "array_clear", + BuiltinFunction::ArrayDrop => "array_drop", + BuiltinFunction::ArrayGet => "array_get", + BuiltinFunction::ArrayLength => "array_length", + BuiltinFunction::ArrayPop => "array_pop", + BuiltinFunction::ArrayPush => "array_push", + BuiltinFunction::ArrayRemove => "array_remove", + BuiltinFunction::ArrayReserve => "array_reserve", + BuiltinFunction::ArraySet => "array_set", + BuiltinFunction::ByteArrayNew => "byte_array_new", + BuiltinFunction::ByteArrayAppend => "byte_array_append", + BuiltinFunction::ByteArrayClear => "byte_array_clear", + BuiltinFunction::ByteArrayClone => "byte_array_clone", + BuiltinFunction::ByteArrayCopyFrom => "byte_array_copy_from", + BuiltinFunction::ByteArrayDrainToString => { + "byte_array_drain_to_string" + } + BuiltinFunction::ByteArrayDrop => "byte_array_drop", + BuiltinFunction::ByteArrayEq => "byte_array_eq", + BuiltinFunction::ByteArrayGet => "byte_array_get", + BuiltinFunction::ByteArrayLength => "byte_array_length", + BuiltinFunction::ByteArrayPop => "byte_array_pop", + BuiltinFunction::ByteArrayPush => "byte_array_push", + BuiltinFunction::ByteArrayRemove => "byte_array_remove", + BuiltinFunction::ByteArrayResize => "byte_array_resize", + BuiltinFunction::ByteArraySet => "byte_array_set", + BuiltinFunction::ByteArraySlice => "byte_array_slice", + BuiltinFunction::ByteArrayToString => "byte_array_to_string", + BuiltinFunction::ChildProcessDrop => "child_process_drop", + BuiltinFunction::ChildProcessSpawn => "child_process_spawn", + BuiltinFunction::ChildProcessStderrClose => { + "child_process_stderr_close" + } + BuiltinFunction::ChildProcessStderrRead => { + "child_process_stderr_read" + } + BuiltinFunction::ChildProcessStdinClose => { + "child_process_stdin_close" + } + BuiltinFunction::ChildProcessStdinFlush => { + "child_process_stdin_flush" + } + BuiltinFunction::ChildProcessStdinWriteBytes => { + "child_process_stdin_write_bytes" + } + BuiltinFunction::ChildProcessStdinWriteString => { + "child_process_stdin_write_string" + } + BuiltinFunction::ChildProcessStdoutClose => { + "child_process_stdout_close" + } + BuiltinFunction::ChildProcessStdoutRead => { + "child_process_stdout_read" + } + BuiltinFunction::ChildProcessTryWait => "child_process_try_wait", + BuiltinFunction::ChildProcessWait => "child_process_wait", + BuiltinFunction::CpuCores => "cpu_cores", + BuiltinFunction::DirectoryCreate => "directory_create", + BuiltinFunction::DirectoryCreateRecursive => { + "directory_create_recursive" + } + BuiltinFunction::DirectoryList => "directory_list", + BuiltinFunction::DirectoryRemove => "directory_remove", + BuiltinFunction::DirectoryRemoveRecursive => { + "directory_remove_recursive" + } + BuiltinFunction::EnvArguments => "env_arguments", + BuiltinFunction::EnvExecutable => "env_executable", + BuiltinFunction::EnvGet => "env_get", + BuiltinFunction::EnvGetWorkingDirectory => { + "env_get_working_directory" + } + BuiltinFunction::EnvHomeDirectory => "env_home_directory", + BuiltinFunction::EnvSetWorkingDirectory => { + "env_set_working_directory" + } + BuiltinFunction::EnvTempDirectory => "env_temp_directory", + BuiltinFunction::EnvVariables => "env_variables", + BuiltinFunction::Exit => "exit", + BuiltinFunction::FileCopy => "file_copy", + BuiltinFunction::FileDrop => "file_drop", + BuiltinFunction::FileFlush => "file_flush", + BuiltinFunction::FileOpen => "file_open", + BuiltinFunction::FileRead => "file_read", + BuiltinFunction::FileRemove => "file_remove", + BuiltinFunction::FileSeek => "file_seek", + BuiltinFunction::FileSize => "file_size", + BuiltinFunction::FileWriteBytes => "file_write_bytes", + BuiltinFunction::FileWriteString => "file_write_string", + BuiltinFunction::FloatAdd => "float_add", + BuiltinFunction::FloatCeil => "float_ceil", + BuiltinFunction::FloatDiv => "float_div", + BuiltinFunction::FloatEq => "float_eq", + BuiltinFunction::FloatFloor => "float_floor", + BuiltinFunction::FloatFromBits => "float_from_bits", + BuiltinFunction::FloatGe => "float_ge", + BuiltinFunction::FloatGt => "float_gt", + BuiltinFunction::FloatIsInf => "float_is_inf", + BuiltinFunction::FloatIsNan => "float_is_nan", + BuiltinFunction::FloatLe => "float_le", + BuiltinFunction::FloatLt => "float_lt", + BuiltinFunction::FloatMod => "float_mod", + BuiltinFunction::FloatMul => "float_mul", + BuiltinFunction::FloatRound => "float_round", + BuiltinFunction::FloatSub => "float_sub", + BuiltinFunction::FloatToBits => "float_to_bits", + BuiltinFunction::FloatToInt => "float_to_int", + BuiltinFunction::FloatToString => "float_to_string", + BuiltinFunction::ChannelReceive => "channel_receive", + BuiltinFunction::ChannelReceiveUntil => "channel_receive_until", + BuiltinFunction::ChannelDrop => "channel_drop", + BuiltinFunction::ChannelWait => "channel_wait", + BuiltinFunction::ChannelNew => "channel_new", + BuiltinFunction::ChannelSend => "channel_send", + BuiltinFunction::ChannelTryReceive => "channel_try_receive", + BuiltinFunction::IntAdd => "int_add", + BuiltinFunction::IntBitAnd => "int_bit_and", + BuiltinFunction::IntBitNot => "int_bit_not", + BuiltinFunction::IntBitOr => "int_bit_or", + BuiltinFunction::IntBitXor => "int_bit_xor", + BuiltinFunction::IntDiv => "int_div", + BuiltinFunction::IntEq => "int_eq", + BuiltinFunction::IntGe => "int_ge", + BuiltinFunction::IntGt => "int_gt", + BuiltinFunction::IntLe => "int_le", + BuiltinFunction::IntLt => "int_lt", + BuiltinFunction::IntRem => "int_rem", + BuiltinFunction::IntMul => "int_mul", + BuiltinFunction::IntPow => "int_pow", + BuiltinFunction::IntRotateLeft => "int_rotate_left", + BuiltinFunction::IntRotateRight => "int_rotate_right", + BuiltinFunction::IntShl => "int_shl", + BuiltinFunction::IntShr => "int_shr", + BuiltinFunction::IntSub => "int_sub", + BuiltinFunction::IntToFloat => "int_to_float", + BuiltinFunction::IntToString => "int_to_string", + BuiltinFunction::IntUnsignedShr => "int_unsigned_shr", + BuiltinFunction::IntWrappingAdd => "int_wrapping_add", + BuiltinFunction::IntWrappingMul => "int_wrapping_mul", + BuiltinFunction::IntWrappingSub => "int_wrapping_sub", + BuiltinFunction::Moved => "moved", + BuiltinFunction::ObjectEq => "object_eq", + BuiltinFunction::Panic => "panic", + BuiltinFunction::PathAccessedAt => "path_accessed_at", + BuiltinFunction::PathCreatedAt => "path_created_at", + BuiltinFunction::PathExists => "path_exists", + BuiltinFunction::PathIsDirectory => "path_is_directory", + BuiltinFunction::PathIsFile => "path_is_file", + BuiltinFunction::PathModifiedAt => "path_modified_at", + BuiltinFunction::PathExpand => "path_expand", + BuiltinFunction::ProcessStackFrameLine => { + "process_stack_frame_line" + } + BuiltinFunction::ProcessStackFrameName => { + "process_stack_frame_name" + } + BuiltinFunction::ProcessStackFramePath => { + "process_stack_frame_path" + } + BuiltinFunction::ProcessStacktrace => "process_stacktrace", + BuiltinFunction::ProcessStacktraceDrop => "process_stacktrace_drop", + BuiltinFunction::ProcessStacktraceLength => { + "process_stacktrace_length" + } + BuiltinFunction::ProcessSuspend => "process_suspend", + BuiltinFunction::RandomBytes => "random_bytes", + BuiltinFunction::RandomDrop => "random_drop", + BuiltinFunction::RandomFloat => "random_float", + BuiltinFunction::RandomFloatRange => "random_float_range", + BuiltinFunction::RandomFromInt => "random_from_int", + BuiltinFunction::RandomInt => "random_int", + BuiltinFunction::RandomIntRange => "random_int_range", + BuiltinFunction::RandomNew => "random_new", + BuiltinFunction::SocketAccept => "socket_accept", + BuiltinFunction::SocketAddressPairAddress => { + "socket_address_pair_address" + } + BuiltinFunction::SocketAddressPairDrop => { + "socket_address_pair_drop" + } + BuiltinFunction::SocketAddressPairPort => { + "socket_address_pair_port" + } + BuiltinFunction::SocketNew => "socket_new", + BuiltinFunction::SocketBind => "socket_bind", + BuiltinFunction::SocketConnect => "socket_connect", + BuiltinFunction::SocketDrop => "socket_drop", + BuiltinFunction::SocketListen => "socket_listen", + BuiltinFunction::SocketLocalAddress => "socket_local_address", + BuiltinFunction::SocketPeerAddress => "socket_peer_address", + BuiltinFunction::SocketRead => "socket_read", + BuiltinFunction::SocketReceiveFrom => "socket_receive_from", + BuiltinFunction::SocketSendBytesTo => "socket_send_bytes_to", + BuiltinFunction::SocketSendStringTo => "socket_send_string_to", + BuiltinFunction::SocketSetBroadcast => "socket_set_broadcast", + BuiltinFunction::SocketSetKeepalive => "socket_set_keepalive", + BuiltinFunction::SocketSetLinger => "socket_set_linger", + BuiltinFunction::SocketSetNodelay => "socket_set_nodelay", + BuiltinFunction::SocketSetOnlyV6 => "socket_set_only_v6", + BuiltinFunction::SocketSetRecvSize => "socket_set_recv_size", + BuiltinFunction::SocketSetReuseAddress => { + "socket_set_reuse_address" + } + BuiltinFunction::SocketSetReusePort => "socket_set_reuse_port", + BuiltinFunction::SocketSetSendSize => "socket_set_send_size", + BuiltinFunction::SocketSetTtl => "socket_set_ttl", + BuiltinFunction::SocketShutdownRead => "socket_shutdown_read", + BuiltinFunction::SocketShutdownReadWrite => { + "socket_shutdown_read_write" + } + BuiltinFunction::SocketShutdownWrite => "socket_shutdown_write", + BuiltinFunction::SocketTryClone => "socket_try_clone", + BuiltinFunction::SocketWriteBytes => "socket_write_bytes", + BuiltinFunction::SocketWriteString => "socket_write_string", + BuiltinFunction::StderrFlush => "stderr_flush", + BuiltinFunction::StderrWriteBytes => "stderr_write_bytes", + BuiltinFunction::StderrWriteString => "stderr_write_string", + BuiltinFunction::StdinRead => "stdin_read", + BuiltinFunction::StdoutFlush => "stdout_flush", + BuiltinFunction::StdoutWriteBytes => "stdout_write_bytes", + BuiltinFunction::StdoutWriteString => "stdout_write_string", + BuiltinFunction::StringByte => "string_byte", + BuiltinFunction::StringCharacters => "string_characters", + BuiltinFunction::StringCharactersDrop => "string_characters_drop", + BuiltinFunction::StringCharactersNext => "string_characters_next", + BuiltinFunction::StringConcat => "string_concat", + BuiltinFunction::StringConcatArray => "string_concat_array", + BuiltinFunction::StringDrop => "string_drop", + BuiltinFunction::StringEq => "string_eq", + BuiltinFunction::StringSize => "string_size", + BuiltinFunction::StringSliceBytes => "string_slice_bytes", + BuiltinFunction::StringToByteArray => "string_to_byte_array", + BuiltinFunction::StringToFloat => "string_to_float", + BuiltinFunction::StringToInt => "string_to_int", + BuiltinFunction::StringToLower => "string_to_lower", + BuiltinFunction::StringToUpper => "string_to_upper", + BuiltinFunction::TimeMonotonic => "time_monotonic", + BuiltinFunction::TimeSystem => "time_system", + BuiltinFunction::TimeSystemOffset => "time_system_offset", + BuiltinFunction::HashKey0 => "hash_key0", + BuiltinFunction::HashKey1 => "hash_key1", + } + } + + pub fn return_type(self) -> TypeRef { + let result = TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new( + ClassId::result(), + ))); - db.builtin_functions.insert(name, func); - BuiltinFunctionId(id) + match self { + BuiltinFunction::ArrayCapacity => TypeRef::int(), + BuiltinFunction::ArrayClear => TypeRef::nil(), + BuiltinFunction::ArrayDrop => TypeRef::nil(), + BuiltinFunction::ArrayGet => TypeRef::Any, + BuiltinFunction::ArrayLength => TypeRef::int(), + BuiltinFunction::ArrayPop => result, + BuiltinFunction::ArrayPush => TypeRef::nil(), + BuiltinFunction::ArrayRemove => TypeRef::Any, + BuiltinFunction::ArrayReserve => TypeRef::nil(), + BuiltinFunction::ArraySet => TypeRef::Any, + BuiltinFunction::ByteArrayNew => TypeRef::byte_array(), + BuiltinFunction::ByteArrayAppend => TypeRef::nil(), + BuiltinFunction::ByteArrayClear => TypeRef::nil(), + BuiltinFunction::ByteArrayClone => TypeRef::byte_array(), + BuiltinFunction::ByteArrayCopyFrom => TypeRef::int(), + BuiltinFunction::ByteArrayDrainToString => TypeRef::string(), + BuiltinFunction::ByteArrayDrop => TypeRef::nil(), + BuiltinFunction::ByteArrayEq => TypeRef::boolean(), + BuiltinFunction::ByteArrayGet => TypeRef::int(), + BuiltinFunction::ByteArrayLength => TypeRef::int(), + BuiltinFunction::ByteArrayPop => TypeRef::int(), + BuiltinFunction::ByteArrayPush => TypeRef::nil(), + BuiltinFunction::ByteArrayRemove => TypeRef::int(), + BuiltinFunction::ByteArrayResize => TypeRef::nil(), + BuiltinFunction::ByteArraySet => TypeRef::int(), + BuiltinFunction::ByteArraySlice => TypeRef::byte_array(), + BuiltinFunction::ByteArrayToString => TypeRef::string(), + BuiltinFunction::ChildProcessDrop => TypeRef::nil(), + BuiltinFunction::ChildProcessSpawn => result, + BuiltinFunction::ChildProcessStderrClose => TypeRef::nil(), + BuiltinFunction::ChildProcessStderrRead => result, + BuiltinFunction::ChildProcessStdinClose => TypeRef::nil(), + BuiltinFunction::ChildProcessStdinFlush => result, + BuiltinFunction::ChildProcessStdinWriteBytes => result, + BuiltinFunction::ChildProcessStdinWriteString => result, + BuiltinFunction::ChildProcessStdoutClose => result, + BuiltinFunction::ChildProcessStdoutRead => result, + BuiltinFunction::ChildProcessTryWait => result, + BuiltinFunction::ChildProcessWait => result, + BuiltinFunction::CpuCores => TypeRef::int(), + BuiltinFunction::DirectoryCreate => result, + BuiltinFunction::DirectoryCreateRecursive => result, + BuiltinFunction::DirectoryList => result, + BuiltinFunction::DirectoryRemove => result, + BuiltinFunction::DirectoryRemoveRecursive => result, + BuiltinFunction::EnvArguments => TypeRef::Any, + BuiltinFunction::EnvExecutable => result, + BuiltinFunction::EnvGet => result, + BuiltinFunction::EnvGetWorkingDirectory => result, + BuiltinFunction::EnvHomeDirectory => result, + BuiltinFunction::EnvSetWorkingDirectory => result, + BuiltinFunction::EnvTempDirectory => TypeRef::string(), + BuiltinFunction::EnvVariables => TypeRef::Any, + BuiltinFunction::Exit => TypeRef::Never, + BuiltinFunction::FileCopy => result, + BuiltinFunction::FileDrop => TypeRef::nil(), + BuiltinFunction::FileFlush => result, + BuiltinFunction::FileOpen => result, + BuiltinFunction::FileRead => result, + BuiltinFunction::FileRemove => result, + BuiltinFunction::FileSeek => result, + BuiltinFunction::FileSize => result, + BuiltinFunction::FileWriteBytes => result, + BuiltinFunction::FileWriteString => result, + BuiltinFunction::FloatAdd => TypeRef::float(), + BuiltinFunction::FloatCeil => TypeRef::float(), + BuiltinFunction::FloatDiv => TypeRef::float(), + BuiltinFunction::FloatEq => TypeRef::boolean(), + BuiltinFunction::FloatFloor => TypeRef::float(), + BuiltinFunction::FloatFromBits => TypeRef::float(), + BuiltinFunction::FloatGe => TypeRef::boolean(), + BuiltinFunction::FloatGt => TypeRef::boolean(), + BuiltinFunction::FloatIsInf => TypeRef::boolean(), + BuiltinFunction::FloatIsNan => TypeRef::boolean(), + BuiltinFunction::FloatLe => TypeRef::boolean(), + BuiltinFunction::FloatLt => TypeRef::boolean(), + BuiltinFunction::FloatMod => TypeRef::float(), + BuiltinFunction::FloatMul => TypeRef::float(), + BuiltinFunction::FloatRound => TypeRef::float(), + BuiltinFunction::FloatSub => TypeRef::float(), + BuiltinFunction::FloatToBits => TypeRef::int(), + BuiltinFunction::FloatToInt => TypeRef::int(), + BuiltinFunction::FloatToString => TypeRef::string(), + BuiltinFunction::ChannelReceive => TypeRef::Any, + BuiltinFunction::ChannelReceiveUntil => result, + BuiltinFunction::ChannelDrop => TypeRef::nil(), + BuiltinFunction::ChannelWait => TypeRef::nil(), + BuiltinFunction::ChannelNew => TypeRef::Any, + BuiltinFunction::ChannelSend => TypeRef::nil(), + BuiltinFunction::ChannelTryReceive => result, + BuiltinFunction::IntAdd => TypeRef::int(), + BuiltinFunction::IntBitAnd => TypeRef::int(), + BuiltinFunction::IntBitNot => TypeRef::int(), + BuiltinFunction::IntBitOr => TypeRef::int(), + BuiltinFunction::IntBitXor => TypeRef::int(), + BuiltinFunction::IntDiv => TypeRef::int(), + BuiltinFunction::IntEq => TypeRef::boolean(), + BuiltinFunction::IntGe => TypeRef::boolean(), + BuiltinFunction::IntGt => TypeRef::boolean(), + BuiltinFunction::IntLe => TypeRef::boolean(), + BuiltinFunction::IntLt => TypeRef::boolean(), + BuiltinFunction::IntRem => TypeRef::int(), + BuiltinFunction::IntMul => TypeRef::int(), + BuiltinFunction::IntPow => TypeRef::int(), + BuiltinFunction::IntRotateLeft => TypeRef::int(), + BuiltinFunction::IntRotateRight => TypeRef::int(), + BuiltinFunction::IntShl => TypeRef::int(), + BuiltinFunction::IntShr => TypeRef::int(), + BuiltinFunction::IntSub => TypeRef::int(), + BuiltinFunction::IntToFloat => TypeRef::float(), + BuiltinFunction::IntToString => TypeRef::string(), + BuiltinFunction::IntUnsignedShr => TypeRef::int(), + BuiltinFunction::IntWrappingAdd => TypeRef::int(), + BuiltinFunction::IntWrappingMul => TypeRef::int(), + BuiltinFunction::IntWrappingSub => TypeRef::int(), + BuiltinFunction::Moved => TypeRef::nil(), + BuiltinFunction::ObjectEq => TypeRef::boolean(), + BuiltinFunction::Panic => TypeRef::Never, + BuiltinFunction::PathAccessedAt => result, + BuiltinFunction::PathCreatedAt => result, + BuiltinFunction::PathExists => TypeRef::boolean(), + BuiltinFunction::PathIsDirectory => TypeRef::boolean(), + BuiltinFunction::PathIsFile => TypeRef::boolean(), + BuiltinFunction::PathModifiedAt => result, + BuiltinFunction::PathExpand => result, + BuiltinFunction::ProcessStackFrameLine => TypeRef::int(), + BuiltinFunction::ProcessStackFrameName => TypeRef::string(), + BuiltinFunction::ProcessStackFramePath => TypeRef::string(), + BuiltinFunction::ProcessStacktrace => TypeRef::Any, + BuiltinFunction::ProcessStacktraceDrop => TypeRef::nil(), + BuiltinFunction::ProcessStacktraceLength => TypeRef::int(), + BuiltinFunction::ProcessSuspend => TypeRef::nil(), + BuiltinFunction::RandomBytes => TypeRef::byte_array(), + BuiltinFunction::RandomDrop => TypeRef::nil(), + BuiltinFunction::RandomFloat => TypeRef::float(), + BuiltinFunction::RandomFloatRange => TypeRef::float(), + BuiltinFunction::RandomFromInt => TypeRef::Any, + BuiltinFunction::RandomInt => TypeRef::int(), + BuiltinFunction::RandomIntRange => TypeRef::int(), + BuiltinFunction::RandomNew => TypeRef::Any, + BuiltinFunction::SocketAccept => result, + BuiltinFunction::SocketAddressPairAddress => TypeRef::string(), + BuiltinFunction::SocketAddressPairDrop => TypeRef::nil(), + BuiltinFunction::SocketAddressPairPort => TypeRef::int(), + BuiltinFunction::SocketBind => result, + BuiltinFunction::SocketConnect => result, + BuiltinFunction::SocketDrop => TypeRef::nil(), + BuiltinFunction::SocketListen => result, + BuiltinFunction::SocketLocalAddress => result, + BuiltinFunction::SocketNew => result, + BuiltinFunction::SocketPeerAddress => result, + BuiltinFunction::SocketRead => result, + BuiltinFunction::SocketReceiveFrom => result, + BuiltinFunction::SocketSendBytesTo => result, + BuiltinFunction::SocketSendStringTo => result, + BuiltinFunction::SocketSetBroadcast => result, + BuiltinFunction::SocketSetKeepalive => result, + BuiltinFunction::SocketSetLinger => result, + BuiltinFunction::SocketSetNodelay => result, + BuiltinFunction::SocketSetOnlyV6 => result, + BuiltinFunction::SocketSetRecvSize => result, + BuiltinFunction::SocketSetReuseAddress => result, + BuiltinFunction::SocketSetReusePort => result, + BuiltinFunction::SocketSetSendSize => result, + BuiltinFunction::SocketSetTtl => result, + BuiltinFunction::SocketShutdownRead => result, + BuiltinFunction::SocketShutdownReadWrite => result, + BuiltinFunction::SocketShutdownWrite => result, + BuiltinFunction::SocketTryClone => result, + BuiltinFunction::SocketWriteBytes => result, + BuiltinFunction::SocketWriteString => result, + BuiltinFunction::StderrFlush => TypeRef::nil(), + BuiltinFunction::StderrWriteBytes => result, + BuiltinFunction::StderrWriteString => result, + BuiltinFunction::StdinRead => result, + BuiltinFunction::StdoutFlush => TypeRef::nil(), + BuiltinFunction::StdoutWriteBytes => result, + BuiltinFunction::StdoutWriteString => result, + BuiltinFunction::StringByte => TypeRef::int(), + BuiltinFunction::StringCharacters => TypeRef::Any, + BuiltinFunction::StringCharactersDrop => TypeRef::nil(), + BuiltinFunction::StringCharactersNext => result, + BuiltinFunction::StringConcat => TypeRef::string(), + BuiltinFunction::StringConcatArray => TypeRef::string(), + BuiltinFunction::StringDrop => TypeRef::nil(), + BuiltinFunction::StringEq => TypeRef::boolean(), + BuiltinFunction::StringSize => TypeRef::int(), + BuiltinFunction::StringSliceBytes => TypeRef::string(), + BuiltinFunction::StringToByteArray => TypeRef::byte_array(), + BuiltinFunction::StringToFloat => result, + BuiltinFunction::StringToInt => result, + BuiltinFunction::StringToLower => TypeRef::string(), + BuiltinFunction::StringToUpper => TypeRef::string(), + BuiltinFunction::TimeMonotonic => TypeRef::int(), + BuiltinFunction::TimeSystem => TypeRef::float(), + BuiltinFunction::TimeSystemOffset => TypeRef::int(), + BuiltinFunction::HashKey0 => TypeRef::int(), + BuiltinFunction::HashKey1 => TypeRef::int(), + } } } -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct BuiltinFunctionId(usize); +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum BuiltinConstant { + Arch, + Os, + Abi, +} -impl BuiltinFunctionId { - pub fn kind(self, db: &Database) -> BuiltinFunctionKind { - self.get(db).kind +impl BuiltinConstant { + pub fn mapping() -> HashMap { + vec![BuiltinConstant::Arch, BuiltinConstant::Os, BuiltinConstant::Abi] + .into_iter() + .fold(HashMap::new(), |mut map, cons| { + map.insert(cons.name().to_string(), cons); + map + }) } - pub fn return_type(self, db: &Database) -> TypeRef { - self.get(db).return_type - } - - pub fn throw_type(self, db: &Database) -> TypeRef { - self.get(db).throw_type + pub fn name(self) -> &'static str { + match self { + BuiltinConstant::Arch => "_INKO_ARCH", + BuiltinConstant::Os => "_INKO_OS", + BuiltinConstant::Abi => "_INKO_ABI", + } } - fn get(self, db: &Database) -> &BuiltinFunction { - &db.builtin_functions[self.0] + pub fn return_type(self) -> TypeRef { + match self { + BuiltinConstant::Arch => TypeRef::string(), + BuiltinConstant::Os => TypeRef::string(), + BuiltinConstant::Abi => TypeRef::string(), + } } } @@ -2262,21 +2470,8 @@ pub enum MethodSource { /// The method is directly defined for a type. Direct, - /// The method is defined using a regular trait implementation. - Implementation(TraitInstance), - - /// The method is defined using a bounded trait implementation. - BoundedImplementation(TraitInstance), -} - -impl MethodSource { - pub fn implementation(bounded: bool, instance: TraitInstance) -> Self { - if bounded { - Self::BoundedImplementation(instance) - } else { - Self::Implementation(instance) - } - } + /// The method is defined using a trait implementation. + Implementation(TraitInstance, MethodId), } pub enum MethodLookup { @@ -2307,7 +2502,7 @@ pub struct Method { visibility: Visibility, type_parameters: IndexMap, arguments: Arguments, - throw_type: TypeRef, + bounds: TypeBounds, return_type: TypeRef, source: MethodSource, main: bool, @@ -2316,13 +2511,6 @@ pub struct Method { /// `Self`). receiver: TypeRef, - /// The type to use for `Self` in this method. - /// - /// This differs from the receiver type. For example, for a static method - /// the receiver type is the class, while the type of `Self` is an instance - /// of the class. - self_type: Option, - /// The fields this method has access to, along with their types. field_types: HashMap, } @@ -2354,12 +2542,11 @@ impl Method { kind, visibility, type_parameters: IndexMap::new(), + bounds: TypeBounds::new(), arguments: Arguments::new(), - throw_type: TypeRef::Never, return_type: TypeRef::Unknown, source: MethodSource::Direct, receiver: TypeRef::Unknown, - self_type: None, field_types: HashMap::new(), main: false, } @@ -2367,7 +2554,7 @@ impl Method { } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub struct MethodId(usize); +pub struct MethodId(pub usize); impl MethodId { pub fn named_type(self, db: &Database, name: &str) -> Option { @@ -2377,6 +2564,10 @@ impl MethodId { .map(|&id| Symbol::TypeParameter(id)) } + pub fn type_parameters(self, db: &Database) -> Vec { + self.get(db).type_parameters.values().clone() + } + pub fn new_type_parameter( self, db: &mut Database, @@ -2392,18 +2583,12 @@ impl MethodId { self.get_mut(db).receiver = receiver; } - pub fn set_self_type(self, db: &mut Database, typ: TypeId) { - self.get_mut(db).self_type = Some(typ); - } - pub fn receiver(self, db: &Database) -> TypeRef { self.get(db).receiver } - pub fn self_type(self, db: &Database) -> TypeId { - self.get(db).self_type.expect( - "The method's Self type must be set before it can be obtained", - ) + pub fn receiver_id(self, db: &Database) -> TypeId { + self.get(db).receiver.type_id(db).unwrap() } pub fn source(self, db: &Database) -> MethodSource { @@ -2414,94 +2599,6 @@ impl MethodId { self.get_mut(db).source = source; } - pub fn type_check( - self, - db: &mut Database, - with: MethodId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db); - let theirs = with.get(db); - - if ours.kind != theirs.kind { - return false; - } - - if ours.visibility != theirs.visibility { - return false; - } - - if ours.name != theirs.name { - return false; - } - - // These checks are performed in separate methods so we can avoid - // borrowing conflicts of the type database. - self.type_check_type_parameters(db, with, context) - && self.type_check_arguments(db, with, context) - && self.type_check_throw_type(db, with, context) - && self.type_check_return_type(db, with, context) - } - - fn type_check_type_parameters( - self, - db: &mut Database, - with: MethodId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db); - let theirs = with.get(db); - - if ours.type_parameters.len() != theirs.type_parameters.len() { - return false; - } - - ours.type_parameters - .values() - .clone() - .into_iter() - .zip(theirs.type_parameters.values().clone().into_iter()) - .all(|(ours, theirs)| { - ours.type_check_with_type_parameter(db, theirs, context, false) - }) - } - - fn type_check_arguments( - self, - db: &mut Database, - with: MethodId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db).arguments.clone(); - let theirs = with.get(db).arguments.clone(); - - ours.type_check(db, &theirs, context, true) - } - - fn type_check_return_type( - self, - db: &mut Database, - with: MethodId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db).return_type; - let theirs = with.get(db).return_type; - - ours.type_check(db, theirs, context, true) - } - - fn type_check_throw_type( - self, - db: &mut Database, - with: MethodId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db).throw_type; - let theirs = with.get(db).throw_type; - - ours.type_check(db, theirs, context, true) - } - pub fn name(self, db: &Database) -> &String { &self.get(db).name } @@ -2613,7 +2710,7 @@ impl MethodId { self.get(db).field_types.values().cloned().collect() } - pub fn add_argument(&self, db: &mut Database, argument: Argument) { + pub fn add_argument(self, db: &mut Database, argument: Argument) { self.get_mut(db).arguments.new_argument( argument.name.clone(), argument.value_type, @@ -2621,14 +2718,22 @@ impl MethodId { ); } - pub fn set_main(&self, db: &mut Database) { + pub fn set_main(self, db: &mut Database) { self.get_mut(db).main = true; } - pub fn is_main(&self, db: &Database) -> bool { + pub fn is_main(self, db: &Database) -> bool { self.get(db).main } + pub fn bounds(self, db: &Database) -> &TypeBounds { + &self.get(db).bounds + } + + pub fn set_bounds(self, db: &mut Database, bounds: TypeBounds) { + self.get_mut(db).bounds = bounds; + } + fn get(self, db: &Database) -> &Method { &db.methods[self.0] } @@ -2652,68 +2757,15 @@ impl Block for MethodId { var } - fn set_throw_type(&self, db: &mut Database, typ: TypeRef) { - self.get_mut(db).throw_type = typ; - } - fn set_return_type(&self, db: &mut Database, typ: TypeRef) { self.get_mut(db).return_type = typ; } - fn throw_type(&self, db: &Database) -> TypeRef { - self.get(db).throw_type - } - fn return_type(&self, db: &Database) -> TypeRef { self.get(db).return_type } } -impl FormatType for MethodId { - fn format_type(&self, buffer: &mut TypeFormatter) { - let block = self.get(buffer.db); - - buffer.write("fn "); - - if block.visibility == Visibility::Public { - buffer.write("pub "); - } - - match block.kind { - MethodKind::Async => buffer.write("async "), - MethodKind::AsyncMutable => buffer.write("async mut "), - MethodKind::Static => buffer.write("static "), - MethodKind::Moving => buffer.write("move "), - MethodKind::Mutable | MethodKind::Destructor => { - buffer.write("mut ") - } - _ => {} - } - - buffer.write(&block.name); - - if block.type_parameters.len() > 0 { - buffer.write(" ["); - - for (index, param) in - block.type_parameters.values().iter().enumerate() - { - if index > 0 { - buffer.write(", "); - } - - param.format_type(buffer); - } - - buffer.write("]"); - } - - buffer.arguments(&block.arguments, true); - buffer.throw_type(block.throw_type); - buffer.return_type(block.return_type); - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub enum Receiver { /// The receiver is explicit (e.g. `foo.bar()`) @@ -2753,7 +2805,6 @@ pub struct CallInfo { pub id: MethodId, pub receiver: Receiver, pub returns: TypeRef, - pub throws: TypeRef, pub dynamic: bool, } @@ -2762,18 +2813,17 @@ pub struct ClosureCallInfo { pub id: ClosureId, pub expected_arguments: Vec, pub returns: TypeRef, - pub throws: TypeRef, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct BuiltinCallInfo { - pub id: BuiltinFunctionId, + pub id: BuiltinFunction, pub returns: TypeRef, - pub throws: TypeRef, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct FieldInfo { + pub class: ClassId, pub id: FieldId, pub variable_type: TypeRef, } @@ -2782,7 +2832,7 @@ pub struct FieldInfo { pub enum CallKind { Unknown, Call(CallInfo), - ClosureCall(ClosureCallInfo), + CallClosure(ClosureCallInfo), GetField(FieldInfo), SetField(FieldInfo), } @@ -2799,6 +2849,7 @@ pub enum IdentifierKind { pub enum ConstantKind { Unknown, Constant(ConstantId), + Builtin(BuiltinConstant), Method(CallInfo), } @@ -2810,6 +2861,40 @@ pub enum ConstantPatternKind { Int(ConstantId), } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ThrowKind { + Unknown, + Option(TypeRef), + Result(TypeRef, TypeRef), +} + +impl ThrowKind { + pub fn throw_type_name(self, db: &Database, ok: TypeRef) -> String { + match self { + ThrowKind::Unknown => "?".to_string(), + ThrowKind::Option(_) => { + format!("Option[{}]", format::format_type(db, ok)) + } + ThrowKind::Result(_, err) => { + format!( + "Result[{}, {}]", + format::format_type(db, ok), + format::format_type(db, err) + ) + } + } + } + + pub fn as_uni(self, db: &Database) -> ThrowKind { + match self { + ThrowKind::Result(ok, err) if err.is_owned(db) => { + ThrowKind::Result(ok, err.as_uni(db)) + } + kind => kind, + } + } +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Symbol { Class(ClassId), @@ -2867,7 +2952,7 @@ impl Module { let class_id = Class::alloc( db, name.to_string(), - ClassKind::Regular, + ClassKind::Module, Visibility::Private, id, ); @@ -2945,12 +3030,6 @@ impl ModuleId { } } -impl FormatType for ModuleId { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.write(&self.get(buffer.db).name.to_string()); - } -} - /// A local variable. pub struct Variable { /// The user-defined name of the variable. @@ -3003,7 +3082,6 @@ impl VariableId { /// Unlike variables, constants can't be assigned new values. They are also /// limited to values of a select few types. pub struct Constant { - /// The ID of the constant local to its module. id: u16, module: ModuleId, name: String, @@ -3091,7 +3169,6 @@ pub struct Closure { /// The type of `self` as captured by the closure. captured_self_type: Option, arguments: Arguments, - throw_type: TypeRef, return_type: TypeRef, } @@ -3102,7 +3179,7 @@ impl Closure { Self::add(db, closure) } - fn add(db: &mut Database, closure: Closure) -> ClosureId { + pub(crate) fn add(db: &mut Database, closure: Closure) -> ClosureId { let id = db.closures.len(); db.closures.push(closure); @@ -3115,7 +3192,6 @@ impl Closure { captured_self_type: None, captured: HashSet::new(), arguments: Arguments::new(), - throw_type: TypeRef::Never, return_type: TypeRef::Unknown, } } @@ -3183,8 +3259,13 @@ impl ClosureId { pub fn can_infer_as_uni(self, db: &Database) -> bool { let closure = self.get(db); + let allow_captures = if closure.moving { + closure.captured.iter().all(|v| v.value_type(db).is_sendable(db)) + } else { + closure.captured.is_empty() + }; - if !closure.captured.is_empty() { + if !allow_captures { return false; } @@ -3195,110 +3276,13 @@ impl ClosureId { } } - fn type_check( - self, - db: &mut Database, - with: TypeId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - match with { - TypeId::Closure(with) => { - self.type_check_arguments(db, with, context) - && self.type_check_throw_type(db, with, context, subtyping) - && self.type_check_return_type(db, with, context, subtyping) - } - // Implementing traits for closures with a specific signature (e.g. - // `impl ToString for do -> X`) isn't supported. Even if it was, it - // probably wouldn't be useful. Implementing traits for all closures - // isn't supported either, again because it isn't really useful. - // - // For this reason, we only consider a closures compatible with a - // type parameter if the parameter has no requirements. This still - // allows you to e.g. put a bunch of lambdas in an Array. - TypeId::TypeParameter(id) => id.get(db).requirements.is_empty(), - _ => false, - } - } - - fn type_check_arguments( - self, - db: &mut Database, - with: ClosureId, - context: &mut TypeContext, - ) -> bool { - let ours = self.get(db).arguments.clone(); - let theirs = with.get(db).arguments.clone(); - - ours.type_check(db, &theirs, context, false) - } - - fn type_check_return_type( - self, - db: &mut Database, - with: ClosureId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - let ours = self.get(db).return_type; - let theirs = with.get(db).return_type; - - ours.type_check(db, theirs, context, subtyping) - } - - fn type_check_throw_type( - self, - db: &mut Database, - with: ClosureId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - let ours = self.get(db).throw_type; - let theirs = with.get(db).throw_type; - - ours.type_check(db, theirs, context, subtyping) - } - - fn get(self, db: &Database) -> &Closure { + pub(crate) fn get(self, db: &Database) -> &Closure { &db.closures[self.0] } fn get_mut(self, db: &mut Database) -> &mut Closure { &mut db.closures[self.0] } - - fn as_rigid_type(self, db: &mut Database, bounds: &TypeBounds) -> Self { - let mut new_func = self.get(db).clone(); - - for arg in new_func.arguments.mapping.values_mut() { - arg.value_type = arg.value_type.as_rigid_type(db, bounds); - } - - new_func.throw_type = new_func.throw_type.as_rigid_type(db, bounds); - new_func.return_type = new_func.return_type.as_rigid_type(db, bounds); - - Closure::add(db, new_func) - } - - fn inferred( - self, - db: &mut Database, - context: &mut TypeContext, - immutable: bool, - ) -> Self { - let mut new_func = self.get(db).clone(); - - for arg in new_func.arguments.mapping.values_mut() { - arg.value_type = arg.value_type.inferred(db, context, immutable); - } - - new_func.throw_type = - new_func.throw_type.inferred(db, context, immutable); - new_func.return_type = - new_func.return_type.inferred(db, context, immutable); - - Closure::add(db, new_func) - } } impl Block for ClosureId { @@ -3315,41 +3299,15 @@ impl Block for ClosureId { var } - fn set_throw_type(&self, db: &mut Database, typ: TypeRef) { - self.get_mut(db).throw_type = typ; - } - fn set_return_type(&self, db: &mut Database, typ: TypeRef) { self.get_mut(db).return_type = typ; } - fn throw_type(&self, db: &Database) -> TypeRef { - self.get(db).throw_type - } - fn return_type(&self, db: &Database) -> TypeRef { self.get(db).return_type } } -impl FormatType for ClosureId { - fn format_type(&self, buffer: &mut TypeFormatter) { - buffer.descend(|buffer| { - let fun = self.get(buffer.db); - - if fun.moving { - buffer.write("fn move"); - } else { - buffer.write("fn"); - } - - buffer.arguments(&fun.arguments, false); - buffer.throw_type(fun.throw_type); - buffer.return_type(fun.return_type); - }); - } -} - /// A reference to a type. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum TypeRef { @@ -3380,8 +3338,7 @@ pub enum TypeRef { /// A type that signals something never happens. /// - /// When used as a return type, it means a method never returns. When used - /// as an (explicit) throw type, it means the method never throws. + /// When used as a return type, it means a method never returns. Never, /// A value that could be anything _including_ non-managed objects. @@ -3399,18 +3356,6 @@ pub enum TypeRef { /// transferred. RefAny, - /// The `Self` type. - OwnedSelf, - - /// The `uni Self` type. - UniSelf, - - /// The `ref Self` type. - RefSelf, - - /// The `mut Self` type. - MutSelf, - /// A value indicating a typing error. /// /// This type is produced whenever a type couldn't be produced, for example @@ -3427,38 +3372,6 @@ pub enum TypeRef { } impl TypeRef { - fn mut_or_ref(id: TypeId, immutable: bool) -> TypeRef { - if immutable { - TypeRef::Ref(id) - } else { - TypeRef::Mut(id) - } - } - - fn mut_or_ref_uni(id: TypeId, immutable: bool) -> TypeRef { - if immutable { - TypeRef::RefUni(id) - } else { - TypeRef::MutUni(id) - } - } - - fn owned_or_ref(id: TypeId, immutable: bool) -> TypeRef { - if immutable { - TypeRef::Ref(id) - } else { - TypeRef::Owned(id) - } - } - - fn uni_or_ref(id: TypeId, immutable: bool) -> TypeRef { - if immutable { - TypeRef::RefUni(id) - } else { - TypeRef::Uni(id) - } - } - pub fn nil() -> TypeRef { TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(ClassId( NIL_ID, @@ -3495,33 +3408,18 @@ impl TypeRef { ))) } - pub fn array(db: &mut Database, value: TypeRef) -> TypeRef { - let array_class = ClassId::array(); - let mut arguments = TypeArguments::new(); - let param = array_class.type_parameters(db)[0]; - - arguments.assign(param, value); - - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::generic( - db, - array_class, - arguments, - ))) - } - pub fn module(id: ModuleId) -> TypeRef { TypeRef::Owned(TypeId::Module(id)) } - pub fn placeholder(db: &mut Database) -> TypeRef { - TypeRef::Placeholder(TypePlaceholder::alloc(db)) + pub fn placeholder( + db: &mut Database, + required: Option, + ) -> TypeRef { + TypeRef::Placeholder(TypePlaceholder::alloc(db, required)) } - pub fn type_id( - self, - db: &Database, - self_type: TypeId, - ) -> Result { + pub fn type_id(self, db: &Database) -> Result { match self { TypeRef::Owned(id) | TypeRef::Uni(id) @@ -3530,23 +3428,15 @@ impl TypeRef { | TypeRef::RefUni(id) | TypeRef::MutUni(id) | TypeRef::Infer(id) => Ok(id), - TypeRef::OwnedSelf - | TypeRef::RefSelf - | TypeRef::MutSelf - | TypeRef::UniSelf => Ok(self_type), TypeRef::Placeholder(id) => { - id.value(db).ok_or(self).and_then(|t| t.type_id(db, self_type)) + id.value(db).ok_or(self).and_then(|t| t.type_id(db)) } _ => Err(self), } } - pub fn closure_id( - self, - db: &Database, - self_type: TypeId, - ) -> Option { - if let Ok(TypeId::Closure(id)) = self.type_id(db, self_type) { + pub fn closure_id(self, db: &Database) -> Option { + if let Ok(TypeId::Closure(id)) = self.type_id(db) { Some(id) } else { None @@ -3608,8 +3498,6 @@ impl TypeRef { TypeRef::Owned(_) | TypeRef::Uni(_) | TypeRef::Infer(_) - | TypeRef::UniSelf - | TypeRef::OwnedSelf | TypeRef::Any => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.is_owned_or_uni(db)) @@ -3620,10 +3508,7 @@ impl TypeRef { pub fn is_owned(self, db: &Database) -> bool { match self { - TypeRef::Owned(_) - | TypeRef::Infer(_) - | TypeRef::OwnedSelf - | TypeRef::Any => true, + TypeRef::Owned(_) | TypeRef::Infer(_) | TypeRef::Any => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.is_owned(db)) } @@ -3669,19 +3554,50 @@ impl TypeRef { } } - pub fn is_self_type(self, db: &Database) -> bool { + pub fn is_generic(self, db: &Database) -> bool { match self { - TypeRef::OwnedSelf | TypeRef::MutSelf | TypeRef::RefSelf => true, + TypeRef::Owned(TypeId::TraitInstance(ins)) + | TypeRef::Uni(TypeId::TraitInstance(ins)) + | TypeRef::Ref(TypeId::TraitInstance(ins)) + | TypeRef::Mut(TypeId::TraitInstance(ins)) + | TypeRef::RefUni(TypeId::TraitInstance(ins)) + | TypeRef::MutUni(TypeId::TraitInstance(ins)) => { + ins.instance_of.is_generic(db) + } + TypeRef::Owned(TypeId::ClassInstance(ins)) + | TypeRef::Uni(TypeId::ClassInstance(ins)) + | TypeRef::Ref(TypeId::ClassInstance(ins)) + | TypeRef::Mut(TypeId::ClassInstance(ins)) + | TypeRef::RefUni(TypeId::ClassInstance(ins)) + | TypeRef::MutUni(TypeId::ClassInstance(ins)) => { + ins.instance_of.is_generic(db) + } TypeRef::Placeholder(id) => { - id.value(db).map_or(false, |v| v.is_self_type(db)) + id.value(db).map_or(false, |v| v.is_generic(db)) } _ => false, } } + pub fn type_arguments(self, db: &Database) -> TypeArguments { + match self.type_id(db) { + Ok(TypeId::TraitInstance(ins)) + if ins.instance_of.is_generic(db) => + { + ins.type_arguments(db).clone() + } + Ok(TypeId::ClassInstance(ins)) + if ins.instance_of.is_generic(db) => + { + ins.type_arguments(db).clone() + } + _ => TypeArguments::new(), + } + } + pub fn is_uni(self, db: &Database) -> bool { match self { - TypeRef::Uni(_) | TypeRef::UniSelf => true, + TypeRef::Uni(_) => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.is_uni(db)) } @@ -3691,10 +3607,7 @@ impl TypeRef { pub fn require_sendable_arguments(self, db: &Database) -> bool { match self { - TypeRef::Uni(_) - | TypeRef::RefUni(_) - | TypeRef::MutUni(_) - | TypeRef::UniSelf => true, + TypeRef::Uni(_) | TypeRef::RefUni(_) | TypeRef::MutUni(_) => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.require_sendable_arguments(db)) } @@ -3704,7 +3617,7 @@ impl TypeRef { pub fn is_ref(self, db: &Database) -> bool { match self { - TypeRef::Ref(_) | TypeRef::RefSelf => true, + TypeRef::Ref(_) => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.is_ref(db)) } @@ -3714,7 +3627,7 @@ impl TypeRef { pub fn is_mut(self, db: &Database) -> bool { match self { - TypeRef::Mut(_) | TypeRef::MutSelf => true, + TypeRef::Mut(_) => true, TypeRef::Placeholder(id) => { id.value(db).map_or(false, |v| v.is_ref(db)) } @@ -3722,12 +3635,26 @@ impl TypeRef { } } + pub fn is_mutable(self, db: &Database) -> bool { + match self { + TypeRef::Owned(_) + | TypeRef::Uni(_) + | TypeRef::Mut(_) + | TypeRef::Infer(_) + | TypeRef::Error + | TypeRef::Unknown + | TypeRef::Never => true, + TypeRef::Placeholder(id) => { + id.value(db).map_or(true, |v| v.is_mutable(db)) + } + _ => false, + } + } + pub fn use_reference_counting(self, db: &Database) -> bool { match self { TypeRef::Ref(_) - | TypeRef::RefSelf | TypeRef::Mut(_) - | TypeRef::MutSelf | TypeRef::RefUni(_) | TypeRef::MutUni(_) => true, TypeRef::Placeholder(id) => { @@ -3737,38 +3664,34 @@ impl TypeRef { } } - pub fn use_atomic_reference_counting( - self, - db: &Database, - self_type: TypeId, - ) -> bool { - self.class_id(db, self_type) - .map_or(false, |id| id.0 == STRING_ID || id.kind(db).is_async()) + pub fn use_atomic_reference_counting(self, db: &Database) -> bool { + self.class_id(db).map_or(false, |id| id.is_atomic(db)) + } + + pub fn is_bool(self, db: &Database) -> bool { + self.is_instance_of(db, ClassId::boolean()) } - pub fn is_bool(self, db: &Database, self_type: TypeId) -> bool { - self.is_instance_of(db, ClassId::boolean(), self_type) + pub fn is_int(self, db: &Database) -> bool { + self.is_instance_of(db, ClassId::int()) } - pub fn is_string(self, db: &Database, self_type: TypeId) -> bool { - self.is_instance_of(db, ClassId::string(), self_type) + pub fn is_string(self, db: &Database) -> bool { + self.is_instance_of(db, ClassId::string()) } - pub fn is_nil(self, db: &Database, self_type: TypeId) -> bool { - self.is_instance_of(db, ClassId::nil(), self_type) + pub fn is_nil(self, db: &Database) -> bool { + self.is_instance_of(db, ClassId::nil()) } pub fn allow_moving(self) -> bool { - matches!(self, TypeRef::Owned(_) | TypeRef::Uni(_) | TypeRef::OwnedSelf) + matches!(self, TypeRef::Owned(_) | TypeRef::Uni(_)) } pub fn allow_mutating(self) -> bool { matches!( self, TypeRef::Mut(_) - | TypeRef::MutSelf - | TypeRef::OwnedSelf - | TypeRef::UniSelf | TypeRef::Owned(_) | TypeRef::Uni(_) | TypeRef::MutUni(_) @@ -3791,10 +3714,7 @@ impl TypeRef { } match self { - TypeRef::Uni(_) - | TypeRef::UniSelf - | TypeRef::Never - | TypeRef::Error => true, + TypeRef::Uni(_) | TypeRef::Never | TypeRef::Error => true, TypeRef::Placeholder(id) => { id.value(db).map_or(true, |v| v.is_sendable(db)) } @@ -3809,7 +3729,6 @@ impl TypeRef { match self { TypeRef::Uni(_) - | TypeRef::UniSelf | TypeRef::Never | TypeRef::Any | TypeRef::Error => true, @@ -3838,10 +3757,6 @@ impl TypeRef { } } - pub fn is_async(self, db: &Database, self_type: TypeId) -> bool { - self.class_id(db, self_type).map_or(false, |id| id.kind(db).is_async()) - } - pub fn cast_according_to(self, other: Self, db: &Database) -> Self { if other.is_uni(db) && self.is_value_type(db) { self.as_uni(db) @@ -3873,9 +3788,6 @@ impl TypeRef { TypeRef::Ref(id) } TypeRef::Uni(id) => TypeRef::RefUni(id), - TypeRef::OwnedSelf => TypeRef::RefSelf, - TypeRef::MutSelf => TypeRef::RefSelf, - TypeRef::UniSelf => TypeRef::RefSelf, TypeRef::Placeholder(id) => { id.value(db).map_or(self, |v| v.as_ref(db)) } @@ -3883,23 +3795,52 @@ impl TypeRef { } } - pub fn as_mut(self, db: &Database) -> Self { + pub fn allow_as_ref(self, db: &Database) -> bool { match self { - TypeRef::Owned(id) | TypeRef::Infer(id) => TypeRef::Mut(id), - TypeRef::Uni(id) => TypeRef::MutUni(id), - TypeRef::OwnedSelf | TypeRef::UniSelf => TypeRef::MutSelf, + TypeRef::Any => true, + TypeRef::Owned(_) | TypeRef::Mut(_) | TypeRef::Ref(_) => true, TypeRef::Placeholder(id) => { - id.value(db).map_or(self, |v| v.as_mut(db)) + id.value(db).map_or(false, |v| v.allow_as_ref(db)) } - _ => self, + _ => false, } } - pub fn as_ref_uni(self, db: &Database) -> Self { + pub fn allow_as_mut(self, db: &Database) -> bool { match self { - TypeRef::Uni(id) => TypeRef::RefUni(id), + TypeRef::Any => true, + TypeRef::Owned(TypeId::RigidTypeParameter(id)) => id.is_mutable(db), + TypeRef::Owned(_) | TypeRef::Mut(_) => true, TypeRef::Placeholder(id) => { - id.value(db).map_or(self, |v| v.as_ref_uni(db)) + id.value(db).map_or(false, |v| v.allow_as_mut(db)) + } + _ => false, + } + } + + pub fn as_mut(self, db: &Database) -> Self { + match self { + TypeRef::Owned(TypeId::RigidTypeParameter(id)) => { + if id.is_mutable(db) { + TypeRef::Mut(TypeId::RigidTypeParameter(id)) + } else { + self + } + } + TypeRef::Owned(id) => TypeRef::Mut(id), + TypeRef::Uni(id) => TypeRef::MutUni(id), + TypeRef::Placeholder(id) => { + id.value(db).map_or(self, |v| v.as_mut(db)) + } + _ => self, + } + } + + pub fn as_ref_uni(self, db: &Database) -> Self { + match self { + TypeRef::Uni(id) => TypeRef::RefUni(id), + TypeRef::Placeholder(id) => { + id.value(db).map_or(self, |v| v.as_ref_uni(db)) } _ => self, } @@ -3922,7 +3863,6 @@ impl TypeRef { | TypeRef::Uni(id) | TypeRef::Mut(id) | TypeRef::Ref(id) => TypeRef::Uni(id), - TypeRef::OwnedSelf | TypeRef::UniSelf => TypeRef::UniSelf, TypeRef::Placeholder(id) => { id.value(db).map_or(self, |v| v.as_uni(db)) } @@ -3937,9 +3877,6 @@ impl TypeRef { | TypeRef::Mut(id) | TypeRef::RefUni(id) | TypeRef::MutUni(id) => TypeRef::Owned(id), - TypeRef::UniSelf | TypeRef::MutSelf | TypeRef::RefSelf => { - TypeRef::OwnedSelf - } TypeRef::Placeholder(id) => { id.value(db).map_or(self, |v| v.as_owned(db)) } @@ -3947,192 +3884,7 @@ impl TypeRef { } } - /// Replaces temporary types with their inferred types. - pub fn inferred( - self, - db: &mut Database, - context: &mut TypeContext, - immutable: bool, - ) -> TypeRef { - if context.depth == MAX_TYPE_DEPTH { - return TypeRef::Unknown; - } - - context.depth += 1; - - let result = match self { - TypeRef::OwnedSelf => TypeRef::owned_or_ref( - self.infer_self_type_id(db, context), - immutable, - ), - TypeRef::RefSelf => { - TypeRef::Ref(self.infer_self_type_id(db, context)) - } - TypeRef::MutSelf => TypeRef::mut_or_ref( - self.infer_self_type_id(db, context), - immutable, - ), - TypeRef::UniSelf => TypeRef::uni_or_ref( - self.infer_self_type_id(db, context), - immutable, - ), - // Owned and Infer variants are treated the same, because: - // - // 1. For non-type parameters, Infer(T) is the same as Owned(T) - // (simply because we never use Infer for anything but type - // parameters). - // 2. For a type parameter, in both cases we can just use the - // assigned value if there is any, as said value determines the - // ownership. In case of an owned parameter non-owned values - // can't be assigned to it anyway. - TypeRef::Owned(id) | TypeRef::Infer(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::owned_or_ref( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TraitInstance(tid) => TypeRef::owned_or_ref( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TypeParameter(pid) => { - let typ = - self.infer_type_parameter(pid, db, context, immutable); - - if immutable { - typ.as_ref(db) - } else { - typ - } - } - TypeId::Closure(fid) => TypeRef::owned_or_ref( - TypeId::Closure(fid.inferred(db, context, immutable)), - immutable, - ), - _ => self, - }, - TypeRef::Uni(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::uni_or_ref( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TraitInstance(tid) => TypeRef::uni_or_ref( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TypeParameter(pid) => { - let typ = - self.infer_type_parameter(pid, db, context, immutable); - - if immutable { - typ.as_ref(db) - } else { - typ - } - } - TypeId::Closure(fid) => TypeRef::uni_or_ref( - TypeId::Closure(fid.inferred(db, context, immutable)), - immutable, - ), - _ => self, - }, - TypeRef::Ref(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::Ref( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - ), - TypeId::TraitInstance(tid) => TypeRef::Ref( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - ), - TypeId::TypeParameter(pid) => self - .infer_type_parameter(pid, db, context, immutable) - .as_ref(db), - TypeId::Closure(fid) => TypeRef::Ref(TypeId::Closure( - fid.inferred(db, context, immutable), - )), - _ => self, - }, - TypeRef::RefUni(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::RefUni( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - ), - TypeId::TraitInstance(tid) => TypeRef::RefUni( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - ), - TypeId::TypeParameter(pid) => self - .infer_type_parameter(pid, db, context, immutable) - .as_ref_uni(db), - TypeId::Closure(fid) => TypeRef::RefUni(TypeId::Closure( - fid.inferred(db, context, immutable), - )), - _ => self, - }, - TypeRef::Mut(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::mut_or_ref( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TraitInstance(tid) => TypeRef::mut_or_ref( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TypeParameter(pid) => { - let typ = - self.infer_type_parameter(pid, db, context, immutable); - - if immutable { - typ.as_ref(db) - } else { - typ.as_mut(db) - } - } - TypeId::Closure(fid) => TypeRef::mut_or_ref( - TypeId::Closure(fid.inferred(db, context, immutable)), - immutable, - ), - _ => self, - }, - TypeRef::MutUni(id) => match id { - TypeId::ClassInstance(cid) => TypeRef::mut_or_ref_uni( - TypeId::ClassInstance(cid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TraitInstance(tid) => TypeRef::mut_or_ref_uni( - TypeId::TraitInstance(tid.inferred(db, context, immutable)), - immutable, - ), - TypeId::TypeParameter(pid) => { - let typ = - self.infer_type_parameter(pid, db, context, immutable); - - if immutable { - typ.as_ref_uni(db) - } else { - typ.as_mut_uni(db) - } - } - TypeId::Closure(fid) => TypeRef::mut_or_ref_uni( - TypeId::Closure(fid.inferred(db, context, immutable)), - immutable, - ), - _ => self, - }, - TypeRef::Placeholder(id) => id - .value(db) - .map(|t| t.inferred(db, context, immutable)) - .unwrap_or_else( - || if immutable { self.as_ref(db) } else { self }, - ), - _ => self, - }; - - context.depth -= 1; - result - } - - pub fn as_enum_instance( - self, - db: &Database, - self_type: TypeId, - ) -> Option { + pub fn as_enum_instance(self, db: &Database) -> Option { match self { TypeRef::Owned(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) @@ -4142,17 +3894,6 @@ impl TypeRef { { Some(ins) } - TypeRef::OwnedSelf - | TypeRef::RefSelf - | TypeRef::MutSelf - | TypeRef::UniSelf => match self_type { - TypeId::ClassInstance(ins) - if ins.instance_of.kind(db).is_enum() => - { - Some(ins) - } - _ => None, - }, _ => None, } } @@ -4198,102 +3939,19 @@ impl TypeRef { } } - fn is_regular_type_parameter(self) -> bool { - matches!( - self, - TypeRef::Owned(TypeId::TypeParameter(_)) - | TypeRef::Uni(TypeId::TypeParameter(_)) - | TypeRef::Ref(TypeId::TypeParameter(_)) - | TypeRef::Mut(TypeId::TypeParameter(_)) - | TypeRef::Infer(TypeId::TypeParameter(_)) - | TypeRef::RefUni(TypeId::TypeParameter(_)) - | TypeRef::MutUni(TypeId::TypeParameter(_)) - ) - } - - pub fn is_compatible_with_type_parameter( - self, - db: &mut Database, - parameter: TypeParameterId, - context: &mut TypeContext, - ) -> bool { - parameter - .requirements(db) - .into_iter() - .all(|r| self.implements_trait_instance(db, r, context)) - } - - pub fn allow_cast_to( - self, - db: &mut Database, - with: TypeRef, - context: &mut TypeContext, - ) -> bool { - // Casting to/from Any is dangerous but necessary to make the standard - // library work. - if self == TypeRef::Any || with == TypeRef::Any { - return true; - } - - self.type_check_directly(db, with, context, true) - } - pub fn as_rigid_type(self, db: &mut Database, bounds: &TypeBounds) -> Self { - match self { - TypeRef::Owned(id) => TypeRef::Owned(id.as_rigid_type(db, bounds)), - TypeRef::Uni(id) => TypeRef::Uni(id.as_rigid_type(db, bounds)), - TypeRef::Ref(id) => TypeRef::Ref(id.as_rigid_type(db, bounds)), - TypeRef::Mut(id) => TypeRef::Mut(id.as_rigid_type(db, bounds)), - TypeRef::Infer(id) => TypeRef::Owned(id.as_rigid_type(db, bounds)), - _ => self, - } - } - - pub fn implements_trait_instance( - self, - db: &mut Database, - trait_type: TraitInstance, - context: &mut TypeContext, - ) -> bool { - match self { - TypeRef::Any => false, - TypeRef::Error => true, - TypeRef::Never => true, - TypeRef::OwnedSelf - | TypeRef::RefSelf - | TypeRef::MutSelf - | TypeRef::UniSelf => context - .self_type - .implements_trait_instance(db, trait_type, context), - TypeRef::Owned(id) - | TypeRef::Uni(id) - | TypeRef::Ref(id) - | TypeRef::Mut(id) - | TypeRef::Infer(id) => { - id.implements_trait_instance(db, trait_type, context) - } - TypeRef::Placeholder(id) => id.value(db).map_or(true, |v| { - v.implements_trait_instance(db, trait_type, context) - }), - _ => false, - } + TypeResolver::new(db, &TypeArguments::new(), bounds) + .with_rigid(true) + .resolve(self) } pub fn is_value_type(self, db: &Database) -> bool { match self { - TypeRef::Owned(TypeId::ClassInstance(ins)) - if ins.instance_of.kind(db).is_async() => - { - true - } TypeRef::Owned(TypeId::ClassInstance(ins)) | TypeRef::Ref(TypeId::ClassInstance(ins)) | TypeRef::Mut(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) => { - matches!( - ins.instance_of.0, - INT_ID | FLOAT_ID | STRING_ID | BOOLEAN_ID | NIL_ID - ) + ins.instance_of().get(db).value_type } TypeRef::Placeholder(id) => { id.value(db).map_or(true, |v| v.is_value_type(db)) @@ -4308,7 +3966,8 @@ impl TypeRef { | TypeRef::Ref(TypeId::ClassInstance(ins)) | TypeRef::Mut(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) => { - matches!(ins.instance_of.0, BOOLEAN_ID | NIL_ID) + ins.instance_of.kind(db).is_extern() + || matches!(ins.instance_of.0, BOOLEAN_ID | NIL_ID) } TypeRef::Owned(TypeId::Module(_)) => true, TypeRef::Owned(TypeId::Class(_)) => true, @@ -4330,682 +3989,181 @@ impl TypeRef { | TypeRef::Mut(id) | TypeRef::RefUni(id) | TypeRef::MutUni(id) - | TypeRef::Infer(id) => { - let args = match id { - TypeId::ClassInstance(ins) - if ins.instance_of.is_generic(db) => - { - ins.type_arguments(db) - } - TypeId::TraitInstance(ins) - if ins.instance_of.is_generic(db) => - { - ins.type_arguments(db) - } - _ => return true, - }; - - args.mapping.values().all(|v| v.is_inferred(db)) + | TypeRef::Infer(id) => match id { + TypeId::ClassInstance(ins) + if ins.instance_of.is_generic(db) => + { + ins.type_arguments(db) + .mapping + .values() + .all(|v| v.is_inferred(db)) + } + TypeId::TraitInstance(ins) + if ins.instance_of.is_generic(db) => + { + ins.type_arguments(db) + .mapping + .values() + .all(|v| v.is_inferred(db)) + } + TypeId::Closure(id) => { + id.arguments(db) + .into_iter() + .all(|arg| arg.value_type.is_inferred(db)) + && id.return_type(db).is_inferred(db) + } + _ => true, + }, + TypeRef::Placeholder(id) => { + id.value(db).map_or(false, |v| v.is_inferred(db)) } - TypeRef::Placeholder(id) => id.value(db).is_some(), _ => true, } } - pub fn implements_trait_id( - self, - db: &Database, - trait_id: TraitId, - self_type: TypeId, - ) -> bool { + pub fn class_id(self, db: &Database) -> Option { match self { - TypeRef::Any => false, - TypeRef::Error => false, - TypeRef::Never => false, - TypeRef::OwnedSelf - | TypeRef::RefSelf - | TypeRef::MutSelf - | TypeRef::UniSelf => self_type.implements_trait_id(db, trait_id), - TypeRef::Owned(id) - | TypeRef::Uni(id) - | TypeRef::Ref(id) - | TypeRef::Mut(id) - | TypeRef::Infer(id) => id.implements_trait_id(db, trait_id), - _ => false, + TypeRef::Owned(TypeId::ClassInstance(ins)) + | TypeRef::Uni(TypeId::ClassInstance(ins)) + | TypeRef::Ref(TypeId::ClassInstance(ins)) + | TypeRef::Mut(TypeId::ClassInstance(ins)) => Some(ins.instance_of), + TypeRef::Placeholder(p) => p.value(db).and_then(|v| v.class_id(db)), + _ => None, } } - pub fn class_id(self, db: &Database, self_type: TypeId) -> Option { + pub fn throw_kind(self, db: &Database) -> ThrowKind { match self { TypeRef::Owned(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) | TypeRef::Ref(TypeId::ClassInstance(ins)) - | TypeRef::Mut(TypeId::ClassInstance(ins)) => Some(ins.instance_of), - TypeRef::OwnedSelf | TypeRef::RefSelf | TypeRef::MutSelf => { - match self_type { - TypeId::ClassInstance(ins) => Some(ins.instance_of), - _ => None, + | TypeRef::Mut(TypeId::ClassInstance(ins)) => { + let opt_class = db.class_in_module(OPTION_MODULE, OPTION_CLASS); + let res_class = db.class_in_module(RESULT_MODULE, RESULT_CLASS); + let params = ins.instance_of.type_parameters(db); + + if ins.instance_of == res_class { + let args = ins.type_arguments(db); + let ok = args.get(params[0]).unwrap(); + let err = args.get(params[1]).unwrap(); + + ThrowKind::Result(ok, err) + } else if ins.instance_of == opt_class { + let args = ins.type_arguments(db); + let some = args.get(params[0]).unwrap(); + + ThrowKind::Option(some) + } else { + ThrowKind::Unknown } } TypeRef::Placeholder(p) => { - p.value(db).and_then(|v| v.class_id(db, self_type)) + p.value(db).map_or(ThrowKind::Unknown, |v| v.throw_kind(db)) } - _ => None, + _ => ThrowKind::Unknown, } } - pub fn type_check( - self, - db: &mut Database, - with: TypeRef, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - // We special-case type parameters on the right-hand side here, that way - // we don't need to cover this case for all the various TypeRef variants - // individually. - match with { - TypeRef::Owned(TypeId::TypeParameter(pid)) - | TypeRef::Uni(TypeId::TypeParameter(pid)) - | TypeRef::Infer(TypeId::TypeParameter(pid)) - | TypeRef::RefUni(TypeId::TypeParameter(pid)) - | TypeRef::MutUni(TypeId::TypeParameter(pid)) - | TypeRef::Mut(TypeId::TypeParameter(pid)) - | TypeRef::Ref(TypeId::TypeParameter(pid)) => self - .type_check_with_type_parameter( - db, with, pid, context, subtyping, - ), - TypeRef::Placeholder(id) => { - if let Some(assigned) = id.value(db) { - self.type_check_directly(db, assigned, context, subtyping) - } else if let TypeRef::Placeholder(ours) = self { - // Assigning a placeholder to an unassigned placeholder - // isn't useful, and can break type inference when returning - // empty generic types in e.g. a closure (as this will - // compare them to a type placeholder). - // - // Instead, we track our placeholder in the one we're - // comparing with, ensuring our placeholder is also assigned - // when the one we're comparing with is assigned a value. - id.add_depending(db, ours); - true - } else { - id.assign(db, self); - true - } - } - _ => self.type_check_directly(db, with, context, subtyping), - } + fn is_instance_of(self, db: &Database, id: ClassId) -> bool { + self.class_id(db) == Some(id) } +} - fn type_check_with_type_parameter( - self, - db: &mut Database, - with: TypeRef, - param: TypeParameterId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - if let Some(assigned) = context.type_arguments.get(param) { - if let TypeRef::Placeholder(placeholder) = assigned { - let mut rhs = with; - let mut update = true; - - if let Some(val) = placeholder.value(db) { - rhs = val; - - // A placeholder may be assigned to a regular type - // parameter (not a rigid one). In this case we want to - // update the placeholder value to `self`. An example of - // where this can happen is the following: - // - // class Stack[V] { @values: Array[V] } - // Stack { @values = [] } - // - // Here `[]` is of type `Array[P]` where P is a placeholder. - // When assigned to `@values`, we end up assigning V to P, - // and P to V. - // - // If the parameter is rigid we have to leave it as-is, as - // inferring the types further is unsafe. - update = matches!( - val, - TypeRef::Owned(TypeId::TypeParameter(_)) - | TypeRef::Uni(TypeId::TypeParameter(_)) - | TypeRef::Ref(TypeId::TypeParameter(_)) - | TypeRef::Mut(TypeId::TypeParameter(_)) - | TypeRef::Infer(TypeId::TypeParameter(_)) - ); - } - - rhs = rhs.cast_according_to(with, db); +/// An ID pointing to a type. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub enum TypeId { + Class(ClassId), + Trait(TraitId), + Module(ModuleId), + ClassInstance(ClassInstance), + TraitInstance(TraitInstance), + TypeParameter(TypeParameterId), + RigidTypeParameter(TypeParameterId), + Closure(ClosureId), +} - let compat = - self.type_check_directly(db, rhs, context, subtyping); +impl TypeId { + pub fn named_type(self, db: &Database, name: &str) -> Option { + match self { + TypeId::Module(id) => id.symbol(db, name), + TypeId::Trait(id) => id.named_type(db, name), + TypeId::Class(id) => id.named_type(db, name), + TypeId::ClassInstance(id) => id.named_type(db, name), + TypeId::TraitInstance(id) => id.named_type(db, name), + _ => None, + } + } - if compat && update { - placeholder.assign(db, self); - } + pub fn lookup_method( + self, + db: &Database, + name: &str, + module: ModuleId, + allow_type_private: bool, + ) -> MethodLookup { + if let Some(id) = self.method(db, name) { + let kind = id.kind(db); + let is_ins = !matches!( + self, + TypeId::Class(_) | TypeId::Trait(_) | TypeId::Module(_) + ); - return compat; + if is_ins && kind == MethodKind::Static { + MethodLookup::StaticOnInstance + } else if !is_ins && kind != MethodKind::Static { + MethodLookup::InstanceOnStatic + } else if self.can_call(db, id, module, allow_type_private) { + MethodLookup::Ok(id) + } else { + MethodLookup::Private } - - return self.type_check_directly( - db, - assigned.cast_according_to(with, db), - context, - subtyping, - ); + } else { + MethodLookup::None } + } - if self.type_check_directly(db, with, context, subtyping) { - context.type_arguments.assign(param, self); - - return true; + pub fn method(self, db: &Database, name: &str) -> Option { + match self { + TypeId::Class(id) => id.method(db, name), + TypeId::Trait(id) => id.method(db, name), + TypeId::Module(id) => id.method(db, name), + TypeId::ClassInstance(id) => id.method(db, name), + TypeId::TraitInstance(id) => id.method(db, name), + TypeId::TypeParameter(id) | TypeId::RigidTypeParameter(id) => { + id.method(db, name) + } + TypeId::Closure(_) => None, } + } - false + pub fn use_dynamic_dispatch(self) -> bool { + matches!( + self, + TypeId::TraitInstance(_) + | TypeId::TypeParameter(_) + | TypeId::RigidTypeParameter(_) + ) } - fn type_check_with_type_placeholder( - self, - db: &mut Database, - with: TypePlaceholderId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - if let Some(assigned) = with.value(db) { - self.type_check(db, assigned, context, subtyping) + pub fn has_destructor(self, db: &Database) -> bool { + if let TypeId::ClassInstance(id) = self { + id.instance_of().has_destructor(db) } else { - with.assign(db, self); - true + false } } - fn type_check_directly( + fn can_call( self, - db: &mut Database, - with: TypeRef, - context: &mut TypeContext, - subtyping: bool, + db: &Database, + method: MethodId, + module: ModuleId, + allow_type_private: bool, ) -> bool { - // This case is the same for all variants of `self`, so we handle it - // here once. - if let TypeRef::Placeholder(id) = with { - return self - .type_check_with_type_placeholder(db, id, context, subtyping); - } - - match self { - TypeRef::Owned(our_id) => match with { - TypeRef::Owned(their_id) | TypeRef::Infer(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Ref(their_id) | TypeRef::Mut(their_id) - if self.is_value_type(db) => - { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Any | TypeRef::RefAny | TypeRef::Error => true, - TypeRef::OwnedSelf => { - our_id.type_check(db, context.self_type, context, subtyping) - } - _ => false, - }, - TypeRef::Uni(our_id) => match with { - TypeRef::Owned(their_id) - | TypeRef::Infer(their_id) - | TypeRef::Uni(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Any | TypeRef::RefAny | TypeRef::Error => true, - TypeRef::UniSelf => { - our_id.type_check(db, context.self_type, context, subtyping) - } - _ => false, - }, - TypeRef::RefUni(our_id) => match with { - TypeRef::RefUni(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Error => true, - _ => false, - }, - TypeRef::MutUni(our_id) => match with { - TypeRef::RefUni(their_id) | TypeRef::MutUni(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Error => true, - _ => false, - }, - TypeRef::Ref(our_id) => match with { - TypeRef::Ref(their_id) | TypeRef::Infer(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Owned(their_id) | TypeRef::Uni(their_id) - if self.is_value_type(db) => - { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Error => true, - TypeRef::RefSelf => { - our_id.type_check(db, context.self_type, context, subtyping) - } - _ => false, - }, - TypeRef::Mut(our_id) => match with { - TypeRef::Ref(their_id) | TypeRef::Infer(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Mut(their_id) => { - our_id.type_check(db, their_id, context, false) - } - TypeRef::Owned(their_id) | TypeRef::Uni(their_id) - if self.is_value_type(db) => - { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Error => true, - TypeRef::RefSelf => { - our_id.type_check(db, context.self_type, context, subtyping) - } - TypeRef::MutSelf => { - our_id.type_check(db, context.self_type, context, false) - } - _ => false, - }, - TypeRef::Infer(our_id) => match with { - TypeRef::Infer(their_id) => { - our_id.type_check(db, their_id, context, subtyping) - } - TypeRef::Error => true, - _ => false, - }, - // Since a Never can't actually be passed around, it's compatible - // with everything else. This allows for code like this: - // - // try foo else panic - // - // Where `panic` would return a `Never`. - TypeRef::Never => true, - TypeRef::OwnedSelf => match with { - TypeRef::Owned(their_id) | TypeRef::Infer(their_id) => context - .self_type - .type_check(db, their_id, context, subtyping), - TypeRef::Any - | TypeRef::RefAny - | TypeRef::Error - | TypeRef::OwnedSelf => true, - _ => false, - }, - TypeRef::RefSelf => match with { - TypeRef::Ref(their_id) | TypeRef::Infer(their_id) => context - .self_type - .type_check(db, their_id, context, subtyping), - TypeRef::Error | TypeRef::RefSelf => true, - _ => false, - }, - TypeRef::MutSelf => match with { - TypeRef::Mut(their_id) | TypeRef::Infer(their_id) => { - context.self_type.type_check(db, their_id, context, false) - } - TypeRef::Error | TypeRef::MutSelf => true, - _ => false, - }, - TypeRef::UniSelf => match with { - TypeRef::Owned(their_id) - | TypeRef::Uni(their_id) - | TypeRef::Infer(their_id) => { - context.self_type.type_check(db, their_id, context, false) - } - TypeRef::Any - | TypeRef::RefAny - | TypeRef::Error - | TypeRef::UniSelf - | TypeRef::OwnedSelf => true, - _ => false, - }, - // Type errors are compatible with all other types to prevent a - // cascade of type errors. - TypeRef::Error => true, - TypeRef::Any => { - matches!(with, TypeRef::Any | TypeRef::RefAny | TypeRef::Error) - } - TypeRef::RefAny => matches!(with, TypeRef::RefAny | TypeRef::Error), - TypeRef::Placeholder(id) => { - if let Some(assigned) = id.value(db) { - return assigned.type_check(db, with, context, subtyping); - } - - if !with.is_regular_type_parameter() { - // This is best explained with an example. Consider the - // following code: - // - // class Stack[X] { - // @values: Array[X] - // - // static fn new -> Self { - // Self { @values = [] } - // } - // } - // - // When the array is created, it's type is `Array[?]` where - // `?` is a placeholder. When assigned to `Array[X]`, we end - // up comparing the placeholder to `X`. The type of `X` in - // this case is `Infer(X)`, because we don't know the - // ownership at runtime. - // - // The return type is expected to be a rigid type (i.e. - // literally `Stack[X]` and not e.g. `Stack[Int]`). This - // creates a problem: Infer() isn't compatible with a rigid - // type parameter, so the above code would produce a type - // error. - // - // In addition, it introduces a cycle of `X -> ? -> X` - // that's not needed. - // - // To prevent both from happening, we _don't_ assign to the - // placeholder if the assigned value is a regular type - // parameter. Regular type parameters can't occur outside of - // method signatures, as they are either turned into rigid - // parameters or replaced with placeholders. - id.assign(db, with); - } - - true - } - _ => false, - } - } - - fn infer_self_type_id( - self, - db: &mut Database, - context: &TypeContext, - ) -> TypeId { - // Self types always refer to instances of a type, so if - // `context.self_type` is a class or trait, we need to turn it into an - // instance. - match context.self_type { - TypeId::Class(id) => { - let ins = if id.is_generic(db) { - let args = context - .type_arguments - .assigned_or_placeholders(db, id.type_parameters(db)); - - ClassInstance::generic(db, id, args) - } else { - ClassInstance::new(id) - }; - - TypeId::ClassInstance(ins) - } - TypeId::Trait(id) => { - let ins = if id.is_generic(db) { - let args = context - .type_arguments - .assigned_or_placeholders(db, id.type_parameters(db)); - - TraitInstance::generic(db, id, args) - } else { - TraitInstance::new(id) - }; - - TypeId::TraitInstance(ins) - } - val => val, - } - } - - fn infer_type_parameter( - self, - type_parameter: TypeParameterId, - db: &mut Database, - context: &mut TypeContext, - immutable: bool, - ) -> TypeRef { - if let Some(arg) = context.type_arguments.get(type_parameter) { - // Given a case of `A -> placeholder -> A`, this prevents us from - // recursing back into this code and eventually blowing up the - // stack. - if let TypeRef::Placeholder(id) = arg { - if id.value(db).map_or(false, |v| v == self) { - return arg; - } - } - - if arg == self { - return self; - } - - return arg.inferred(db, context, immutable); - } - - if let TypeId::TraitInstance(ins) = context.self_type { - if let Some(arg) = ins - .instance_of - .get(db) - .inherited_type_arguments - .get(type_parameter) - { - return arg.inferred(db, context, immutable); - } - } - - TypeRef::placeholder(db) - } - - fn format_self_type(self, buffer: &mut TypeFormatter) { - if let Some(val) = buffer.self_type { - val.format_type(buffer); - } else { - buffer.write("Self"); - } - } - - fn is_instance_of( - self, - db: &Database, - id: ClassId, - self_type: TypeId, - ) -> bool { - self.class_id(db, self_type) == Some(id) - } -} - -impl FormatType for TypeRef { - fn format_type(&self, buffer: &mut TypeFormatter) { - match self { - TypeRef::Owned(id) | TypeRef::Infer(id) => id.format_type(buffer), - TypeRef::Uni(id) => { - buffer.write_ownership("uni "); - id.format_type(buffer); - } - TypeRef::RefUni(id) => { - buffer.write_ownership("ref uni "); - id.format_type(buffer); - } - TypeRef::MutUni(id) => { - buffer.write_ownership("mut uni "); - id.format_type(buffer); - } - TypeRef::Ref(id) => { - buffer.write_ownership("ref "); - id.format_type(buffer); - } - TypeRef::Mut(id) => { - buffer.write_ownership("mut "); - id.format_type(buffer); - } - TypeRef::Never => buffer.write("Never"), - TypeRef::Any => buffer.write("Any"), - TypeRef::RefAny => buffer.write("ref Any"), - TypeRef::OwnedSelf => { - self.format_self_type(buffer); - } - TypeRef::RefSelf => { - buffer.write_ownership("ref "); - self.format_self_type(buffer); - } - TypeRef::MutSelf => { - buffer.write_ownership("mut "); - self.format_self_type(buffer); - } - TypeRef::UniSelf => { - buffer.write_ownership("uni "); - self.format_self_type(buffer); - } - TypeRef::Error => buffer.write(""), - TypeRef::Unknown => buffer.write(""), - TypeRef::Placeholder(id) => id.format_type(buffer), - }; - } -} - -/// An ID pointing to a type. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] -pub enum TypeId { - Class(ClassId), - Trait(TraitId), - Module(ModuleId), - ClassInstance(ClassInstance), - TraitInstance(TraitInstance), - TypeParameter(TypeParameterId), - RigidTypeParameter(TypeParameterId), - Closure(ClosureId), -} - -impl TypeId { - pub fn named_type(self, db: &Database, name: &str) -> Option { - match self { - TypeId::Module(id) => id.symbol(db, name), - TypeId::Trait(id) => id.named_type(db, name), - TypeId::Class(id) => id.named_type(db, name), - TypeId::ClassInstance(id) => id.named_type(db, name), - TypeId::TraitInstance(id) => id.named_type(db, name), - _ => None, - } - } - - pub fn lookup_method( - self, - db: &Database, - name: &str, - module: ModuleId, - allow_type_private: bool, - ) -> MethodLookup { - if let Some(id) = self.method(db, name) { - let kind = id.kind(db); - let is_ins = !matches!( - self, - TypeId::Class(_) | TypeId::Trait(_) | TypeId::Module(_) - ); - - if is_ins && kind == MethodKind::Static { - MethodLookup::StaticOnInstance - } else if !is_ins && kind != MethodKind::Static { - MethodLookup::InstanceOnStatic - } else if self.can_call(db, id, module, allow_type_private) { - MethodLookup::Ok(id) - } else { - MethodLookup::Private - } - } else { - MethodLookup::None - } - } - - pub fn method(self, db: &Database, name: &str) -> Option { - match self { - TypeId::Class(id) => id.method(db, name), - TypeId::Trait(id) => id.method(db, name), - TypeId::Module(id) => id.method(db, name), - TypeId::ClassInstance(id) => id.method(db, name), - TypeId::TraitInstance(id) => id.method(db, name), - TypeId::TypeParameter(id) | TypeId::RigidTypeParameter(id) => { - id.method(db, name) - } - TypeId::Closure(_) => None, - } - } - - pub fn implements_trait_instance( - self, - db: &mut Database, - trait_type: TraitInstance, - context: &mut TypeContext, - ) -> bool { - match self { - TypeId::ClassInstance(id) => { - id.type_check_with_trait_instance(db, trait_type, context, true) - } - TypeId::TraitInstance(id) => { - id.implements_trait_instance(db, trait_type, context) - } - TypeId::TypeParameter(id) | TypeId::RigidTypeParameter(id) => { - id.type_check_with_trait_instance(db, trait_type, context, true) - } - _ => false, - } - } - - pub fn use_dynamic_dispatch(self) -> bool { - matches!( - self, - TypeId::TraitInstance(_) - | TypeId::TypeParameter(_) - | TypeId::RigidTypeParameter(_) - ) - } - - pub fn has_destructor(self, db: &Database) -> bool { - if let TypeId::ClassInstance(id) = self { - id.instance_of().has_destructor(db) - } else { - false - } - } - - fn implements_trait_id(self, db: &Database, trait_id: TraitId) -> bool { - match self { - TypeId::ClassInstance(id) => id.implements_trait_id(db, trait_id), - TypeId::TraitInstance(id) => id.implements_trait_id(db, trait_id), - TypeId::TypeParameter(id) => id - .requirements(db) - .iter() - .any(|req| req.implements_trait_id(db, trait_id)), - _ => false, - } - } - - fn as_rigid_type(self, db: &mut Database, bounds: &TypeBounds) -> Self { - match self { - TypeId::Class(_) | TypeId::Trait(_) | TypeId::Module(_) => self, - TypeId::ClassInstance(ins) => { - TypeId::ClassInstance(ins.as_rigid_type(db, bounds)) - } - TypeId::TraitInstance(ins) => { - TypeId::TraitInstance(ins.as_rigid_type(db, bounds)) - } - TypeId::TypeParameter(ins) => ins.as_rigid_type(bounds), - TypeId::RigidTypeParameter(_) => self, - TypeId::Closure(ins) => { - TypeId::Closure(ins.as_rigid_type(db, bounds)) - } - } - } - - fn can_call( - self, - db: &Database, - method: MethodId, - module: ModuleId, - allow_type_private: bool, - ) -> bool { - let m = method.get(db); - - if m.kind == MethodKind::Destructor { - return false; + let m = method.get(db); + + if m.kind == MethodKind::Destructor { + return false; } match m.visibility { @@ -5014,53 +4172,6 @@ impl TypeId { Visibility::TypePrivate => allow_type_private, } } - - fn type_check( - self, - db: &mut Database, - with: TypeId, - context: &mut TypeContext, - subtyping: bool, - ) -> bool { - match self { - TypeId::Class(_) | TypeId::Trait(_) | TypeId::Module(_) => { - self == with - } - TypeId::ClassInstance(ins) => { - ins.type_check(db, with, context, subtyping) - } - TypeId::TraitInstance(ins) => { - ins.type_check(db, with, context, subtyping) - } - TypeId::TypeParameter(ins) => { - ins.type_check(db, with, context, subtyping) - } - TypeId::RigidTypeParameter(our_ins) => match with { - TypeId::RigidTypeParameter(their_ins) => our_ins == their_ins, - _ => our_ins.type_check(db, with, context, subtyping), - }, - TypeId::Closure(ins) => { - ins.type_check(db, with, context, subtyping) - } - } - } -} - -impl FormatType for TypeId { - fn format_type(&self, buffer: &mut TypeFormatter) { - match self { - TypeId::Class(id) => id.format_type(buffer), - TypeId::Trait(id) => id.format_type(buffer), - TypeId::Module(id) => id.format_type(buffer), - TypeId::ClassInstance(ins) => ins.format_type(buffer), - TypeId::TraitInstance(id) => id.format_type(buffer), - TypeId::TypeParameter(id) => id.format_type(buffer), - TypeId::RigidTypeParameter(id) => { - id.format_type_without_argument(buffer); - } - TypeId::Closure(id) => id.format_type(buffer), - } - } } /// A database of all Inko types. @@ -5076,7 +4187,8 @@ pub struct Database { closures: Vec, variables: Vec, constants: Vec, - builtin_functions: IndexMap, + builtin_functions: HashMap, + builtin_constants: HashMap, type_placeholders: Vec, variants: Vec, @@ -5085,6 +4197,8 @@ pub struct Database { /// For executables this will be set based on the file that is built/run. /// When just type-checking a project, this may be left as a None. main_module: Option, + main_method: Option, + main_class: Option, } impl Database { @@ -5094,14 +4208,14 @@ impl Database { module_mapping: HashMap::new(), traits: Vec::new(), classes: vec![ - Class::regular(INT_NAME.to_string()), - Class::regular(FLOAT_NAME.to_string()), - Class::regular(STRING_NAME.to_string()), + Class::value_type(INT_NAME.to_string()), + Class::value_type(FLOAT_NAME.to_string()), + Class::atomic(STRING_NAME.to_string()), Class::regular(ARRAY_NAME.to_string()), - Class::regular(BOOLEAN_NAME.to_string()), - Class::regular(NIL_NAME.to_string()), + Class::value_type(BOOLEAN_NAME.to_string()), + Class::value_type(NIL_NAME.to_string()), Class::regular(BYTE_ARRAY_NAME.to_string()), - Class::regular(FUTURE_NAME.to_string()), + Class::atomic(CHANNEL_NAME.to_string()), Class::tuple(TUPLE1_NAME.to_string()), Class::tuple(TUPLE2_NAME.to_string()), Class::tuple(TUPLE3_NAME.to_string()), @@ -5110,6 +4224,7 @@ impl Database { Class::tuple(TUPLE6_NAME.to_string()), Class::tuple(TUPLE7_NAME.to_string()), Class::tuple(TUPLE8_NAME.to_string()), + Class::external(RESULT_NAME.to_string()), ], type_parameters: Vec::new(), type_arguments: Vec::new(), @@ -5118,10 +4233,13 @@ impl Database { closures: Vec::new(), variables: Vec::new(), constants: Vec::new(), - builtin_functions: IndexMap::new(), + builtin_functions: BuiltinFunction::mapping(), + builtin_constants: BuiltinConstant::mapping(), type_placeholders: Vec::new(), variants: Vec::new(), main_module: None, + main_method: None, + main_class: None, } } @@ -5134,7 +4252,7 @@ impl Database { BOOLEAN_NAME => Some(ClassId(BOOLEAN_ID)), NIL_NAME => Some(ClassId(NIL_ID)), BYTE_ARRAY_NAME => Some(ClassId(BYTE_ARRAY_ID)), - FUTURE_NAME => Some(ClassId(FUTURE_ID)), + CHANNEL_NAME => Some(ClassId(CHANNEL_ID)), TUPLE1_NAME => Some(ClassId(TUPLE1_ID)), TUPLE2_NAME => Some(ClassId(TUPLE2_ID)), TUPLE3_NAME => Some(ClassId(TUPLE3_ID)), @@ -5147,8 +4265,12 @@ impl Database { } } - pub fn builtin_function(&self, name: &str) -> Option { - self.builtin_functions.index_of(name).map(BuiltinFunctionId) + pub fn builtin_function(&self, name: &str) -> Option { + self.builtin_functions.get(name).cloned() + } + + pub fn builtin_constant(&self, name: &str) -> Option { + self.builtin_constants.get(name).cloned() } pub fn module(&self, name: &str) -> ModuleId { @@ -5200,16 +4322,37 @@ impl Database { pub fn main_module(&self) -> Option<&ModuleName> { self.main_module.as_ref() } + + pub fn set_main_method(&mut self, id: MethodId) { + self.main_method = Some(id); + } + + pub fn main_method(&self) -> Option { + self.main_method + } + + pub fn set_main_class(&mut self, id: ClassId) { + self.main_class = Some(id); + } + + pub fn main_class(&self) -> Option { + self.main_class + } } #[cfg(test)] mod tests { use super::*; + use crate::test::{ + immutable, instance, mutable, new_parameter, owned, placeholder, rigid, + uni, + }; use std::mem::size_of; #[test] fn test_type_sizes() { assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 24); } #[test] @@ -5254,69 +4397,6 @@ mod tests { assert_eq!(id.requirements(&db), vec![requirement]); } - #[test] - fn test_type_parameter_id_type_check() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let p1 = TypeParameter::alloc(&mut db, "A".to_string()); - let p2 = TypeParameter::alloc(&mut db, "B".to_string()); - let p3 = TypeParameter::alloc(&mut db, "C".to_string()); - let mut ctx = TypeContext::new(self_type); - - assert!(p1.type_check( - &mut db, - TypeId::TypeParameter(p2), - &mut ctx, - false - )); - assert!(!p1.type_check( - &mut db, - TypeId::RigidTypeParameter(p3), - &mut ctx, - false - )); - } - - #[test] - fn test_type_parameter_id_type_check_with_requirements() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let p1 = TypeParameter::alloc(&mut db, "A".to_string()); - let p2 = TypeParameter::alloc(&mut db, "B".to_string()); - - p1.add_requirements(&mut db, vec![TraitInstance::new(to_s)]); - p2.add_requirements(&mut db, vec![TraitInstance::new(to_s)]); - - let mut ctx = TypeContext::new(self_type); - - assert!(p1.type_check( - &mut db, - TypeId::TypeParameter(p2), - &mut ctx, - false - )); - } - #[test] fn test_type_arguments_assign() { let mut targs = TypeArguments::new(); @@ -5404,348 +4484,9 @@ mod tests { } #[test] - fn test_trait_instance_type_check_with_generic_trait_instance() { + fn test_class_alloc() { let mut db = Database::new(); - let trait_a = Trait::alloc( - &mut db, - "A".to_string(), - ModuleId(0), - Visibility::Private, - ); - let trait_b = Trait::alloc( - &mut db, - "B".to_string(), - ModuleId(0), - Visibility::Private, - ); - - let param1 = trait_a.new_type_parameter(&mut db, "A".to_string()); - - trait_b.new_type_parameter(&mut db, "A".to_string()); - - let mut ins1_args = TypeArguments::new(); - let mut ins2_args = TypeArguments::new(); - let mut ins3_args = TypeArguments::new(); - - ins1_args.assign(param1, TypeRef::Any); - ins2_args.assign(param1, TypeRef::Any); - ins3_args.assign(param1, TypeRef::Never); - - let ins1 = TraitInstance::generic(&mut db, trait_a, ins1_args); - let ins2 = TraitInstance::generic(&mut db, trait_a, ins2_args); - let ins3 = TraitInstance::generic(&mut db, trait_a, ins3_args); - let ins4 = - TraitInstance::generic(&mut db, trait_a, TypeArguments::new()); - let ins5 = - TraitInstance::generic(&mut db, trait_b, TypeArguments::new()); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(ins1.type_check( - &mut db, - TypeId::TraitInstance(ins2), - &mut ctx, - false - )); - assert!(!ins1.type_check( - &mut db, - TypeId::TraitInstance(ins3), - &mut ctx, - false - )); - assert!(!ins1.type_check( - &mut db, - TypeId::TraitInstance(ins4), - &mut ctx, - false - )); - assert!(!ins4.type_check( - &mut db, - TypeId::TraitInstance(ins5), - &mut ctx, - false - )); - } - - #[test] - fn test_trait_instance_type_check_with_generic_trait_as_required_trait() { - let mut db = Database::new(); - let trait_b = Trait::alloc( - &mut db, - "B".to_string(), - ModuleId(0), - Visibility::Private, - ); - let trait_c = Trait::alloc( - &mut db, - "C".to_string(), - ModuleId(0), - Visibility::Private, - ); - let ins_b = - TraitInstance::generic(&mut db, trait_b, TypeArguments::new()); - let ins_c = TypeId::TraitInstance(TraitInstance::generic( - &mut db, - trait_c, - TypeArguments::new(), - )); - - { - let req = - TraitInstance::generic(&mut db, trait_c, TypeArguments::new()); - - trait_b.add_required_trait(&mut db, req); - } - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(ins_b.type_check(&mut db, ins_c, &mut ctx, true)); - } - - #[test] - fn test_trait_instance_type_check_with_regular_trait() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_int = Trait::alloc( - &mut db, - "ToInt".to_string(), - ModuleId(0), - Visibility::Private, - ); - let requirement = TraitInstance::new(to_string); - - debug.add_required_trait(&mut db, requirement); - - let debug_ins = TraitInstance::new(debug); - let to_string_ins = - TypeId::TraitInstance(TraitInstance::new(to_string)); - let to_int_ins = TypeId::TraitInstance(TraitInstance::new(to_int)); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(debug_ins.type_check(&mut db, to_string_ins, &mut ctx, true)); - assert!(!debug_ins.type_check(&mut db, to_int_ins, &mut ctx, true)); - } - - #[test] - fn test_trait_instance_type_check_with_rigid_type_parameter() { - let mut db = Database::new(); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - let param = TypeParameter::alloc(&mut db, "A".to_string()); - let param_ins = TypeId::RigidTypeParameter(param); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!to_s_ins.type_check(&mut db, param_ins, &mut ctx, false)); - } - - #[test] - fn test_trait_instance_type_check_with_type_parameter() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_int = Trait::alloc( - &mut db, - "ToInt".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param1 = TypeParameter::alloc(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "B".to_string()); - let param3 = TypeParameter::alloc(&mut db, "C".to_string()); - let debug_ins = TraitInstance::new(debug); - let to_string_ins = TraitInstance::new(to_string); - - debug.add_required_trait(&mut db, to_string_ins); - param2.add_requirements(&mut db, vec![debug_ins]); - param3.add_requirements(&mut db, vec![to_string_ins]); - - let to_int_ins = TraitInstance::new(to_int); - let param1_ins = TypeId::TypeParameter(param1); - let param2_ins = TypeId::TypeParameter(param2); - let param3_ins = TypeId::TypeParameter(param3); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(debug_ins.type_check(&mut db, param1_ins, &mut ctx, true)); - assert!(debug_ins.type_check(&mut db, param2_ins, &mut ctx, true)); - assert!(debug_ins.type_check(&mut db, param3_ins, &mut ctx, true)); - assert!(!to_int_ins.type_check(&mut db, param2_ins, &mut ctx, true)); - } - - #[test] - fn test_trait_instance_type_check_with_other_variants() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let debug_ins = TraitInstance::new(debug); - let closure = TypeId::Closure(Closure::alloc(&mut db, false)); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!debug_ins.type_check(&mut db, closure, &mut ctx, false)); - } - - #[test] - fn test_trait_instance_format_type_with_regular_trait() { - let mut db = Database::new(); - let trait_id = Trait::alloc( - &mut db, - "A".to_string(), - ModuleId(0), - Visibility::Private, - ); - let trait_ins = TraitInstance::new(trait_id); - - assert_eq!(format_type(&db, trait_ins), "A".to_string()); - } - - #[test] - fn test_trait_instance_format_type_with_generic_trait() { - let mut db = Database::new(); - let trait_id = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param1 = trait_id.new_type_parameter(&mut db, "A".to_string()); - - trait_id.new_type_parameter(&mut db, "B".to_string()); - - let mut targs = TypeArguments::new(); - - targs.assign(param1, TypeRef::Any); - - let trait_ins = TraitInstance::generic(&mut db, trait_id, targs); - - assert_eq!(format_type(&db, trait_ins), "ToString[Any, B]"); - } - - #[test] - fn test_trait_instance_as_rigid_type_with_regular_trait() { - let mut db = Database::new(); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - let bounds = TypeBounds::new(); - let rigid = to_s_ins.as_rigid_type(&mut db, &bounds); - - assert_eq!(rigid, to_s_ins); - } - - #[test] - fn test_trait_instance_as_rigid_type_with_generic_trait() { - let mut db = Database::new(); - let to_a = Trait::alloc( - &mut db, - "ToArray".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param1 = to_a.new_type_parameter(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "A".to_string()); - let mut args = TypeArguments::new(); - - args.assign(param1, TypeRef::Owned(TypeId::TypeParameter(param2))); - - let to_a_ins = TraitInstance::generic(&mut db, to_a, args); - let bounds = TypeBounds::new(); - let rigid = to_a_ins.as_rigid_type(&mut db, &bounds); - let old_arg = to_a_ins.type_arguments(&db).get(param1).unwrap(); - let new_arg = rigid.type_arguments(&db).get(param1).unwrap(); - - assert_ne!(old_arg, new_arg); - assert_eq!(new_arg, TypeParameterId(1).as_owned_rigid()); - } - - #[test] - fn test_class_alloc() { - let mut db = Database::new(); - let id = Class::alloc( + let id = Class::alloc( &mut db, "A".to_string(), ClassKind::Regular, @@ -5844,2925 +4585,258 @@ mod tests { assert_eq!(ins.type_arguments, 0); } - #[test] - fn test_class_instance_generic() { - let mut db = Database::new(); - let id = Class::alloc( - &mut db, - "A".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let ins1 = ClassInstance::generic(&mut db, id, TypeArguments::new()); - let ins2 = ClassInstance::generic(&mut db, id, TypeArguments::new()); - - assert_eq!(ins1.instance_of.0, FIRST_USER_CLASS_ID); - assert_eq!(ins1.type_arguments, 0); - - assert_eq!(ins2.instance_of.0, FIRST_USER_CLASS_ID); - assert_eq!(ins2.type_arguments, 1); - } - - #[test] - fn test_class_instance_type_check_with_class_instance() { - let mut db = Database::new(); - let cls1 = Class::alloc( - &mut db, - "A".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let cls2 = Class::alloc( - &mut db, - "B".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let ins1 = ClassInstance::new(cls1); - let ins2 = ClassInstance::new(cls1); - let ins3 = ClassInstance::new(cls2); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(ins1.type_check( - &mut db, - TypeId::ClassInstance(ins1), - &mut ctx, - false - )); - assert!(ins1.type_check( - &mut db, - TypeId::ClassInstance(ins2), - &mut ctx, - false - )); - assert!(!ins1.type_check( - &mut db, - TypeId::ClassInstance(ins3), - &mut ctx, - false - )); - } - - #[test] - fn test_class_instance_type_check_with_generic_class_instance() { - let mut db = Database::new(); - let class_a = Class::alloc( - &mut db, - "A".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let class_b = Class::alloc( - &mut db, - "B".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - - let param1 = class_a.new_type_parameter(&mut db, "A".to_string()); - - class_b.new_type_parameter(&mut db, "A".to_string()); - - let mut ins1_args = TypeArguments::new(); - let mut ins2_args = TypeArguments::new(); - let mut ins3_args = TypeArguments::new(); - - ins1_args.assign(param1, TypeRef::Any); - ins2_args.assign(param1, TypeRef::Any); - ins3_args.assign(param1, TypeRef::Never); - - let ins1 = ClassInstance::generic(&mut db, class_a, ins1_args); - let ins2 = ClassInstance::generic(&mut db, class_a, ins2_args); - let ins3 = ClassInstance::generic(&mut db, class_a, ins3_args); - let ins4 = - ClassInstance::generic(&mut db, class_a, TypeArguments::new()); - let ins5 = - ClassInstance::generic(&mut db, class_b, TypeArguments::new()); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(ins1.type_check( - &mut db, - TypeId::ClassInstance(ins2), - &mut ctx, - false - )); - assert!(!ins1.type_check( - &mut db, - TypeId::ClassInstance(ins3), - &mut ctx, - false - )); - assert!(!ins1.type_check( - &mut db, - TypeId::ClassInstance(ins4), - &mut ctx, - false - )); - assert!(!ins4.type_check( - &mut db, - TypeId::ClassInstance(ins5), - &mut ctx, - false - )); - } - - #[test] - fn test_class_instance_type_check_with_empty_type_arguments() { - let mut db = Database::new(); - let array = Class::alloc( - &mut db, - "Array".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let param = array.new_type_parameter(&mut db, "T".to_string()); - let ins1 = ClassInstance::generic(&mut db, array, TypeArguments::new()); - let ins2 = { - let mut args = TypeArguments::new(); - - args.assign(param, TypeRef::Any); - ClassInstance::generic(&mut db, array, args) - }; - - let stype = TypeId::ClassInstance(ins1); - let mut ctx = TypeContext::new(stype); - - assert!(ins1.type_check( - &mut db, - TypeId::ClassInstance(ins2), - &mut ctx, - false - )); - assert!(!ins2.type_check( - &mut db, - TypeId::ClassInstance(ins1), - &mut ctx, - false - )); - } - - #[test] - fn test_class_instance_type_check_with_trait_instance() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string_ins = TraitInstance::new(to_string); - let to_int = Trait::alloc( - &mut db, - "ToInt".to_string(), - ModuleId(0), - Visibility::Private, - ); - - string.add_trait_implementation( - &mut db, - TraitImplementation { - instance: to_string_ins, - bounds: TypeBounds::new(), - }, - ); - - let string_ins = ClassInstance::new(string); - let to_int_ins = TypeId::TraitInstance(TraitInstance::new(to_int)); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(string_ins.type_check( - &mut db, - TypeId::TraitInstance(to_string_ins), - &mut ctx, - true - )); - - assert!(!string_ins.type_check(&mut db, to_int_ins, &mut ctx, true)); - } - - #[test] - fn test_class_instance_type_check_with_generic_trait_instance() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let owned_string = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(string))); - let owned_int = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(int))); - let equal = Trait::alloc( - &mut db, - "Equal".to_string(), - ModuleId(0), - Visibility::Private, - ); - let equal_param = equal.new_type_parameter(&mut db, "T".to_string()); - - let equal_string = { - let mut type_args = TypeArguments::new(); - - type_args.assign(equal_param, owned_string); - TraitInstance::generic(&mut db, equal, type_args) - }; - - let equal_int = { - let mut type_args = TypeArguments::new(); - - type_args.assign(equal_param, owned_int); - TraitInstance::generic(&mut db, equal, type_args) - }; - - let equal_any = { - let mut type_args = TypeArguments::new(); - - type_args.assign(equal_param, TypeRef::Any); - TraitInstance::generic(&mut db, equal, type_args) - }; - - string.add_trait_implementation( - &mut db, - TraitImplementation { - instance: equal_string, - bounds: TypeBounds::new(), - }, - ); - - let string_ins = ClassInstance::new(string); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - // String -> Equal[String] is OK because String implements - // Equal[String]. - assert!(string_ins.type_check( - &mut db, - TypeId::TraitInstance(equal_string), - &mut ctx, - true - )); - - // String -> Equal[Any] is OK, as Equal[String] is compatible with - // Equal[Any] (but not the other way around). - assert!(string_ins.type_check( - &mut db, - TypeId::TraitInstance(equal_any), - &mut ctx, - true - )); - - // String -> Equal[Int] is not OK, as Equal[Int] isn't implemented by - // String. - assert!(!string_ins.type_check( - &mut db, - TypeId::TraitInstance(equal_int), - &mut ctx, - true - )); - } - - #[test] - fn test_class_instance_type_check_with_trait_instance_with_bounds() { - let mut db = Database::new(); - let array = Class::alloc( - &mut db, - "Array".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let float = Class::alloc( - &mut db, - "Float".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let param = array.new_type_parameter(&mut db, "T".to_string()); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string_ins = TraitInstance::new(to_string); - let mut to_string_impl = TraitImplementation { - instance: to_string_ins, - bounds: TypeBounds::new(), - }; - - let bound_param = TypeParameter::alloc(&mut db, "T".to_string()); - - bound_param.add_requirements(&mut db, vec![to_string_ins]); - to_string_impl.bounds.set(param, bound_param); - array.add_trait_implementation(&mut db, to_string_impl); - - int.add_trait_implementation( - &mut db, - TraitImplementation { - instance: to_string_ins, - bounds: TypeBounds::new(), - }, - ); - - let empty_array = - ClassInstance::generic(&mut db, array, TypeArguments::new()); - - let int_array = { - let mut args = TypeArguments::new(); - - args.assign( - param, - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(int))), - ); - - ClassInstance::generic(&mut db, array, args) - }; - - let float_array = { - let mut args = TypeArguments::new(); - - args.assign( - param, - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new( - float, - ))), - ); - - ClassInstance::generic(&mut db, array, args) - }; - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - let to_string_type = TypeId::TraitInstance(to_string_ins); - - assert!(!empty_array.type_check( - &mut db, - to_string_type, - &mut ctx, - true - )); - assert!(!float_array.type_check( - &mut db, - to_string_type, - &mut ctx, - true - )); - assert!(int_array.type_check(&mut db, to_string_type, &mut ctx, true)); - } - - #[test] - fn test_class_instance_type_check_with_type_parameter() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string_ins = TraitInstance::new(to_string); - let to_int = Trait::alloc( - &mut db, - "ToInt".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_int_ins = TraitInstance::new(to_int); - let param1 = TypeParameter::alloc(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "B".to_string()); - let param3 = TypeParameter::alloc(&mut db, "C".to_string()); - - string.add_trait_implementation( - &mut db, - TraitImplementation { - instance: to_string_ins, - bounds: TypeBounds::new(), - }, - ); - - param2.add_requirements(&mut db, vec![to_string_ins]); - param3.add_requirements(&mut db, vec![to_int_ins]); - - let string_ins = ClassInstance::new(string); - let param1_type = TypeId::TypeParameter(param1); - let param2_type = TypeId::TypeParameter(param2); - let param3_type = TypeId::TypeParameter(param3); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - // String -> A is OK, as A has no requirements. - assert!(string_ins.type_check(&mut db, param1_type, &mut ctx, true)); - - // String -> B is OK, as ToString is implemented by String. - assert!(string_ins.type_check(&mut db, param2_type, &mut ctx, true)); - - // String -> C is not OK, as ToInt isn't implemented. - assert!(!string_ins.type_check(&mut db, param3_type, &mut ctx, true)); - } - - #[test] - fn test_class_instance_type_check_with_rigid_type_parameter() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = ClassInstance::new(string); - let param = TypeParameter::alloc(&mut db, "A".to_string()); - let param_ins = TypeId::RigidTypeParameter(param); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!string_ins.type_check(&mut db, param_ins, &mut ctx, false)); - } - - #[test] - fn test_class_instance_type_check_with_function() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let closure = TypeId::Closure(Closure::alloc(&mut db, false)); - let string_ins = ClassInstance::new(string); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!string_ins.type_check(&mut db, closure, &mut ctx, false)); - } - - #[test] - fn test_class_instance_as_rigid_type_with_regular_trait() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = ClassInstance::new(string); - let bounds = TypeBounds::new(); - let rigid = string_ins.as_rigid_type(&mut db, &bounds); - - assert_eq!(rigid, string_ins); - } - - #[test] - fn test_class_instance_as_rigid_type_with_generic_trait() { - let mut db = Database::new(); - let array = Class::alloc( - &mut db, - "Array".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let param1 = array.new_type_parameter(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "A".to_string()); - let mut args = TypeArguments::new(); - - args.assign(param1, TypeRef::Owned(TypeId::TypeParameter(param2))); - - let to_a_ins = ClassInstance::generic(&mut db, array, args); - let bounds = TypeBounds::new(); - let rigid = to_a_ins.as_rigid_type(&mut db, &bounds); - let old_arg = to_a_ins.type_arguments(&db).get(param1).unwrap(); - let new_arg = rigid.type_arguments(&db).get(param1).unwrap(); - - assert_ne!(old_arg, new_arg); - assert_eq!(new_arg, TypeParameterId(1).as_owned_rigid()); - } - - #[test] - fn test_method_alloc() { - let mut db = Database::new(); - let id = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Moving, - ); - - assert_eq!(id.0, 0); - assert_eq!(&db.methods[0].name, &"foo".to_string()); - assert_eq!(db.methods[0].kind, MethodKind::Moving); - } - - #[test] - fn test_method_id_named_type() { - let mut db = Database::new(); - let method = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let param = method.new_type_parameter(&mut db, "A".to_string()); - - assert_eq!( - method.named_type(&db, "A"), - Some(Symbol::TypeParameter(param)) - ); - } - - #[test] - fn test_method_id_format_type_with_instance_method() { - let mut db = Database::new(); - let class_a = Class::alloc( - &mut db, - "A".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let class_b = Class::alloc( - &mut db, - "B".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let class_c = Class::alloc( - &mut db, - "C".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let class_d = Class::alloc( - &mut db, - "D".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let block = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - let ins_a = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_a))); - - let ins_b = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_b))); - - let ins_c = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_c))); - - let ins_d = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_d))); - - block.new_argument(&mut db, "a".to_string(), ins_a, ins_a); - block.new_argument(&mut db, "b".to_string(), ins_b, ins_b); - block.set_throw_type(&mut db, ins_c); - block.set_return_type(&mut db, ins_d); - - assert_eq!(format_type(&db, block), "fn foo (a: A, b: B) !! C -> D"); - } - - #[test] - fn test_method_id_format_type_with_moving_method() { - let mut db = Database::new(); - let block = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Moving, - ); - - block.set_return_type(&mut db, TypeRef::Any); - - assert_eq!(format_type(&db, block), "fn move foo -> Any"); - } - - #[test] - fn test_method_id_format_type_with_type_parameters() { - let mut db = Database::new(); - let block = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Static, - ); - - block.new_type_parameter(&mut db, "A".to_string()); - block.new_type_parameter(&mut db, "B".to_string()); - block.set_return_type(&mut db, TypeRef::Any); - - assert_eq!(format_type(&db, block), "fn static foo [A, B] -> Any"); - } - - #[test] - fn test_method_id_format_type_with_static_method() { - let mut db = Database::new(); - let block = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Static, - ); - - block.new_argument( - &mut db, - "a".to_string(), - TypeRef::Any, - TypeRef::Any, - ); - block.set_return_type(&mut db, TypeRef::Any); - - assert_eq!(format_type(&db, block), "fn static foo (a: Any) -> Any"); - } - - #[test] - fn test_method_id_format_type_with_async_method() { - let mut db = Database::new(); - let block = Method::alloc( - &mut db, - ModuleId(0), - "foo".to_string(), - Visibility::Private, - MethodKind::Async, - ); - - block.new_argument( - &mut db, - "a".to_string(), - TypeRef::Any, - TypeRef::Any, - ); - block.set_return_type(&mut db, TypeRef::Any); - - assert_eq!(format_type(&db, block), "fn async foo (a: Any) -> Any"); - } - - #[test] - fn test_method_id_type_check_with_different_name() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "b".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_different_visibility() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Public, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_different_kind() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Static, - ); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_different_param_count() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - m2.new_type_parameter(&mut db, "T".to_string()); - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_incompatible_params() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - - m1.new_type_parameter(&mut db, "T".to_string()); - - m2.new_type_parameter(&mut db, "T".to_string()) - .add_requirements(&mut db, vec![to_s_ins]); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_incompatible_arg_types() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m3 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = - TypeRef::Owned(TypeId::TraitInstance(TraitInstance::new(to_s))); - - m1.new_argument(&mut db, "a".to_string(), to_s_ins, to_s_ins); - m3.new_argument(&mut db, "a".to_string(), to_s_ins, to_s_ins); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - m3.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - assert!(!m2.type_check(&mut db, m3, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_incompatible_arg_names() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - m1.new_argument(&mut db, "a".to_string(), TypeRef::Any, TypeRef::Any); - m2.new_argument(&mut db, "b".to_string(), TypeRef::Any, TypeRef::Any); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_incompatible_throw_type() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - m1.set_throw_type(&mut db, TypeRef::Any); - m1.set_return_type(&mut db, TypeRef::Any); - - m2.set_throw_type(&mut db, TypeRef::Never); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_incompatible_return_type() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - m1.set_return_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Never); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(!m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_method_id_type_check_with_compatible_method() { - let mut db = Database::new(); - let m1 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - let m2 = Method::alloc( - &mut db, - ModuleId(0), - "a".to_string(), - Visibility::Private, - MethodKind::Instance, - ); - - m1.new_type_parameter(&mut db, "T".to_string()); - m1.new_argument(&mut db, "a".to_string(), TypeRef::Any, TypeRef::Any); - m1.set_throw_type(&mut db, TypeRef::Any); - m1.set_return_type(&mut db, TypeRef::Any); - - m2.new_type_parameter(&mut db, "T".to_string()); - m2.new_argument(&mut db, "a".to_string(), TypeRef::Any, TypeRef::Any); - m2.set_throw_type(&mut db, TypeRef::Any); - m2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(m1.type_check(&mut db, m2, &mut ctx)); - } - - #[test] - fn test_module_alloc() { - let mut db = Database::new(); - let name = ModuleName::new("foo"); - let id = Module::alloc(&mut db, name.clone(), "foo.inko".into()); - - assert_eq!(id.0, 0); - assert_eq!(&db.modules[0].name, &name); - assert_eq!(&db.modules[0].file, &PathBuf::from("foo.inko")); - } - - #[test] - fn test_module_id_file() { - let mut db = Database::new(); - let id = Module::alloc( - &mut db, - ModuleName::new("foo"), - PathBuf::from("test.inko"), - ); - - assert_eq!(id.file(&db), PathBuf::from("test.inko")); - } - - #[test] - fn test_module_id_symbol() { - let mut db = Database::new(); - let id = Module::alloc( - &mut db, - ModuleName::new("foo"), - PathBuf::from("test.inko"), - ); - - id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); - - assert_eq!(id.symbol(&db, "A"), Some(Symbol::Module(id))); - } - - #[test] - fn test_module_id_symbols() { - let mut db = Database::new(); - let id = Module::alloc( - &mut db, - ModuleName::new("foo"), - PathBuf::from("test.inko"), - ); - - id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); - - assert_eq!( - id.symbols(&db), - vec![("A".to_string(), Symbol::Module(id))] - ); - } - - #[test] - fn test_module_id_symbol_exists() { - let mut db = Database::new(); - let id = Module::alloc( - &mut db, - ModuleName::new("foo"), - PathBuf::from("test.inko"), - ); - - id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); - - assert!(id.symbol_exists(&db, "A")); - assert!(!id.symbol_exists(&db, "B")); - } - - #[test] - fn test_function_closure() { - let mut db = Database::new(); - let id = Closure::alloc(&mut db, false); - - assert_eq!(id.0, 0); - } - - #[test] - fn test_closure_id_format_type_never_throws() { - let mut db = Database::new(); - let block = Closure::alloc(&mut db, false); - - block.set_throw_type(&mut db, TypeRef::Never); - block.set_return_type(&mut db, TypeRef::Any); - - assert_eq!(format_type(&db, block), "fn -> Any"); - } - - #[test] - fn test_closure_id_format_type_never_returns() { - let mut db = Database::new(); - let block = Closure::alloc(&mut db, false); - - block.set_return_type(&mut db, TypeRef::Never); - - assert_eq!(format_type(&db, block), "fn -> Never"); - } - - #[test] - fn test_closure_id_type_check_with_empty_closure() { - let mut db = Database::new(); - let closure1 = Closure::alloc(&mut db, false); - let closure2 = Closure::alloc(&mut db, false); - - closure1.set_return_type(&mut db, TypeRef::Any); - closure2.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(closure1.type_check( - &mut db, - TypeId::Closure(closure2), - &mut ctx, - false - )); - } - - #[test] - fn test_closure_id_type_check_with_arguments() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(string))); - let closure1 = Closure::alloc(&mut db, false); - let closure2 = Closure::alloc(&mut db, false); - let closure3 = Closure::alloc(&mut db, false); - - closure1.new_argument(&mut db, "a".to_string(), string_ins, string_ins); - closure1.set_return_type(&mut db, TypeRef::Any); - - closure2.new_argument(&mut db, "x".to_string(), string_ins, string_ins); - closure2.set_return_type(&mut db, TypeRef::Any); - - closure3.new_argument(&mut db, "a".to_string(), string_ins, string_ins); - closure3.new_argument(&mut db, "b".to_string(), string_ins, string_ins); - closure3.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(closure1.type_check( - &mut db, - TypeId::Closure(closure2), - &mut ctx, - false - )); - assert!(!closure1.type_check( - &mut db, - TypeId::Closure(closure3), - &mut ctx, - false - )); - } - - #[test] - fn test_closure_id_type_check_with_throw_type() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(string))); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(int))); - let closure1 = Closure::alloc(&mut db, false); - let closure2 = Closure::alloc(&mut db, false); - let closure3 = Closure::alloc(&mut db, false); - let closure4 = Closure::alloc(&mut db, false); - - closure1.set_throw_type(&mut db, string_ins); - closure1.set_return_type(&mut db, TypeRef::Any); - - closure2.set_throw_type(&mut db, string_ins); - closure2.set_return_type(&mut db, TypeRef::Any); - - closure3.set_throw_type(&mut db, int_ins); - closure3.set_return_type(&mut db, TypeRef::Any); - - closure4.set_return_type(&mut db, TypeRef::Any); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(closure1.type_check( - &mut db, - TypeId::Closure(closure2), - &mut ctx, - false - )); - assert!(closure4.type_check( - &mut db, - TypeId::Closure(closure1), - &mut ctx, - false - )); - assert!(!closure1.type_check( - &mut db, - TypeId::Closure(closure3), - &mut ctx, - false - )); - assert!(!closure1.type_check( - &mut db, - TypeId::Closure(closure4), - &mut ctx, - false - )); - } - - #[test] - fn test_closure_id_type_check_with_return_type() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(string))); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(int))); - let closure1 = Closure::alloc(&mut db, false); - let closure2 = Closure::alloc(&mut db, false); - let closure3 = Closure::alloc(&mut db, false); - let closure4 = Closure::alloc(&mut db, false); - - closure1.set_return_type(&mut db, string_ins); - closure2.set_return_type(&mut db, string_ins); - closure3.set_return_type(&mut db, int_ins); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(closure1.type_check( - &mut db, - TypeId::Closure(closure2), - &mut ctx, - false - )); - assert!(!closure1.type_check( - &mut db, - TypeId::Closure(closure3), - &mut ctx, - false - )); - assert!(!closure1.type_check( - &mut db, - TypeId::Closure(closure4), - &mut ctx, - false - )); - } - - #[test] - fn test_closure_id_type_check_with_type_parameter() { - let mut db = Database::new(); - let closure = Closure::alloc(&mut db, false); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string_ins = TraitInstance::new(to_string); - let param1 = TypeParameter::alloc(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "B".to_string()); - - param2.add_requirements(&mut db, vec![to_string_ins]); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let self_type = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(self_type); - - assert!(closure.type_check( - &mut db, - TypeId::TypeParameter(param1), - &mut ctx, - false - )); - assert!(!closure.type_check( - &mut db, - TypeId::TypeParameter(param2), - &mut ctx, - false - )); - } - - #[test] - fn test_closure_id_as_rigid_type_with_regular_function() { - let mut db = Database::new(); - let closure = Closure::alloc(&mut db, false); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - let to_s_type = TypeRef::Owned(TypeId::TraitInstance(to_s_ins)); - - closure.new_argument(&mut db, "a".to_string(), to_s_type, to_s_type); - - let bounds = TypeBounds::new(); - let new_closure = closure.as_rigid_type(&mut db, &bounds); - - assert_eq!(new_closure, ClosureId(1)); - } - - #[test] - fn test_closure_id_as_rigid_type_with_generic_function() { - let mut db = Database::new(); - let closure = Closure::alloc(&mut db, false); - let param = TypeParameter::alloc(&mut db, "T".to_string()); - let param_type = TypeRef::Owned(TypeId::TypeParameter(param)); - - closure.new_argument(&mut db, "a".to_string(), param_type, param_type); - closure.set_throw_type(&mut db, param_type); - closure.set_return_type(&mut db, param_type); - - let bounds = TypeBounds::new(); - let new_closure = closure.as_rigid_type(&mut db, &bounds); - - assert_ne!(closure, new_closure); - - let new_arg = new_closure.get(&db).arguments.get("a").unwrap(); - - assert_eq!(new_arg.value_type, param.as_owned_rigid(),); - assert_eq!( - new_closure.throw_type(&db), - TypeParameterId(0).as_owned_rigid() - ); - assert_eq!( - new_closure.return_type(&db), - TypeParameterId(0).as_owned_rigid() - ); - } - - #[test] - fn test_type_ref_type_check_with_owned() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let string_typ = TypeRef::Owned(string_ins); - let string_ref_typ = TypeRef::Ref(string_ins); - let int_typ = TypeRef::Owned(int_ins); - - let mut ctx = TypeContext::new(string_ins); - assert!(string_typ.type_check(&mut db, string_typ, &mut ctx, false)); - - let mut ctx = TypeContext::new(string_ins); - assert!(string_typ.type_check(&mut db, TypeRef::Any, &mut ctx, false)); - - let mut ctx = TypeContext::new(string_ins); - assert!(string_typ.type_check( - &mut db, - TypeRef::OwnedSelf, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(string_typ.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(!string_typ.type_check( - &mut db, - string_ref_typ, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(!string_typ.type_check( - &mut db, - TypeRef::RefSelf, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(!string_typ.type_check( - &mut db, - TypeRef::Unknown, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(!string_typ.type_check(&mut db, int_typ, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_ref() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let string_typ = TypeRef::Ref(string_ins); - let mut ctx_int = TypeContext::new(int_ins); - let mut ctx_str = TypeContext::new(string_ins); - - assert!(string_typ.type_check( - &mut db, - string_typ, - &mut ctx_int, - false - )); - assert!(string_typ.type_check( - &mut db, - TypeRef::Error, - &mut ctx_int, - false - )); - assert!(string_typ.type_check( - &mut db, - TypeRef::RefSelf, - &mut ctx_str, - false - )); - assert!(!string_typ.type_check( - &mut db, - TypeRef::Owned(string_ins), - &mut ctx_int, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_mut() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let string_typ = TypeRef::Mut(string_ins); - let mut ctx_int = TypeContext::new(int_ins); - let mut ctx_str = TypeContext::new(string_ins); - - assert!(string_typ.type_check( - &mut db, - string_typ, - &mut ctx_int, - false - )); - assert!(string_typ.type_check( - &mut db, - TypeRef::Error, - &mut ctx_int, - false - )); - assert!(string_typ.type_check( - &mut db, - TypeRef::RefSelf, - &mut ctx_str, - false - )); - assert!(string_typ.type_check( - &mut db, - TypeRef::MutSelf, - &mut ctx_str, - false - )); - assert!(!string_typ.type_check( - &mut db, - TypeRef::Owned(string_ins), - &mut ctx_int, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_mut_trait() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let to_string = TraitInstance::new(Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - )); - - string.add_trait_implementation( - &mut db, - TraitImplementation { - instance: to_string, - bounds: TypeBounds::new(), - }, - ); - - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let string_typ = TypeRef::Mut(string_ins); - let mut ctx = TypeContext::new(string_ins); - - assert!(string_typ.type_check( - &mut db, - TypeRef::Ref(TypeId::TraitInstance(to_string)), - &mut ctx, - true - )); - - assert!(!string_typ.type_check( - &mut db, - TypeRef::Mut(TypeId::TraitInstance(to_string)), - &mut ctx, - true - )); - } - - #[test] - fn test_type_ref_type_check_with_infer() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let param = TypeParameter::alloc(&mut db, "T".to_string()); - let param_ins = TypeId::TypeParameter(param); - let param_typ = TypeRef::Infer(param_ins); - - let mut ctx = TypeContext::new(int_ins); - assert!(param_typ.type_check(&mut db, param_typ, &mut ctx, false)); - - let mut ctx = TypeContext::new(int_ins); - assert!(param_typ.type_check(&mut db, TypeRef::Error, &mut ctx, false)); - - let mut ctx = TypeContext::new(param_ins); - assert!(!param_typ.type_check( - &mut db, - TypeRef::RefSelf, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(int_ins); - assert!(!param_typ.type_check( - &mut db, - TypeRef::Owned(param_ins), - &mut ctx, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_never() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(TypeRef::Never.type_check( - &mut db, - TypeRef::Never, - &mut ctx, - false - )); - assert!(TypeRef::Never.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - assert!(TypeRef::Never.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_any() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(string))); - let param = TypeRef::Owned(TypeId::TypeParameter( - TypeParameter::alloc(&mut db, "T".to_string()), - )); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(TypeRef::Any.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - assert!(TypeRef::Any.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - assert!(!TypeRef::Any.type_check(&mut db, param, &mut ctx, false)); - assert!(!TypeRef::Any.type_check( - &mut db, - TypeRef::OwnedSelf, - &mut ctx, - false - )); - assert!(!TypeRef::Any.type_check( - &mut db, - TypeRef::RefSelf, - &mut ctx, - false - )); - assert!(!TypeRef::Any.type_check(&mut db, string_ins, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_owned_self() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - - let mut ctx = TypeContext::new(string_ins); - assert!(TypeRef::OwnedSelf.type_check( - &mut db, - TypeRef::Owned(string_ins), - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(TypeRef::OwnedSelf.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(TypeRef::OwnedSelf.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - - let mut ctx = TypeContext::new(string_ins); - assert!(!TypeRef::OwnedSelf.type_check( - &mut db, - TypeRef::Ref(string_ins), - &mut ctx, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_ref_self() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let string_ref = TypeRef::Ref(string_ins); - let mut ctx = TypeContext::new(string_ins); - - assert!( - TypeRef::RefSelf.type_check(&mut db, string_ref, &mut ctx, false) - ); - assert!(TypeRef::RefSelf.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - assert!(!TypeRef::RefSelf.type_check( - &mut db, - TypeRef::OwnedSelf, - &mut ctx, - false - )); - assert!(!TypeRef::RefSelf.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - assert!(!TypeRef::RefSelf.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_error() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(TypeRef::Error.type_check( - &mut db, - TypeRef::Error, - &mut ctx, - false - )); - assert!(TypeRef::Error.type_check( - &mut db, - TypeRef::Never, - &mut ctx, - false - )); - assert!(TypeRef::Error.type_check( - &mut db, - TypeRef::Any, - &mut ctx, - false - )); - } - - #[test] - fn test_type_ref_type_check_with_unknown() { - let mut db = Database::new(); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - let unknown = TypeRef::Unknown; - - assert!(!unknown.type_check(&mut db, unknown, &mut ctx, false)); - assert!(!unknown.type_check(&mut db, TypeRef::Error, &mut ctx, false)); - assert!(!unknown.type_check(&mut db, TypeRef::Never, &mut ctx, false)); - assert!(!unknown.type_check(&mut db, TypeRef::Any, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_type_parameter() { - let mut db = Database::new(); - let param1 = TypeParameter::alloc(&mut db, "A".to_string()); - let param2 = TypeParameter::alloc(&mut db, "B".to_string()); - let trait_id = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let ins = TypeId::TraitInstance(TraitInstance::new(trait_id)); - let ins_type = TypeRef::Owned(ins); - - param1.add_requirements(&mut db, vec![TraitInstance::new(trait_id)]); - - { - let mut ctx = TypeContext::new(ins); - - assert!(!ins_type - .is_compatible_with_type_parameter(&mut db, param1, &mut ctx)); - } - - { - let mut ctx = TypeContext::new(ins); - - assert!(ins_type - .is_compatible_with_type_parameter(&mut db, param2, &mut ctx)); - } - - { - let mut ctx = TypeContext::new(ins); - - assert!(TypeRef::OwnedSelf - .is_compatible_with_type_parameter(&mut db, param2, &mut ctx)); - } - - { - let mut ctx = TypeContext::new(ins); - - assert!(TypeRef::RefSelf - .is_compatible_with_type_parameter(&mut db, param2, &mut ctx)); - } - - { - let mut ctx = TypeContext::new(ins); - - assert!(TypeRef::Owned(TypeId::TypeParameter(param1)) - .type_check(&mut db, ins_type, &mut ctx, false)); - } - } - - #[test] - fn test_type_ref_type_check_with_assigned_type_parameter() { - let mut db = Database::new(); - let param = TypeRef::Owned(TypeId::TypeParameter( - TypeParameter::alloc(&mut db, "A".to_string()), - )); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_i = Trait::alloc( - &mut db, - "ToInt".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - let to_i_ins = TraitInstance::new(to_i); - let typ1 = TypeRef::Owned(TypeId::TraitInstance(to_s_ins)); - let typ2 = TypeRef::Owned(TypeId::TraitInstance(to_i_ins)); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(typ1.type_check(&mut db, param, &mut ctx, false)); - assert_eq!(ctx.type_arguments.mapping.len(), 1); - assert!(!typ2.type_check(&mut db, param, &mut ctx, false)); - } - - #[test] - fn test_type_ref_implements_trait_with_class_instance() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string_ins = TraitInstance::new(to_string); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let mut ctx = TypeContext::new(string_ins); - - string.add_trait_implementation( - &mut db, - TraitImplementation { - instance: to_string_ins, - bounds: TypeBounds::new(), - }, - ); - - assert!(TypeRef::Owned(string_ins).implements_trait_instance( - &mut db, - to_string_ins, - &mut ctx - )); - assert!(TypeRef::Ref(string_ins).implements_trait_instance( - &mut db, - to_string_ins, - &mut ctx - )); - assert!(TypeRef::OwnedSelf.implements_trait_instance( - &mut db, - to_string_ins, - &mut ctx - )); - assert!(TypeRef::RefSelf.implements_trait_instance( - &mut db, - to_string_ins, - &mut ctx - )); - } - - #[test] - fn test_type_ref_implements_trait_with_trait_instance() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_foo = Trait::alloc( - &mut db, - "ToFoo".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_s_ins = TraitInstance::new(to_s); - let debug_ins = TypeId::TraitInstance(TraitInstance::new(debug)); - let to_foo_ins = TypeId::TraitInstance(TraitInstance::new(to_foo)); - - debug.add_required_trait(&mut db, to_s_ins); - - { - let req = TraitInstance::new(debug); - - to_foo.add_required_trait(&mut db, req); - } - - let mut ctx = TypeContext::new(debug_ins); - - assert!(TypeRef::Owned(debug_ins) - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - assert!(TypeRef::Owned(to_foo_ins) - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - assert!(TypeRef::Ref(debug_ins) - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - assert!(TypeRef::Infer(debug_ins) - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - assert!(TypeRef::OwnedSelf - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - assert!(TypeRef::RefSelf - .implements_trait_instance(&mut db, to_s_ins, &mut ctx)); - } - - #[test] - fn test_type_ref_implements_trait_with_type_parameter() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_foo = Trait::alloc( - &mut db, - "ToFoo".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param = TypeParameter::alloc(&mut db, "T".to_string()); - let to_string_ins = TraitInstance::new(to_string); - let debug_ins = TraitInstance::new(debug); - let to_foo_ins = TraitInstance::new(to_foo); - let param_ins = TypeRef::Owned(TypeId::TypeParameter(param)); - - debug.add_required_trait(&mut db, to_string_ins); - param.add_requirements(&mut db, vec![debug_ins]); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!( - param_ins.implements_trait_instance(&mut db, debug_ins, &mut ctx) - ); - assert!(param_ins.implements_trait_instance( - &mut db, - to_string_ins, - &mut ctx - )); - assert!( - !param_ins.implements_trait_instance(&mut db, to_foo_ins, &mut ctx) - ); - } - - #[test] - fn test_type_ref_implements_trait_with_other_variants() { - let mut db = Database::new(); - let trait_type = Trait::alloc( - &mut db, - "ToA".to_string(), - ModuleId(0), - Visibility::Private, - ); - let ins = TraitInstance::new(trait_type); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(!TypeRef::Any.implements_trait_instance(&mut db, ins, &mut ctx)); - assert!( - !TypeRef::Unknown.implements_trait_instance(&mut db, ins, &mut ctx) - ); - assert!( - TypeRef::Error.implements_trait_instance(&mut db, ins, &mut ctx) - ); - assert!( - TypeRef::Never.implements_trait_instance(&mut db, ins, &mut ctx) - ); - } - - #[test] - fn test_type_ref_type_name() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); - let param = TypeId::TypeParameter(TypeParameter::alloc( - &mut db, - "T".to_string(), - )); - - assert_eq!( - format_type(&db, TypeRef::Owned(string_ins)), - "String".to_string() - ); - assert_eq!(format_type(&db, TypeRef::Infer(param)), "T".to_string()); - assert_eq!( - format_type(&db, TypeRef::Ref(string_ins)), - "ref String".to_string() - ); - assert_eq!(format_type(&db, TypeRef::Never), "Never".to_string()); - assert_eq!(format_type(&db, TypeRef::Any), "Any".to_string()); - assert_eq!(format_type(&db, TypeRef::OwnedSelf), "Self".to_string()); - assert_eq!(format_type(&db, TypeRef::RefSelf), "ref Self".to_string()); - assert_eq!(format_type(&db, TypeRef::Error), "".to_string()); - assert_eq!(format_type(&db, TypeRef::Unknown), "".to_string()); - } - - #[test] - fn test_type_id_named_type_with_class() { - let mut db = Database::new(); - let array = Class::alloc( - &mut db, - "Array".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let param = array.new_type_parameter(&mut db, "T".to_string()); - - assert_eq!( - TypeId::Class(array).named_type(&db, "T"), - Some(Symbol::TypeParameter(param)) - ); - } - - #[test] - fn test_type_id_named_type_with_trait() { - let mut db = Database::new(); - let to_array = Trait::alloc( - &mut db, - "ToArray".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param = to_array.new_type_parameter(&mut db, "T".to_string()); - - assert_eq!( - TypeId::Trait(to_array).named_type(&db, "T"), - Some(Symbol::TypeParameter(param)) - ); - } - - #[test] - fn test_type_id_named_type_with_module() { - let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let module = - Module::alloc(&mut db, ModuleName::new("foo"), "foo.inko".into()); - - let symbol = Symbol::Class(string); - let type_id = TypeId::Module(module); - - module.new_symbol(&mut db, "String".to_string(), symbol); - - assert_eq!(type_id.named_type(&db, "String"), Some(symbol)); - assert!(type_id.named_type(&db, "Foo").is_none()); - } - - #[test] - fn test_type_id_named_type_with_class_instance() { - let mut db = Database::new(); - let array = Class::alloc( - &mut db, - "Array".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let param = array.new_type_parameter(&mut db, "T".to_string()); - let ins = TypeId::ClassInstance(ClassInstance::generic( - &mut db, - array, - TypeArguments::new(), - )); - - assert_eq!( - ins.named_type(&db, "T"), - Some(Symbol::TypeParameter(param)) - ); - assert!(ins.named_type(&db, "E").is_none()); - } - - #[test] - fn test_type_id_named_type_with_trait_instance() { - let mut db = Database::new(); - let to_array = Trait::alloc( - &mut db, - "ToArray".to_string(), - ModuleId(0), - Visibility::Private, - ); - let param = to_array.new_type_parameter(&mut db, "T".to_string()); - let ins = TypeId::TraitInstance(TraitInstance::generic( - &mut db, - to_array, - TypeArguments::new(), - )); - - assert_eq!( - ins.named_type(&db, "T"), - Some(Symbol::TypeParameter(param)) - ); - assert!(ins.named_type(&db, "E").is_none()); - } - - #[test] - fn test_type_id_named_type_with_type_parameter() { - let mut db = Database::new(); - let param = TypeId::TypeParameter(TypeParameter::alloc( - &mut db, - "T".to_string(), - )); - - assert!(param.named_type(&db, "T").is_none()); - } - - #[test] - fn test_type_id_named_type_with_function() { - let mut db = Database::new(); - let block = TypeId::Closure(Closure::alloc(&mut db, false)); - - assert!(block.named_type(&db, "T").is_none()); - } - - #[test] - fn test_type_ref_type_check_with_class() { - let mut db = Database::new(); - let typ1 = TypeRef::Owned(TypeId::Class(ClassId(0))); - let typ2 = TypeRef::Owned(TypeId::Class(ClassId(1))); - let typ3 = TypeRef::Owned(TypeId::Trait(TraitId(0))); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(typ1.type_check(&mut db, typ1, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ2, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ3, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_trait() { - let mut db = Database::new(); - let typ1 = TypeRef::Owned(TypeId::Trait(TraitId(0))); - let typ2 = TypeRef::Owned(TypeId::Trait(TraitId(1))); - let typ3 = TypeRef::Owned(TypeId::Class(ClassId(0))); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(typ1.type_check(&mut db, typ1, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ2, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ3, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_module() { - let mut db = Database::new(); - let typ1 = TypeRef::Owned(TypeId::Module(ModuleId(0))); - let typ2 = TypeRef::Owned(TypeId::Module(ModuleId(1))); - let typ3 = TypeRef::Owned(TypeId::Class(ClassId(0))); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(typ1.type_check(&mut db, typ1, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ2, &mut ctx, false)); - assert!(!typ1.type_check(&mut db, typ3, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_class_instance() { - let mut db = Database::new(); - let cls1 = Class::alloc( - &mut db, - "A".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let cls2 = Class::alloc( - &mut db, - "B".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let ins1 = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(cls1))); - let ins2 = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(cls1))); - let ins3 = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(cls2))); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(ins1.type_check(&mut db, ins1, &mut ctx, false)); - assert!(ins1.type_check(&mut db, ins2, &mut ctx, false)); - assert!(!ins1.type_check(&mut db, ins3, &mut ctx, false)); - } - - #[test] - fn test_type_ref_type_check_with_trait_instance() { - let mut db = Database::new(); - let debug = Trait::alloc( - &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let requirement = TraitInstance::new(to_string); - - debug.add_required_trait(&mut db, requirement); - - let debug_ins = - TypeRef::Owned(TypeId::TraitInstance(TraitInstance::new(debug))); - let to_string_ins = TypeRef::Owned(TypeId::TraitInstance( - TraitInstance::new(to_string), - )); - - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); - - assert!(debug_ins.type_check(&mut db, to_string_ins, &mut ctx, true)); - } - - #[test] - fn test_type_ref_type_check_with_function() { - let mut db = Database::new(); - let closure1 = Closure::alloc(&mut db, false); - let closure2 = Closure::alloc(&mut db, false); - - closure1.set_return_type(&mut db, TypeRef::Any); - closure2.set_return_type(&mut db, TypeRef::Any); - - let closure1_type = TypeRef::Owned(TypeId::Closure(closure1)); - let closure2_type = TypeRef::Owned(TypeId::Closure(closure2)); - - let int = Class::alloc( + #[test] + fn test_class_instance_generic() { + let mut db = Database::new(); + let id = Class::alloc( &mut db, - "Int".to_string(), + "A".to_string(), ClassKind::Regular, Visibility::Private, ModuleId(0), ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); + let ins1 = ClassInstance::generic(&mut db, id, TypeArguments::new()); + let ins2 = ClassInstance::generic(&mut db, id, TypeArguments::new()); - assert!(closure1_type.type_check( - &mut db, - closure2_type, - &mut ctx, - false - )); + assert_eq!(ins1.instance_of.0, FIRST_USER_CLASS_ID); + assert_eq!(ins1.type_arguments, 0); + + assert_eq!(ins2.instance_of.0, FIRST_USER_CLASS_ID); + assert_eq!(ins2.type_arguments, 1); } #[test] - fn test_type_id_format_type_with_class() { + fn test_method_alloc() { let mut db = Database::new(); - let id = TypeId::Class(Class::alloc( + let id = Method::alloc( &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, ModuleId(0), - )); + "foo".to_string(), + Visibility::Private, + MethodKind::Moving, + ); - assert_eq!(format_type(&db, id), "String"); + assert_eq!(id.0, 0); + assert_eq!(&db.methods[0].name, &"foo".to_string()); + assert_eq!(db.methods[0].kind, MethodKind::Moving); } #[test] - fn test_type_id_format_type_with_trait() { + fn test_method_id_named_type() { let mut db = Database::new(); - let id = TypeId::Trait(Trait::alloc( + let method = Method::alloc( &mut db, - "ToString".to_string(), ModuleId(0), + "foo".to_string(), Visibility::Private, - )); + MethodKind::Instance, + ); + let param = method.new_type_parameter(&mut db, "A".to_string()); - assert_eq!(format_type(&db, id), "ToString"); + assert_eq!( + method.named_type(&db, "A"), + Some(Symbol::TypeParameter(param)) + ); } #[test] - fn test_type_id_format_type_with_module() { + fn test_module_alloc() { let mut db = Database::new(); - let id = TypeId::Module(Module::alloc( - &mut db, - ModuleName::new("foo::bar"), - "foo/bar.inko".into(), - )); + let name = ModuleName::new("foo"); + let id = Module::alloc(&mut db, name.clone(), "foo.inko".into()); - assert_eq!(format_type(&db, id), "foo::bar"); + assert_eq!(id.0, 0); + assert_eq!(&db.modules[0].name, &name); + assert_eq!(&db.modules[0].file, &PathBuf::from("foo.inko")); } #[test] - fn test_type_id_format_type_with_class_instance() { + fn test_module_id_file() { let mut db = Database::new(); - let id = Class::alloc( + let id = Module::alloc( &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), + ModuleName::new("foo"), + PathBuf::from("test.inko"), ); - let ins = TypeId::ClassInstance(ClassInstance::new(id)); - assert_eq!(format_type(&db, ins), "String"); + assert_eq!(id.file(&db), PathBuf::from("test.inko")); } #[test] - fn test_type_id_format_type_with_tuple_instance() { + fn test_module_id_symbol() { let mut db = Database::new(); - let id = Class::alloc( + let id = Module::alloc( &mut db, - "MyTuple".to_string(), - ClassKind::Tuple, - Visibility::Private, - ModuleId(0), + ModuleName::new("foo"), + PathBuf::from("test.inko"), ); - let param1 = id.new_type_parameter(&mut db, "A".to_string()); - let param2 = id.new_type_parameter(&mut db, "B".to_string()); - let mut args = TypeArguments::new(); - - args.assign(param1, TypeRef::Any); - args.assign(param2, TypeRef::Never); - let ins = - TypeId::ClassInstance(ClassInstance::generic(&mut db, id, args)); + id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); - assert_eq!(format_type(&db, ins), "(Any, Never)"); + assert_eq!(id.symbol(&db, "A"), Some(Symbol::Module(id))); } #[test] - fn test_type_id_format_type_with_trait_instance() { + fn test_module_id_symbols() { let mut db = Database::new(); - let id = Trait::alloc( + let id = Module::alloc( &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, + ModuleName::new("foo"), + PathBuf::from("test.inko"), ); - let ins = TypeId::TraitInstance(TraitInstance::new(id)); - assert_eq!(format_type(&db, ins), "ToString"); + id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); + + assert_eq!( + id.symbols(&db), + vec![("A".to_string(), Symbol::Module(id))] + ); } #[test] - fn test_type_id_format_type_with_generic_class_instance() { + fn test_module_id_symbol_exists() { let mut db = Database::new(); - let id = Class::alloc( + let id = Module::alloc( &mut db, - "Future".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), + ModuleName::new("foo"), + PathBuf::from("test.inko"), ); - let param1 = id.new_type_parameter(&mut db, "T".to_string()); - id.new_type_parameter(&mut db, "E".to_string()); - - let mut targs = TypeArguments::new(); + id.new_symbol(&mut db, "A".to_string(), Symbol::Module(id)); - targs.assign(param1, TypeRef::Any); + assert!(id.symbol_exists(&db, "A")); + assert!(!id.symbol_exists(&db, "B")); + } - let ins = - TypeId::ClassInstance(ClassInstance::generic(&mut db, id, targs)); + #[test] + fn test_function_closure() { + let mut db = Database::new(); + let id = Closure::alloc(&mut db, false); - assert_eq!(format_type(&db, ins), "Future[Any, E]"); + assert_eq!(id.0, 0); } #[test] - fn test_type_id_format_type_with_generic_trait_instance() { + fn test_type_id_named_type_with_class() { let mut db = Database::new(); - let id = Trait::alloc( + let array = Class::alloc( &mut db, - "ToFoo".to_string(), - ModuleId(0), + "Array".to_string(), + ClassKind::Regular, Visibility::Private, + ModuleId(0), ); - let param1 = id.new_type_parameter(&mut db, "T".to_string()); - - id.new_type_parameter(&mut db, "E".to_string()); - - let mut targs = TypeArguments::new(); - - targs.assign(param1, TypeRef::Any); - - let ins = - TypeId::TraitInstance(TraitInstance::generic(&mut db, id, targs)); + let param = array.new_type_parameter(&mut db, "T".to_string()); - assert_eq!(format_type(&db, ins), "ToFoo[Any, E]"); + assert_eq!( + TypeId::Class(array).named_type(&db, "T"), + Some(Symbol::TypeParameter(param)) + ); } #[test] - fn test_type_id_format_type_with_type_parameter() { + fn test_type_id_named_type_with_trait() { let mut db = Database::new(); - let param1 = TypeParameter::alloc(&mut db, "T".to_string()); - let param2 = TypeParameter::alloc(&mut db, "T".to_string()); - let to_string = Trait::alloc( - &mut db, - "ToString".to_string(), - ModuleId(0), - Visibility::Private, - ); - let to_int = Trait::alloc( + let to_array = Trait::alloc( &mut db, - "ToInt".to_string(), + "ToArray".to_string(), ModuleId(0), Visibility::Private, ); - let param1_ins = TypeId::TypeParameter(param1); - let param2_ins = TypeId::TypeParameter(param2); - let to_string_ins = TraitInstance::new(to_string); - let to_int_ins = TraitInstance::new(to_int); - - param1.add_requirements(&mut db, vec![to_string_ins]); - param2.add_requirements(&mut db, vec![to_string_ins, to_int_ins]); + let param = to_array.new_type_parameter(&mut db, "T".to_string()); - assert_eq!(format_type(&db, param1_ins), "T: ToString"); - assert_eq!(format_type(&db, param2_ins), "T: ToString + ToInt"); + assert_eq!( + TypeId::Trait(to_array).named_type(&db, "T"), + Some(Symbol::TypeParameter(param)) + ); } #[test] - fn test_type_id_format_type_with_rigid_type_parameter() { + fn test_type_id_named_type_with_module() { let mut db = Database::new(); - let param1 = TypeParameter::alloc(&mut db, "T".to_string()); - let param2 = TypeParameter::alloc(&mut db, "T".to_string()); - let to_string = Trait::alloc( + let string = Class::alloc( &mut db, - "ToString".to_string(), - ModuleId(0), + "String".to_string(), + ClassKind::Regular, Visibility::Private, - ); - let to_int = Trait::alloc( - &mut db, - "ToInt".to_string(), ModuleId(0), - Visibility::Private, ); - let param1_ins = TypeId::RigidTypeParameter(param1); - let param2_ins = TypeId::RigidTypeParameter(param2); - let to_string_ins = TraitInstance::new(to_string); - let to_int_ins = TraitInstance::new(to_int); + let module = + Module::alloc(&mut db, ModuleName::new("foo"), "foo.inko".into()); + + let symbol = Symbol::Class(string); + let type_id = TypeId::Module(module); - param1.add_requirements(&mut db, vec![to_string_ins]); - param2.add_requirements(&mut db, vec![to_string_ins, to_int_ins]); + module.new_symbol(&mut db, "String".to_string(), symbol); - assert_eq!(format_type(&db, param1_ins), "T: ToString"); - assert_eq!(format_type(&db, param2_ins), "T: ToString + ToInt"); + assert_eq!(type_id.named_type(&db, "String"), Some(symbol)); + assert!(type_id.named_type(&db, "Foo").is_none()); } #[test] - fn test_type_id_format_type_with_closure() { + fn test_type_id_named_type_with_class_instance() { let mut db = Database::new(); - let class_a = Class::alloc( + let array = Class::alloc( &mut db, - "A".to_string(), + "Array".to_string(), ClassKind::Regular, Visibility::Private, ModuleId(0), ); - let class_b = Class::alloc( + let param = array.new_type_parameter(&mut db, "T".to_string()); + let ins = TypeId::ClassInstance(ClassInstance::generic( &mut db, - "B".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), + array, + TypeArguments::new(), + )); + + assert_eq!( + ins.named_type(&db, "T"), + Some(Symbol::TypeParameter(param)) ); - let class_c = Class::alloc( + assert!(ins.named_type(&db, "E").is_none()); + } + + #[test] + fn test_type_id_named_type_with_trait_instance() { + let mut db = Database::new(); + let to_array = Trait::alloc( &mut db, - "C".to_string(), - ClassKind::Regular, - Visibility::Private, + "ToArray".to_string(), ModuleId(0), - ); - let class_d = Class::alloc( - &mut db, - "D".to_string(), - ClassKind::Regular, Visibility::Private, - ModuleId(0), ); - let block = Closure::alloc(&mut db, true); - - let ins_a = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_a))); - - let ins_b = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_b))); - - let ins_c = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_c))); - - let ins_d = - TypeRef::Owned(TypeId::ClassInstance(ClassInstance::new(class_d))); - - block.new_argument(&mut db, "a".to_string(), ins_a, ins_a); - block.new_argument(&mut db, "b".to_string(), ins_b, ins_b); - block.set_throw_type(&mut db, ins_c); - block.set_return_type(&mut db, ins_d); - - let block_ins = TypeId::Closure(block); + let param = to_array.new_type_parameter(&mut db, "T".to_string()); + let ins = TypeId::TraitInstance(TraitInstance::generic( + &mut db, + to_array, + TypeArguments::new(), + )); - assert_eq!(format_type(&db, block_ins), "fn move (A, B) !! C -> D"); + assert_eq!( + ins.named_type(&db, "T"), + Some(Symbol::TypeParameter(param)) + ); + assert!(ins.named_type(&db, "E").is_none()); } #[test] - fn test_type_id_implements_trait_with_other_variants() { + fn test_type_id_named_type_with_type_parameter() { let mut db = Database::new(); - let closure = TypeId::Closure(Closure::alloc(&mut db, false)); - let debug = Trait::alloc( + let param = TypeId::TypeParameter(TypeParameter::alloc( &mut db, - "Debug".to_string(), - ModuleId(0), - Visibility::Private, - ); - let debug_ins = TraitInstance::new(debug); + "T".to_string(), + )); - let int = Class::alloc( - &mut db, - "Int".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - ); - let int_ins = TypeId::ClassInstance(ClassInstance::new(int)); - let mut ctx = TypeContext::new(int_ins); + assert!(param.named_type(&db, "T").is_none()); + } - assert!( - !closure.implements_trait_instance(&mut db, debug_ins, &mut ctx) - ); + #[test] + fn test_type_id_named_type_with_function() { + let mut db = Database::new(); + let block = TypeId::Closure(Closure::alloc(&mut db, false)); + + assert!(block.named_type(&db, "T").is_none()); } #[test] @@ -8776,7 +4850,7 @@ mod tests { assert_eq!(&db.classes[4].name, BOOLEAN_NAME); assert_eq!(&db.classes[5].name, NIL_NAME); assert_eq!(&db.classes[6].name, BYTE_ARRAY_NAME); - assert_eq!(&db.classes[7].name, FUTURE_NAME); + assert_eq!(&db.classes[7].name, CHANNEL_NAME); } #[test] @@ -8796,11 +4870,19 @@ mod tests { db.module("foo"); } + #[test] + fn test_class_id_is_builtin() { + assert!(ClassId::int().is_builtin()); + assert!(!ClassId::tuple8().is_builtin()); + assert!(!ClassId(42).is_builtin()); + } + #[test] fn test_type_placeholder_id_assign() { let mut db = Database::new(); - let p1 = TypePlaceholder::alloc(&mut db); - let p2 = TypePlaceholder::alloc(&mut db); + let param = TypeParameter::alloc(&mut db, "T".to_string()); + let p1 = TypePlaceholder::alloc(&mut db, Some(param)); + let p2 = TypePlaceholder::alloc(&mut db, Some(param)); p1.assign(&db, TypeRef::Any); p2.assign(&db, TypeRef::Placeholder(p2)); @@ -8808,4 +4890,91 @@ mod tests { assert_eq!(p1.value(&db), Some(TypeRef::Any)); assert!(p2.value(&db).is_none()); } + + #[test] + fn test_type_placeholder_id_resolve() { + let mut db = Database::new(); + let var1 = TypePlaceholder::alloc(&mut db, None); + let var2 = TypePlaceholder::alloc(&mut db, None); + let var3 = TypePlaceholder::alloc(&mut db, None); + + var1.assign(&db, TypeRef::Any); + var2.assign(&db, TypeRef::Placeholder(var1)); + var3.assign(&db, TypeRef::Placeholder(var2)); + + assert_eq!(var1.value(&db), Some(TypeRef::Any)); + assert_eq!(var2.value(&db), Some(TypeRef::Any)); + assert_eq!(var3.value(&db), Some(TypeRef::Any)); + } + + #[test] + fn test_type_ref_allow_as_ref() { + let mut db = Database::new(); + let int = ClassId::int(); + let var = TypePlaceholder::alloc(&mut db, None); + let param = new_parameter(&mut db, "A"); + + var.assign(&db, owned(instance(int))); + + assert!(owned(instance(int)).allow_as_ref(&db)); + assert!(mutable(instance(int)).allow_as_ref(&db)); + assert!(immutable(instance(int)).allow_as_ref(&db)); + assert!(placeholder(var).allow_as_ref(&db)); + assert!(owned(rigid(param)).allow_as_ref(&db)); + assert!(TypeRef::Any.allow_as_ref(&db)); + assert!(!uni(instance(int)).allow_as_ref(&db)); + } + + #[test] + fn test_type_ref_allow_as_mut() { + let mut db = Database::new(); + let int = ClassId::int(); + let var = TypePlaceholder::alloc(&mut db, None); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "A"); + + param2.set_mutable(&mut db); + var.assign(&db, owned(instance(int))); + + assert!(owned(instance(int)).allow_as_mut(&db)); + assert!(mutable(instance(int)).allow_as_mut(&db)); + assert!(placeholder(var).allow_as_mut(&db)); + assert!(TypeRef::Any.allow_as_mut(&db)); + assert!(owned(rigid(param2)).allow_as_mut(&db)); + assert!(!immutable(instance(int)).allow_as_mut(&db)); + assert!(!owned(rigid(param1)).allow_as_mut(&db)); + assert!(!uni(instance(int)).allow_as_mut(&db)); + } + + #[test] + fn test_type_ref_as_ref() { + let mut db = Database::new(); + let int = ClassId::int(); + let param = new_parameter(&mut db, "A"); + + assert_eq!(owned(instance(int)).as_ref(&db), immutable(instance(int))); + assert_eq!( + uni(instance(int)).as_ref(&db), + TypeRef::RefUni(instance(int)) + ); + assert_eq!(owned(rigid(param)).as_ref(&db), immutable(rigid(param))); + } + + #[test] + fn test_type_ref_as_mut() { + let mut db = Database::new(); + let int = ClassId::int(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "A"); + + param2.set_mutable(&mut db); + + assert_eq!(owned(instance(int)).as_mut(&db), mutable(instance(int))); + assert_eq!( + uni(instance(int)).as_mut(&db), + TypeRef::MutUni(instance(int)) + ); + assert_eq!(owned(rigid(param1)).as_mut(&db), owned(rigid(param1))); + assert_eq!(owned(rigid(param2)).as_mut(&db), mutable(rigid(param2))); + } } diff --git a/types/src/module_name.rs b/types/src/module_name.rs index 2b38d9b00..4d2bf7054 100644 --- a/types/src/module_name.rs +++ b/types/src/module_name.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf, MAIN_SEPARATOR}; const MAIN_MODULE: &str = "main"; const SOURCE_EXT: &str = "inko"; -const SEPARATOR: &str = "::"; +pub const SEPARATOR: &str = "::"; /// The fully qualified name of a module. #[derive(Eq, PartialEq, Hash, Clone, Ord, PartialOrd)] @@ -50,6 +50,10 @@ impl ModuleName { path } + pub fn normalized_name(&self) -> String { + self.value.replace(SEPARATOR, "_") + } + pub fn as_str(&self) -> &str { self.value.as_str() } @@ -131,4 +135,12 @@ mod tests { assert_eq!(format!("{:?}", name), "ModuleName(foo::bar)".to_string()); } + + #[test] + fn test_normalized_name() { + assert_eq!( + ModuleName::new("std::foo::bar").normalized_name(), + "std_foo_bar" + ); + } } diff --git a/types/src/resolve.rs b/types/src/resolve.rs new file mode 100644 index 000000000..c18b85cb0 --- /dev/null +++ b/types/src/resolve.rs @@ -0,0 +1,804 @@ +//! Resolving abstract types into concrete types. +use crate::either::Either; +use crate::{ + ClassInstance, Closure, Database, TraitId, TraitInstance, TypeArguments, + TypeBounds, TypeId, TypeParameterId, TypeRef, +}; +use std::collections::HashMap; + +/// A type that takes an abstract type and resolves it into a more concrete +/// type. +/// +/// For example, if a method has any type parameter bounds then this type +/// ensures any regular type parameters are turned into their corresponding +/// bounds. +pub struct TypeResolver<'a> { + db: &'a mut Database, + + /// A cache of types we've already resolved. + /// + /// This cache is used to handle recursive types, such as a type parameter + /// assigned to a placeholder that's assigned to itself. + cached: HashMap, + + /// The type arguments to use when resolving type parameters. + type_arguments: &'a TypeArguments, + + /// Any type parameters that have additional bounds set. + /// + /// If a type parameter is present in this structure, it's bounded version + /// is produced when resolving the parameter. + bounds: &'a TypeBounds, + + /// If resolved types should be made immutable or not. + immutable: bool, + + /// When set to true, non-rigid type parameters are turned into rigid + /// parameters instead of placeholders. + rigid: bool, + + /// The surrounding trait definition, if any. + /// + /// If present it's used to remap inherited type parameters to their correct + /// types. + surrounding_trait: Option, +} + +impl<'a> TypeResolver<'a> { + pub fn new( + db: &'a mut Database, + type_arguments: &'a TypeArguments, + bounds: &'a TypeBounds, + ) -> TypeResolver<'a> { + TypeResolver { + db, + type_arguments, + bounds, + immutable: false, + rigid: false, + surrounding_trait: None, + cached: HashMap::new(), + } + } + + pub fn with_immutable(mut self, immutable: bool) -> TypeResolver<'a> { + self.immutable = immutable; + self + } + + pub fn with_rigid(mut self, rigid: bool) -> TypeResolver<'a> { + self.rigid = rigid; + self + } + + pub fn resolve(&mut self, value: TypeRef) -> TypeRef { + if let Some(&cached) = self.cached.get(&value) { + return cached; + } + + // To handle recursive types we have to add the raw value first, then + // later update it with the resolved value. If we don't do this we'd + // just end up recursing into this method indefinitely. This also + // ensures we handle type parameters assigned to themselves, without + // needing extra logic. + self.cached.insert(value, value); + + let resolved = match value { + TypeRef::Owned(id) | TypeRef::Infer(id) => { + match self.resolve_type_id(id) { + Either::Left(res) => self.owned(res), + Either::Right(typ) => typ, + } + } + TypeRef::Ref(id) => match self.resolve_type_id(id) { + Either::Left(res) => TypeRef::Ref(res), + Either::Right(TypeRef::Owned(typ) | TypeRef::Mut(typ)) => { + TypeRef::Ref(typ) + } + Either::Right(TypeRef::Uni(typ) | TypeRef::MutUni(typ)) => { + TypeRef::RefUni(typ) + } + Either::Right(typ) => typ, + }, + TypeRef::Mut(id) => match self.resolve_type_id(id) { + Either::Left(res) => self.mutable(res), + Either::Right(TypeRef::Owned(typ)) => self.mutable(typ), + Either::Right(TypeRef::Uni(typ)) => self.mutable_uni(typ), + Either::Right(typ) => typ, + }, + TypeRef::Uni(id) => match self.resolve_type_id(id) { + Either::Left(res) => self.uni(res), + Either::Right(TypeRef::Owned(typ)) => self.uni(typ), + Either::Right(typ) => typ, + }, + TypeRef::RefUni(id) => match self.resolve_type_id(id) { + Either::Left(res) => TypeRef::RefUni(res), + Either::Right(typ) => typ, + }, + TypeRef::MutUni(id) => match self.resolve_type_id(id) { + Either::Left(res) => self.mutable_uni(res), + Either::Right(typ) => typ, + }, + // If a placeholder is unassigned we need to return it as-is. This + // way future use of the placeholder allows us to infer the current + // type. + TypeRef::Placeholder(id) => { + id.value(self.db).map(|v| self.resolve(v)).unwrap_or(value) + } + _ => value, + }; + + // No point in hashing again if the value is the same. + if value != resolved { + self.cached.insert(value, resolved); + } + + resolved + } + + fn resolve_type_id(&mut self, id: TypeId) -> Either { + match id { + TypeId::ClassInstance(ins) => { + let base = ins.instance_of; + + if !base.is_generic(self.db) { + return Either::Left(id); + } + + let mut args = ins.type_arguments(self.db).clone(); + + self.resolve_arguments(&mut args); + + Either::Left(TypeId::ClassInstance(ClassInstance::generic( + self.db, base, args, + ))) + } + TypeId::TraitInstance(ins) => { + let base = ins.instance_of; + + if !base.is_generic(self.db) { + return Either::Left(id); + } + + let mut args = ins.type_arguments(self.db).clone(); + + self.resolve_arguments(&mut args); + + Either::Left(TypeId::TraitInstance(TraitInstance::generic( + self.db, base, args, + ))) + } + TypeId::TypeParameter(pid) => { + let pid = self.remap_type_parameter(pid); + + match self.resolve_type_parameter(pid) { + Some(val) => Either::Right(val), + _ if self.rigid => { + Either::Left(TypeId::RigidTypeParameter(pid)) + } + _ => { + Either::Right(TypeRef::placeholder(self.db, Some(pid))) + } + } + } + TypeId::RigidTypeParameter(pid) => Either::Left( + TypeId::RigidTypeParameter(self.remap_type_parameter(pid)), + ), + TypeId::Closure(id) => { + let mut new = id.get(self.db).clone(); + let immutable = self.immutable; + + // The ownership of the closure's arguments and return type + // shouldn't be changed, instead the ability to use the closure + // in the first place is restricted by the type checker where + // needede. + self.immutable = false; + + for arg in new.arguments.mapping.values_mut() { + arg.value_type = self.resolve(arg.value_type); + } + + new.return_type = self.resolve(new.return_type); + self.immutable = immutable; + Either::Left(TypeId::Closure(Closure::add(self.db, new))) + } + _ => Either::Left(id), + } + } + + fn resolve_arguments(&mut self, arguments: &mut TypeArguments) { + for value in arguments.mapping.values_mut() { + *value = self.resolve(*value); + } + } + + fn resolve_type_parameter( + &mut self, + id: TypeParameterId, + ) -> Option { + // Type arguments are always mapped using the original type parameters. + // This way if we have a bounded parameter we can easily look up the + // corresponding argument. + let key = id.original(self.db).unwrap_or(id); + + if let Some(arg) = self.type_arguments.get(key) { + return Some(self.resolve(arg)); + } + + // Inside a trait we may end up referring to type parameters from a + // required trait. In this case we recursively resolve the type + // parameter chain until reaching the final type. + if let Some(arg) = self + .surrounding_trait + .and_then(|t| t.get(self.db).inherited_type_arguments.get(key)) + { + return Some(self.resolve(arg)); + } + + None + } + + fn remap_type_parameter(&self, id: TypeParameterId) -> TypeParameterId { + self.bounds.get(id).unwrap_or(id) + } + + fn owned(&self, id: TypeId) -> TypeRef { + if self.immutable { + TypeRef::Ref(id) + } else { + TypeRef::Owned(id) + } + } + + fn uni(&self, id: TypeId) -> TypeRef { + if self.immutable { + TypeRef::RefUni(id) + } else { + TypeRef::Uni(id) + } + } + + fn mutable(&self, id: TypeId) -> TypeRef { + if self.immutable { + TypeRef::Ref(id) + } else { + TypeRef::Mut(id) + } + } + + fn mutable_uni(&self, id: TypeId) -> TypeRef { + if self.immutable { + TypeRef::RefUni(id) + } else { + TypeRef::MutUni(id) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::{ + closure, generic_instance_id, generic_trait_instance, + generic_trait_instance_id, immutable, immutable_uni, infer, instance, + mutable, mutable_uni, new_parameter, new_trait, owned, parameter, + placeholder, rigid, type_arguments, type_bounds, uni, + }; + use crate::{Block, ClassId, Closure, TypePlaceholder, TypePlaceholderId}; + + fn resolve( + db: &mut Database, + type_arguments: &TypeArguments, + bounds: &TypeBounds, + source: TypeRef, + ) -> TypeRef { + TypeResolver::new(db, type_arguments, bounds).resolve(source) + } + + fn resolve_immutable( + db: &mut Database, + type_arguments: &TypeArguments, + bounds: &TypeBounds, + source: TypeRef, + ) -> TypeRef { + TypeResolver::new(db, type_arguments, bounds) + .with_immutable(true) + .resolve(source) + } + + #[test] + fn test_owned() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(instance(string))), + owned(instance(string)) + ); + + assert_eq!( + resolve_immutable(&mut db, &args, &bounds, owned(instance(string))), + immutable(instance(string)) + ); + } + + #[test] + fn test_infer() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, infer(instance(string))), + owned(instance(string)) + ); + + assert_eq!( + resolve_immutable(&mut db, &args, &bounds, infer(instance(string))), + immutable(instance(string)) + ); + } + + #[test] + fn test_uni() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, uni(instance(string))), + uni(instance(string)) + ); + + assert_eq!( + resolve_immutable(&mut db, &args, &bounds, uni(instance(string))), + immutable_uni(instance(string)) + ); + } + + #[test] + fn test_ref() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable(instance(string))), + immutable(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + immutable(instance(string)) + ), + immutable(instance(string)) + ); + } + + #[test] + fn test_ref_uni() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable_uni(instance(string))), + immutable_uni(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + immutable_uni(instance(string)) + ), + immutable_uni(instance(string)) + ); + } + + #[test] + fn test_mut() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable(instance(string))), + mutable(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + mutable(instance(string)) + ), + immutable(instance(string)) + ); + } + + #[test] + fn test_mut_uni() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable_uni(instance(string))), + mutable_uni(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + mutable_uni(instance(string)) + ), + immutable_uni(instance(string)) + ); + } + + #[test] + fn test_placeholder() { + let mut db = Database::new(); + let string = ClassId::string(); + let args = TypeArguments::new(); + let bounds = TypeBounds::new(); + let var1 = TypePlaceholder::alloc(&mut db, None); + let var2 = TypePlaceholder::alloc(&mut db, None); + + var1.assign(&db, owned(instance(string))); + + assert_eq!( + resolve(&mut db, &args, &bounds, placeholder(var1)), + owned(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, placeholder(var2)), + placeholder(var2) + ); + + assert_eq!( + resolve_immutable(&mut db, &args, &bounds, placeholder(var1)), + immutable(instance(string)) + ); + } + + #[test] + fn test_type_parameter() { + let mut db = Database::new(); + let string = ClassId::string(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let args = type_arguments(vec![(param1, owned(instance(string)))]); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(parameter(param1))), + owned(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(rigid(param1))), + owned(rigid(param1)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, infer(parameter(param1))), + owned(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + infer(parameter(param1)) + ), + immutable(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(rigid(param2))), + owned(rigid(param2)) + ); + } + + #[test] + fn test_type_parameter_as_reference() { + let mut db = Database::new(); + let string = ClassId::string(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let args = type_arguments(vec![ + (param1, owned(instance(string))), + (param2, immutable(instance(string))), + ]); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable(parameter(param1))), + immutable(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable(parameter(param2))), + immutable(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable(parameter(param1))), + mutable(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + mutable(parameter(param1)) + ), + immutable(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable(parameter(param2))), + immutable(instance(string)) + ); + } + + #[test] + fn test_type_parameter_as_uni() { + let mut db = Database::new(); + let string = ClassId::string(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let args = type_arguments(vec![ + (param1, uni(instance(string))), + (param2, immutable_uni(instance(string))), + ]); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable(parameter(param1))), + immutable_uni(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, immutable(parameter(param2))), + immutable_uni(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable(parameter(param1))), + mutable_uni(instance(string)) + ); + + assert_eq!( + resolve_immutable( + &mut db, + &args, + &bounds, + mutable(parameter(param1)) + ), + immutable_uni(instance(string)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, mutable(parameter(param2))), + immutable_uni(instance(string)) + ); + } + + #[test] + fn test_type_parameter_surrounding_trait() { + let mut db = Database::new(); + let string = ClassId::string(); + let to_foo = new_trait(&mut db, "ToFoo"); + let to_bar = new_trait(&mut db, "ToBar"); + let foo_param = to_foo.new_type_parameter(&mut db, "A".to_string()); + let bar_param = to_bar.new_type_parameter(&mut db, "B".to_string()); + + { + let ins = generic_trait_instance( + &mut db, + to_foo, + vec![owned(parameter(bar_param))], + ); + + // ToBar[B]: ToFoo[B] + to_bar.add_required_trait(&mut db, ins); + } + + let args = type_arguments(vec![(bar_param, owned(instance(string)))]); + let bounds = TypeBounds::new(); + let mut resolver = TypeResolver::new(&mut db, &args, &bounds); + + resolver.surrounding_trait = Some(to_bar); + + assert_eq!( + resolver.resolve(owned(parameter(foo_param))), + owned(instance(string)) + ); + } + + #[test] + fn test_generic_class() { + let mut db = Database::new(); + let array = ClassId::array(); + let string = ClassId::string(); + let param = new_parameter(&mut db, "A"); + let array_param = array.new_type_parameter(&mut db, "T".to_string()); + let args = type_arguments(vec![(param, owned(instance(string)))]); + let bounds = TypeBounds::new(); + let input = owned(generic_instance_id( + &mut db, + array, + vec![owned(parameter(param))], + )); + + let arg = match resolve(&mut db, &args, &bounds, input) { + TypeRef::Owned(TypeId::ClassInstance(ins)) => { + ins.type_arguments(&db).get(array_param).unwrap() + } + _ => TypeRef::Unknown, + }; + + assert_eq!(arg, owned(instance(string))); + } + + #[test] + fn test_generic_class_with_parameter_chain() { + let mut db = Database::new(); + let array = ClassId::array(); + let string = ClassId::string(); + let param1 = new_parameter(&mut db, "A"); + let param2 = new_parameter(&mut db, "B"); + let param3 = new_parameter(&mut db, "C"); + let array_param = array.new_type_parameter(&mut db, "T".to_string()); + let args = type_arguments(vec![ + (param1, owned(parameter(param2))), + (param2, owned(parameter(param3))), + (param3, owned(instance(string))), + ]); + let bounds = TypeBounds::new(); + let input = owned(generic_instance_id( + &mut db, + array, + vec![owned(parameter(param1))], + )); + + let arg = match resolve(&mut db, &args, &bounds, input) { + TypeRef::Owned(TypeId::ClassInstance(ins)) => { + ins.type_arguments(&db).get(array_param).unwrap() + } + _ => TypeRef::Unknown, + }; + + assert_eq!(arg, owned(instance(string))); + } + + #[test] + fn test_generic_trait() { + let mut db = Database::new(); + let to_foo = new_trait(&mut db, "ToFoo"); + let string = ClassId::string(); + let param = new_parameter(&mut db, "A"); + let trait_param = to_foo.new_type_parameter(&mut db, "T".to_string()); + let args = type_arguments(vec![(param, owned(instance(string)))]); + let bounds = TypeBounds::new(); + let input = owned(generic_trait_instance_id( + &mut db, + to_foo, + vec![owned(parameter(param))], + )); + + let arg = match resolve(&mut db, &args, &bounds, input) { + TypeRef::Owned(TypeId::TraitInstance(ins)) => { + ins.type_arguments(&db).get(trait_param).unwrap() + } + _ => TypeRef::Unknown, + }; + + assert_eq!(arg, owned(instance(string))); + } + + #[test] + fn test_closure() { + let mut db = Database::new(); + let fun = Closure::alloc(&mut db, false); + let param = new_parameter(&mut db, "T"); + + fun.set_return_type(&mut db, owned(parameter(param))); + fun.new_argument( + &mut db, + "a".to_string(), + owned(rigid(param)), + infer(parameter(param)), + ); + + let args = type_arguments(vec![(param, TypeRef::Any)]); + let bounds = TypeBounds::new(); + let output = match resolve(&mut db, &args, &bounds, owned(closure(fun))) + { + TypeRef::Owned(TypeId::Closure(id)) => id, + _ => panic!("Expected the resolved value to be a closure"), + }; + + assert_eq!(output.return_type(&db), TypeRef::Any); + assert_eq!(output.arguments(&db)[0].value_type, TypeRef::Any); + } + + #[test] + fn test_recursive() { + let mut db = Database::new(); + let param = new_parameter(&mut db, "A"); + let var = TypePlaceholder::alloc(&mut db, None); + + var.assign(&db, owned(parameter(param))); + + let args = type_arguments(vec![(param, placeholder(var))]); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(parameter(param))), + owned(parameter(param)) + ); + } + + #[test] + fn test_bounded_parameter() { + let mut db = Database::new(); + let param = new_parameter(&mut db, "A"); + let bound = new_parameter(&mut db, "A"); + + bound.set_original(&mut db, param); + + let args = type_arguments(vec![(param, TypeRef::Any)]); + let bounds = TypeBounds::new(); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(parameter(bound))), + TypeRef::Any, + ); + } + + #[test] + fn test_type_bounds() { + let mut db = Database::new(); + let param = new_parameter(&mut db, "A"); + let bound = new_parameter(&mut db, "A"); + + bound.set_original(&mut db, param); + + let args = TypeArguments::new(); + let bounds = type_bounds(vec![(param, bound)]); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(rigid(param))), + owned(rigid(bound)) + ); + + assert_eq!( + resolve(&mut db, &args, &bounds, owned(parameter(param))), + placeholder(TypePlaceholderId(0)) + ); + + assert_eq!(TypePlaceholderId(0).required(&db), Some(bound)); + } +} diff --git a/types/src/test.rs b/types/src/test.rs new file mode 100644 index 000000000..e532a3bae --- /dev/null +++ b/types/src/test.rs @@ -0,0 +1,165 @@ +use crate::{ + Class, ClassId, ClassInstance, ClassKind, ClosureId, Database, ModuleId, + Trait, TraitId, TraitImplementation, TraitInstance, TypeArguments, + TypeBounds, TypeId, TypeParameter, TypeParameterId, TypePlaceholderId, + TypeRef, Visibility, +}; + +pub(crate) fn new_class(db: &mut Database, name: &str) -> ClassId { + Class::alloc( + db, + name.to_string(), + ClassKind::Regular, + Visibility::Public, + ModuleId(0), + ) +} + +pub(crate) fn new_extern_class(db: &mut Database, name: &str) -> ClassId { + Class::alloc( + db, + name.to_string(), + ClassKind::Extern, + Visibility::Public, + ModuleId(0), + ) +} + +pub(crate) fn new_trait(db: &mut Database, name: &str) -> TraitId { + Trait::alloc(db, name.to_string(), ModuleId(0), Visibility::Public) +} + +pub(crate) fn new_parameter(db: &mut Database, name: &str) -> TypeParameterId { + TypeParameter::alloc(db, name.to_string()) +} + +pub(crate) fn implement( + db: &mut Database, + instance: TraitInstance, + class: ClassId, +) { + class.add_trait_implementation( + db, + TraitImplementation { instance, bounds: TypeBounds::new() }, + ); +} + +pub(crate) fn owned(id: TypeId) -> TypeRef { + TypeRef::Owned(id) +} + +pub(crate) fn uni(id: TypeId) -> TypeRef { + TypeRef::Uni(id) +} + +pub(crate) fn immutable_uni(id: TypeId) -> TypeRef { + TypeRef::RefUni(id) +} + +pub(crate) fn mutable_uni(id: TypeId) -> TypeRef { + TypeRef::MutUni(id) +} + +pub(crate) fn infer(id: TypeId) -> TypeRef { + TypeRef::Infer(id) +} + +pub(crate) fn immutable(id: TypeId) -> TypeRef { + TypeRef::Ref(id) +} + +pub(crate) fn mutable(id: TypeId) -> TypeRef { + TypeRef::Mut(id) +} + +pub(crate) fn placeholder(id: TypePlaceholderId) -> TypeRef { + TypeRef::Placeholder(id) +} + +pub(crate) fn instance(class: ClassId) -> TypeId { + TypeId::ClassInstance(ClassInstance::new(class)) +} + +pub(crate) fn parameter(id: TypeParameterId) -> TypeId { + TypeId::TypeParameter(id) +} + +pub(crate) fn rigid(id: TypeParameterId) -> TypeId { + TypeId::RigidTypeParameter(id) +} + +pub(crate) fn closure(id: ClosureId) -> TypeId { + TypeId::Closure(id) +} + +pub(crate) fn generic_instance_id( + db: &mut Database, + class: ClassId, + arguments: Vec, +) -> TypeId { + let mut args = TypeArguments::new(); + + for (param, arg) in + class.type_parameters(db).into_iter().zip(arguments.into_iter()) + { + args.assign(param, arg); + } + + TypeId::ClassInstance(ClassInstance::generic(db, class, args)) +} + +pub(crate) fn generic_trait_instance_id( + db: &mut Database, + trait_id: TraitId, + arguments: Vec, +) -> TypeId { + TypeId::TraitInstance(generic_trait_instance(db, trait_id, arguments)) +} + +pub(crate) fn generic_trait_instance( + db: &mut Database, + trait_id: TraitId, + arguments: Vec, +) -> TraitInstance { + let mut args = TypeArguments::new(); + + for (param, arg) in + trait_id.type_parameters(db).into_iter().zip(arguments.into_iter()) + { + args.assign(param, arg); + } + + TraitInstance::generic(db, trait_id, args) +} + +pub(crate) fn trait_instance(trait_id: TraitId) -> TraitInstance { + TraitInstance::new(trait_id) +} + +pub(crate) fn trait_instance_id(trait_id: TraitId) -> TypeId { + TypeId::TraitInstance(trait_instance(trait_id)) +} + +pub(crate) fn type_arguments( + pairs: Vec<(TypeParameterId, TypeRef)>, +) -> TypeArguments { + let mut args = TypeArguments::new(); + + for (param, typ) in pairs { + args.assign(param, typ); + } + + args +} + +pub(crate) fn type_bounds( + pairs: Vec<(TypeParameterId, TypeParameterId)>, +) -> TypeBounds { + let mut bounds = TypeBounds::new(); + + for (param, bound) in pairs { + bounds.set(param, bound); + } + + bounds +} diff --git a/vm/Cargo.toml b/vm/Cargo.toml deleted file mode 100644 index 82dc4f1fc..000000000 --- a/vm/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "vm" -version = "0.10.0" # VERSION -authors = ["Yorick Peterse "] -edition = "2021" -build = "build.rs" -license = "MPL-2.0" - -[lib] -doctest = false - -[features] -libffi-system = ["libffi/system"] - -[dependencies] -libloading = "^0.7" -libffi = "^3.0" -crossbeam-utils = "^0.8" -crossbeam-queue = "^0.3" -libc = "^0.2" -ahash = "^0.8" -rand = { version = "^0.8", features = ["default", "small_rng"] } -polling = "^2.0" -unicode-segmentation = "^1.8" -bytecode = { path = "../bytecode" } - -[dependencies.socket2] -version = "^0.4" -features = ["all"] - -[target.'cfg(windows)'.dependencies.windows-sys] -version = "^0.36" -features = [ - "Win32_Foundation", - "Win32_Networking_WinSock", - "Win32_System_Time", - "Win32_System_SystemInformation", - "Win32_System_SystemServices", -] diff --git a/vm/src/builtin_functions.rs b/vm/src/builtin_functions.rs deleted file mode 100644 index a7d03e3c9..000000000 --- a/vm/src/builtin_functions.rs +++ /dev/null @@ -1,216 +0,0 @@ -//! A registry of builtin functions that can be called in Inko source code. -use crate::mem::Pointer; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::io::Read; - -mod array; -mod byte_array; -mod env; -mod ffi; -mod float; -mod fs; -mod hasher; -mod process; -mod random; -mod socket; -mod stdio; -mod string; -mod sys; -mod time; - -/// A builtin function that can be called from Inko source code. -pub(crate) type BuiltinFunction = fn( - &State, - &mut Thread, - ProcessPointer, - &[Pointer], -) -> Result; - -/// Reads a number of bytes from a buffer into a Vec. -pub(crate) fn read_into( - stream: &mut T, - output: &mut Vec, - size: i64, -) -> Result { - let read = if size > 0 { - stream.take(size as u64).read_to_end(output)? - } else { - stream.read_to_end(output)? - }; - - Ok(read as i64) -} - -/// A collection of builtin functions. -pub(crate) struct BuiltinFunctions { - functions: Vec, -} - -impl BuiltinFunctions { - /// Creates a collection of builtin functions and registers all functions - /// that Inko ships with. - pub(crate) fn new() -> Self { - Self { - functions: vec![ - byte_array::byte_array_drain_to_string, - byte_array::byte_array_to_string, - sys::child_process_drop, - sys::child_process_spawn, - sys::child_process_stderr_close, - sys::child_process_stderr_read, - sys::child_process_stdin_close, - sys::child_process_stdin_flush, - sys::child_process_stdin_write_bytes, - sys::child_process_stdin_write_string, - sys::child_process_stdout_close, - sys::child_process_stdout_read, - sys::child_process_try_wait, - sys::child_process_wait, - env::env_arguments, - env::env_executable, - env::env_get, - env::env_get_working_directory, - env::env_home_directory, - env::env_platform, - env::env_set_working_directory, - env::env_temp_directory, - env::env_variables, - ffi::ffi_function_attach, - ffi::ffi_function_call, - ffi::ffi_function_drop, - ffi::ffi_library_drop, - ffi::ffi_library_open, - ffi::ffi_pointer_address, - ffi::ffi_pointer_attach, - ffi::ffi_pointer_from_address, - ffi::ffi_pointer_read, - ffi::ffi_pointer_write, - ffi::ffi_type_alignment, - ffi::ffi_type_size, - fs::directory_create, - fs::directory_create_recursive, - fs::directory_list, - fs::directory_remove, - fs::directory_remove_recursive, - fs::file_copy, - fs::file_drop, - fs::file_flush, - fs::file_open_append_only, - fs::file_open_read_append, - fs::file_open_read_only, - fs::file_open_read_write, - fs::file_open_write_only, - fs::file_read, - fs::file_remove, - fs::file_seek, - fs::file_size, - fs::file_write_bytes, - fs::file_write_string, - fs::path_accessed_at, - fs::path_created_at, - fs::path_exists, - fs::path_is_directory, - fs::path_is_file, - fs::path_modified_at, - hasher::hasher_drop, - hasher::hasher_new, - hasher::hasher_to_hash, - hasher::hasher_write_int, - process::process_stacktrace_drop, - process::process_call_frame_line, - process::process_call_frame_name, - process::process_call_frame_path, - process::process_stacktrace, - random::random_bytes, - random::random_float, - random::random_float_range, - random::random_int, - random::random_int_range, - socket::socket_accept_ip, - socket::socket_accept_unix, - socket::socket_address_pair_address, - socket::socket_address_pair_drop, - socket::socket_address_pair_port, - socket::socket_allocate_ipv4, - socket::socket_allocate_ipv6, - socket::socket_allocate_unix, - socket::socket_bind, - socket::socket_connect, - socket::socket_drop, - socket::socket_get_broadcast, - socket::socket_get_keepalive, - socket::socket_get_linger, - socket::socket_get_nodelay, - socket::socket_get_only_v6, - socket::socket_get_recv_size, - socket::socket_get_reuse_address, - socket::socket_get_reuse_port, - socket::socket_get_send_size, - socket::socket_get_ttl, - socket::socket_listen, - socket::socket_local_address, - socket::socket_peer_address, - socket::socket_read, - socket::socket_receive_from, - socket::socket_send_bytes_to, - socket::socket_send_string_to, - socket::socket_set_broadcast, - socket::socket_set_keepalive, - socket::socket_set_linger, - socket::socket_set_nodelay, - socket::socket_set_only_v6, - socket::socket_set_recv_size, - socket::socket_set_reuse_address, - socket::socket_set_reuse_port, - socket::socket_set_send_size, - socket::socket_set_ttl, - socket::socket_shutdown_read, - socket::socket_shutdown_read_write, - socket::socket_shutdown_write, - socket::socket_try_clone, - socket::socket_write_bytes, - socket::socket_write_string, - stdio::stderr_flush, - stdio::stderr_write_bytes, - stdio::stderr_write_string, - stdio::stdin_read, - stdio::stdout_flush, - stdio::stdout_write_bytes, - stdio::stdout_write_string, - string::string_to_byte_array, - string::string_to_float, - string::string_to_int, - string::string_to_lower, - string::string_to_upper, - time::time_monotonic, - time::time_system, - time::time_system_offset, - sys::cpu_cores, - string::string_characters, - string::string_characters_next, - string::string_characters_drop, - string::string_concat_array, - array::array_reserve, - array::array_capacity, - process::process_stacktrace_length, - float::float_to_bits, - float::float_from_bits, - random::random_new, - random::random_from_int, - random::random_drop, - string::string_slice_bytes, - byte_array::byte_array_slice, - byte_array::byte_array_append, - byte_array::byte_array_copy_from, - byte_array::byte_array_resize, - ], - } - } - - pub(crate) fn get(&self, index: u16) -> BuiltinFunction { - self.functions[index as usize] - } -} diff --git a/vm/src/builtin_functions/array.rs b/vm/src/builtin_functions/array.rs deleted file mode 100644 index e84cd8a70..000000000 --- a/vm/src/builtin_functions/array.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::mem::{Array, Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; - -pub(crate) fn array_reserve( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let array = unsafe { args[0].get_mut::() }; - let size = unsafe { Int::read(args[1]) }; - - array.value_mut().reserve(size as usize); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn array_capacity( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let array = unsafe { args[0].get::() }; - let cap = array.value().capacity() as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), cap)) -} diff --git a/vm/src/builtin_functions/byte_array.rs b/vm/src/builtin_functions/byte_array.rs deleted file mode 100644 index a3fd6138c..000000000 --- a/vm/src/builtin_functions/byte_array.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::immutable_string::ImmutableString; -use crate::mem::{ByteArray, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::cmp::min; - -pub(crate) fn byte_array_to_string( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bytes_ref = unsafe { args[0].get_mut::() }; - let string = ImmutableString::from_utf8(bytes_ref.value().clone()); - let res = InkoString::from_immutable_string( - state.permanent_space.string_class(), - string, - ); - - Ok(res) -} - -pub(crate) fn byte_array_drain_to_string( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bytes_ref = unsafe { args[0].get_mut::() }; - let string = ImmutableString::from_utf8(bytes_ref.take_bytes()); - let res = InkoString::from_immutable_string( - state.permanent_space.string_class(), - string, - ); - - Ok(res) -} - -pub(crate) fn byte_array_slice( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bytes = unsafe { args[0].get::() }; - let start = unsafe { Int::read(args[1]) } as usize; - let len = unsafe { Int::read(args[2]) } as usize; - let end = min((start + len) as usize, bytes.value().len()); - - Ok(ByteArray::alloc( - state.permanent_space.byte_array_class(), - bytes.value()[start..end].to_vec(), - )) -} - -pub(crate) fn byte_array_append( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let target = unsafe { args[0].get_mut::() }; - let source = unsafe { args[1].get_mut::() }; - - target.value_mut().append(source.value_mut()); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn byte_array_copy_from( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let target = unsafe { args[0].get_mut::() }; - let source = unsafe { args[1].get_mut::() }; - let start = unsafe { Int::read(args[2]) } as usize; - let len = unsafe { Int::read(args[3]) } as usize; - let end = min((start + len) as usize, source.value().len()); - let slice = &source.value()[start..end]; - let amount = slice.len() as i64; - - target.value_mut().extend_from_slice(slice); - Ok(Int::alloc(state.permanent_space.int_class(), amount)) -} - -pub(crate) fn byte_array_resize( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bytes = unsafe { args[0].get_mut::() }; - let size = unsafe { Int::read(args[1]) as usize }; - let filler = unsafe { Int::read(args[2]) as u8 }; - - bytes.value_mut().resize(size, filler); - Ok(Pointer::nil_singleton()) -} diff --git a/vm/src/builtin_functions/env.rs b/vm/src/builtin_functions/env.rs deleted file mode 100644 index 940e8ab99..000000000 --- a/vm/src/builtin_functions/env.rs +++ /dev/null @@ -1,142 +0,0 @@ -//! Functions for setting/getting environment and operating system data. -use crate::mem::{Array, Pointer, String as InkoString}; -use crate::platform; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::env; -use std::path::PathBuf; - -pub(crate) fn env_get( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let var_name = unsafe { InkoString::read(&arguments[0]) }; - let result = state - .environment - .get(var_name) - .cloned() - .unwrap_or_else(Pointer::undefined_singleton); - - Ok(result) -} - -pub(crate) fn env_variables( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let names = state - .environment - .keys() - .map(|key| { - InkoString::alloc(state.permanent_space.string_class(), key.clone()) - }) - .collect(); - - Ok(Array::alloc(state.permanent_space.array_class(), names)) -} - -pub(crate) fn env_home_directory( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - // Rather than performing all sorts of magical incantations to get the home - // directory, we're just going to require that these environment variables - // are set. - let var = if cfg!(windows) { - state.environment.get("USERPROFILE") - } else { - state.environment.get("HOME") - }; - - // If the home is explicitly set to an empty string we still ignore it, - // because there's no scenario in which Some("") is useful. - let result = var - .cloned() - .filter(|p| unsafe { !InkoString::read(p).is_empty() }) - .unwrap_or_else(Pointer::undefined_singleton); - - Ok(result) -} - -pub(crate) fn env_temp_directory( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let path = canonalize(env::temp_dir().to_string_lossy().into_owned()); - - Ok(InkoString::alloc(state.permanent_space.string_class(), path)) -} - -pub(crate) fn env_get_working_directory( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let path = env::current_dir() - .map(|path| path.to_string_lossy().into_owned()) - .map(canonalize)?; - - Ok(InkoString::alloc(state.permanent_space.string_class(), path)) -} - -pub(crate) fn env_set_working_directory( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let dir = unsafe { InkoString::read(&arguments[0]) }; - - env::set_current_dir(dir)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn env_arguments( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - Ok(Array::alloc( - state.permanent_space.array_class(), - state.arguments.clone(), - )) -} - -pub(crate) fn env_platform( - _: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - Ok(Pointer::int(platform::operating_system())) -} - -pub(crate) fn env_executable( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let path = env::current_exe()?.to_string_lossy().into_owned(); - - Ok(InkoString::alloc(state.permanent_space.string_class(), path)) -} - -fn canonalize(path: String) -> String { - PathBuf::from(&path) - .canonicalize() - .map(|p| p.to_string_lossy().into_owned()) - .unwrap_or(path) -} diff --git a/vm/src/builtin_functions/ffi.rs b/vm/src/builtin_functions/ffi.rs deleted file mode 100644 index 3566ce37f..000000000 --- a/vm/src/builtin_functions/ffi.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! Functions for interacting with C code from Inko. -use crate::ffi::{ - type_alignment, type_size, Function, Library, Pointer as ForeignPointer, -}; -use crate::mem::{Array, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; - -pub(crate) fn ffi_library_open( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let names = unsafe { arguments[0].get::() }.value(); - let result = Library::from_pointers(names) - .map(Pointer::boxed) - .unwrap_or_else(Pointer::undefined_singleton); - - Ok(result) -} - -pub(crate) fn ffi_function_attach( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let func = unsafe { - let lib = arguments[0].get::(); - let name = InkoString::read(&arguments[1]); - let args = arguments[2].get::().value(); - let rtype = arguments[3]; - - Function::attach(lib, name, args, rtype)? - }; - - Ok(func.map(Pointer::boxed).unwrap_or_else(Pointer::undefined_singleton)) -} - -pub(crate) fn ffi_function_call( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let func = unsafe { arguments[0].get::() }; - let args = &arguments[1..]; - - Ok(unsafe { func.call(state, args)? }) -} - -pub(crate) fn ffi_pointer_attach( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let lib = unsafe { arguments[0].get::() }; - let name = unsafe { InkoString::read(&arguments[1]) }; - let raw_ptr = unsafe { - lib.get(name) - .map(|ptr| Pointer::new(ptr.as_ptr())) - .unwrap_or_else(Pointer::undefined_singleton) - }; - - Ok(raw_ptr) -} - -pub(crate) fn ffi_pointer_read( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let ptr = ForeignPointer::new(arguments[0].as_ptr() as _); - let kind = arguments[1]; - let offset = unsafe { Int::read(arguments[2]) as usize }; - let result = unsafe { ptr.with_offset(offset).read_as(state, kind)? }; - - Ok(result) -} - -pub(crate) fn ffi_pointer_write( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let ptr = ForeignPointer::new(arguments[0].as_ptr() as _); - let kind = arguments[1]; - let offset = unsafe { Int::read(arguments[2]) as usize }; - let value = arguments[3]; - - unsafe { - ptr.with_offset(offset).write_as(kind, value)?; - } - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn ffi_pointer_from_address( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let addr = unsafe { Int::read(arguments[0]) }; - - Ok(Pointer::new(addr as _)) -} - -pub(crate) fn ffi_pointer_address( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let addr = arguments[0].as_ptr() as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), addr)) -} - -pub(crate) fn ffi_type_size( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let kind = unsafe { Int::read(arguments[0]) }; - - type_size(kind).map_err(|e| e.into()) -} - -pub(crate) fn ffi_type_alignment( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let kind = unsafe { Int::read(arguments[0]) }; - - type_alignment(kind).map_err(|e| e.into()) -} - -pub(crate) fn ffi_library_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { - arguments[0].drop_boxed::(); - } - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn ffi_function_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { - arguments[0].drop_boxed::(); - } - - Ok(Pointer::nil_singleton()) -} diff --git a/vm/src/builtin_functions/float.rs b/vm/src/builtin_functions/float.rs deleted file mode 100644 index afc38a70a..000000000 --- a/vm/src/builtin_functions/float.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::mem::{Float, Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; - -pub(crate) fn float_to_bits( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bits = unsafe { Float::read(args[0]) }.to_bits() as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), bits)) -} - -pub(crate) fn float_from_bits( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bits = unsafe { Int::read(args[0]) } as u64; - - Ok(Float::alloc(state.permanent_space.float_class(), f64::from_bits(bits))) -} diff --git a/vm/src/builtin_functions/fs.rs b/vm/src/builtin_functions/fs.rs deleted file mode 100644 index 16e9c30e2..000000000 --- a/vm/src/builtin_functions/fs.rs +++ /dev/null @@ -1,379 +0,0 @@ -//! Functions for working with the file system. -//! -//! Files aren't allocated onto the Inko heap. Instead, we allocate them using -//! Rust's allocator and convert them into an Inko pointer. Dropping a file -//! involves turning that pointer back into a File, then dropping the Rust -//! object. -//! -//! This approach means the VM doesn't need to know anything about what objects -//! to use for certain files, how to store file paths, etc; instead we can keep -//! all that in the standard library. -use crate::builtin_functions::read_into; -use crate::mem::{Array, ByteArray, Float, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::fs::{self, File, OpenOptions}; -use std::io::{Seek, SeekFrom, Write}; -use std::time::{SystemTime, UNIX_EPOCH}; - -pub(crate) fn file_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { - arguments[0].drop_boxed::(); - } - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn file_seek( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let file = unsafe { arguments[0].get_mut::() }; - let offset = unsafe { Int::read(arguments[1]) }; - let seek = if offset < 0 { - SeekFrom::End(offset) - } else { - SeekFrom::Start(offset as u64) - }; - - let result = thread.blocking(|| file.seek(seek))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), result)) -} - -pub(crate) fn file_flush( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let file = unsafe { arguments[0].get_mut::() }; - - thread.blocking(|| file.flush())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn file_write_string( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let file = unsafe { arguments[0].get_mut::() }; - let input = unsafe { InkoString::read(&arguments[1]).as_bytes() }; - let written = thread.blocking(|| file.write(input))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), written)) -} - -pub(crate) fn file_write_bytes( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let file = unsafe { arguments[0].get_mut::() }; - let input = unsafe { arguments[1].get::() }; - let written = thread.blocking(|| file.write(input.value()))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), written)) -} - -pub(crate) fn file_copy( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let src = unsafe { InkoString::read(&arguments[0]) }; - let dst = unsafe { InkoString::read(&arguments[1]) }; - let copied = thread.blocking(|| fs::copy(src, dst))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), copied)) -} - -pub(crate) fn file_size( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let size = thread.blocking(|| fs::metadata(path))?.len() as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), size)) -} - -pub(crate) fn file_remove( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - - thread.blocking(|| fs::remove_file(path))?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn path_created_at( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path))?; - let time = system_time_to_timestamp(meta.created()?); - - Ok(Float::alloc(state.permanent_space.float_class(), time)) -} - -pub(crate) fn path_modified_at( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path))?; - let time = system_time_to_timestamp(meta.modified()?); - - Ok(Float::alloc(state.permanent_space.float_class(), time)) -} - -pub(crate) fn path_accessed_at( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path))?; - let time = system_time_to_timestamp(meta.accessed()?); - - Ok(Float::alloc(state.permanent_space.float_class(), time)) -} - -pub(crate) fn path_is_file( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path)); - - if meta.map(|m| m.is_file()).unwrap_or(false) { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn path_is_directory( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path)); - - if meta.map(|m| m.is_dir()).unwrap_or(false) { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn path_exists( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let meta = thread.blocking(|| fs::metadata(path)); - - if meta.is_ok() { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn file_open_read_only( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let mut opts = OpenOptions::new(); - - opts.read(true); - open_file(thread, opts, arguments[0]) -} - -pub(crate) fn file_open_write_only( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let mut opts = OpenOptions::new(); - - opts.write(true).truncate(true).create(true); - open_file(thread, opts, arguments[0]) -} - -pub(crate) fn file_open_append_only( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let mut opts = OpenOptions::new(); - - opts.append(true).create(true); - open_file(thread, opts, arguments[0]) -} - -pub(crate) fn file_open_read_write( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let mut opts = OpenOptions::new(); - - opts.read(true).write(true).create(true); - open_file(thread, opts, arguments[0]) -} - -pub(crate) fn file_open_read_append( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let mut opts = OpenOptions::new(); - - opts.read(true).append(true).create(true); - open_file(thread, opts, arguments[0]) -} - -pub(crate) fn file_read( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let file = unsafe { arguments[0].get_mut::() }; - let buff = unsafe { arguments[1].get_mut::() }; - let size = unsafe { Int::read(arguments[2]) } as i64; - let read = thread.blocking(|| read_into(file, buff.value_mut(), size))?; - - Ok(Int::alloc(state.permanent_space.int_class(), read)) -} - -pub(crate) fn directory_create( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - - thread.blocking(|| fs::create_dir(path))?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn directory_create_recursive( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - - thread.blocking(|| fs::create_dir_all(path))?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn directory_remove( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - - thread.blocking(|| fs::remove_dir(path))?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn directory_remove_recursive( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - - thread.blocking(|| fs::remove_dir_all(path))?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn directory_list( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let path = unsafe { InkoString::read(&arguments[0]) }; - let mut paths = Vec::new(); - - for entry in thread.blocking(|| fs::read_dir(path))? { - let entry = entry?; - let path = entry.path().to_string_lossy().to_string(); - let pointer = - InkoString::alloc(state.permanent_space.string_class(), path); - - paths.push(pointer); - } - - Ok(Array::alloc(state.permanent_space.array_class(), paths)) -} - -fn open_file( - thread: &mut Thread, - options: OpenOptions, - path_ptr: Pointer, -) -> Result { - let path = unsafe { InkoString::read(&path_ptr) }; - - thread - .blocking(|| options.open(path)) - .map(Pointer::boxed) - .map_err(RuntimeError::from) -} - -fn system_time_to_timestamp(time: SystemTime) -> f64 { - let duration = if time < UNIX_EPOCH { - UNIX_EPOCH.duration_since(time) - } else { - time.duration_since(UNIX_EPOCH) - }; - - duration.unwrap().as_secs_f64() -} diff --git a/vm/src/builtin_functions/hasher.rs b/vm/src/builtin_functions/hasher.rs deleted file mode 100644 index 1fd2113a3..000000000 --- a/vm/src/builtin_functions/hasher.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Functions for hashing objects. -use crate::hasher::Hasher; -use crate::mem::{Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::hash::BuildHasher as _; - -pub(crate) fn hasher_new( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let hasher = state.hash_state.build_hasher(); - - Ok(Pointer::boxed(Hasher::new(hasher))) -} - -pub(crate) fn hasher_write_int( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let hasher = unsafe { arguments[0].get_mut::() }; - let value = unsafe { Int::read(arguments[1]) }; - - hasher.write_int(value); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn hasher_to_hash( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let hasher = unsafe { arguments[0].get_mut::() }; - let value = hasher.finish(); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -pub(crate) fn hasher_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { - arguments[0].drop_boxed::(); - } - - Ok(Pointer::nil_singleton()) -} diff --git a/vm/src/builtin_functions/process.rs b/vm/src/builtin_functions/process.rs deleted file mode 100644 index c38929666..000000000 --- a/vm/src/builtin_functions/process.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! Functions for Inko processes. -use crate::location_table::Location; -use crate::mem::{Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; - -pub(crate) fn process_stacktrace( - _: &State, - _: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let skip = unsafe { Int::read(arguments[0]) as usize }; - let raw_trace = process.stacktrace(); - let len = raw_trace.len(); - let limit = if skip > 0 { len.saturating_sub(skip) } else { len }; - let trace: Vec = raw_trace.into_iter().take(limit).collect(); - - Ok(Pointer::boxed(trace)) -} - -pub(crate) fn process_call_frame_name( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let trace = unsafe { arguments[0].get::>() }; - let index = unsafe { Int::read(arguments[1]) as usize }; - - Ok(trace[index].name) -} - -pub(crate) fn process_call_frame_path( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let trace = unsafe { arguments[0].get::>() }; - let index = unsafe { Int::read(arguments[1]) as usize }; - - Ok(trace[index].file) -} - -pub(crate) fn process_call_frame_line( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let trace = unsafe { arguments[0].get::>() }; - let index = unsafe { Int::read(arguments[1]) as usize }; - - Ok(trace[index].line) -} - -pub(crate) fn process_stacktrace_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { arguments[0].drop_boxed::>() } - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn process_stacktrace_length( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let trace = unsafe { arguments[0].get::>() }.len(); - - Ok(Int::alloc(state.permanent_space.int_class(), trace as i64)) -} diff --git a/vm/src/builtin_functions/random.rs b/vm/src/builtin_functions/random.rs deleted file mode 100644 index 0559b4b46..000000000 --- a/vm/src/builtin_functions/random.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Functions for generating random numbers. -use crate::mem::{ByteArray, Float, Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use rand::rngs::StdRng; -use rand::{Rng, SeedableRng}; - -pub(crate) fn random_int( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let rng = unsafe { arguments[0].get_mut::() }; - - Ok(Int::alloc(state.permanent_space.int_class(), rng.gen())) -} - -pub(crate) fn random_float( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let rng = unsafe { arguments[0].get_mut::() }; - - Ok(Float::alloc(state.permanent_space.float_class(), rng.gen())) -} - -pub(crate) fn random_int_range( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let rng = unsafe { arguments[0].get_mut::() }; - let min = unsafe { Int::read(arguments[1]) }; - let max = unsafe { Int::read(arguments[2]) }; - let val = if min < max { rng.gen_range(min..max) } else { 0 }; - - Ok(Int::alloc(state.permanent_space.int_class(), val)) -} - -pub(crate) fn random_float_range( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let rng = unsafe { arguments[0].get_mut::() }; - let min = unsafe { Float::read(arguments[1]) }; - let max = unsafe { Float::read(arguments[2]) }; - let val = if min < max { rng.gen_range(min..max) } else { 0.0 }; - - Ok(Float::alloc(state.permanent_space.float_class(), val)) -} - -pub(crate) fn random_bytes( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let rng = unsafe { arguments[0].get_mut::() }; - let size = unsafe { Int::read(arguments[1]) } as usize; - let mut bytes = vec![0; size]; - - rng.try_fill(&mut bytes[..]).map_err(|e| e.to_string())?; - Ok(ByteArray::alloc(state.permanent_space.byte_array_class(), bytes)) -} - -pub(crate) fn random_new( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let mut seed: ::Seed = Default::default(); - - thread.rng.fill(&mut seed); - - let rng = Pointer::boxed(StdRng::from_seed(seed)); - - Ok(rng) -} - -pub(crate) fn random_from_int( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let seed = unsafe { Int::read(args[0]) } as u64; - let rng = Pointer::boxed(StdRng::seed_from_u64(seed)); - - Ok(rng) -} - -pub(crate) fn random_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - unsafe { args[0].drop_boxed::() }; - Ok(Pointer::nil_singleton()) -} diff --git a/vm/src/builtin_functions/socket.rs b/vm/src/builtin_functions/socket.rs deleted file mode 100644 index 7ee449f5d..000000000 --- a/vm/src/builtin_functions/socket.rs +++ /dev/null @@ -1,670 +0,0 @@ -//! Functions for working with non-blocking sockets. -use crate::mem::{ByteArray, Int, Pointer, String as InkoString}; -use crate::network_poller::Interest; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::scheduler::timeouts::Timeout; -use crate::socket::Socket; -use crate::state::State; -use std::io::Write; - -fn ret( - result: Result, - state: &State, - thread: &Thread, - process: ProcessPointer, - socket: &mut Socket, - interest: Interest, - deadline: Pointer, -) -> Result { - if !matches!(result, Err(ref err) if err.should_poll()) { - return result; - } - - // We must keep the process' state lock open until everything is registered, - // otherwise a timeout thread may reschedule the process (i.e. the timeout - // is very short) before we finish registering the socket with a poller. - let mut proc_state = process.state(); - let nanos = unsafe { Int::read(deadline) }; - - // A deadline of -1 signals that we should wait indefinitely. - if nanos >= 0 { - let time = Timeout::from_nanos_deadline(state, nanos as u64); - - proc_state.waiting_for_io(Some(time.clone())); - state.timeout_worker.suspend(process, time); - } else { - proc_state.waiting_for_io(None); - } - - socket.register(state, process, thread.network_poller, interest)?; - result -} - -fn handle_timeout( - state: &State, - socket: &mut Socket, - process: ProcessPointer, -) -> Result<(), RuntimeError> { - if process.timeout_expired() { - // The socket is still registered at this point, so we have to - // deregister first. If we don't and suspend for another IO operation, - // the poller could end up rescheduling the process multiple times (as - // there are multiple events still in flight for the process). - socket.deregister(state); - - return Err(RuntimeError::timed_out()); - } - - Ok(()) -} - -pub(crate) fn socket_allocate_ipv4( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let kind = unsafe { Int::read(arguments[0]) }; - let socket = Socket::ipv4(kind)?; - - Ok(Pointer::boxed(socket)) -} - -pub(crate) fn socket_allocate_ipv6( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let kind = unsafe { Int::read(arguments[0]) }; - let socket = Socket::ipv6(kind)?; - - Ok(Pointer::boxed(socket)) -} - -pub(crate) fn socket_allocate_unix( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let kind = unsafe { Int::read(arguments[0]) }; - let socket = Socket::unix(kind)?; - - Ok(Pointer::boxed(socket)) -} - -pub(crate) fn socket_write_string( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let input = unsafe { InkoString::read(&arguments[1]).as_bytes() }; - let deadline = arguments[2]; - let res = sock - .write(input) - .map(|size| Int::alloc(state.permanent_space.int_class(), size as i64)) - .map_err(RuntimeError::from); - - ret(res, state, thread, process, sock, Interest::Write, deadline) -} - -pub(crate) fn socket_write_bytes( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let input = unsafe { arguments[1].get::() }.value(); - let deadline = arguments[2]; - let res = sock - .write(input) - .map(|size| Int::alloc(state.permanent_space.int_class(), size as i64)) - .map_err(RuntimeError::from); - - ret(res, state, thread, process, sock, Interest::Write, deadline) -} - -pub(crate) fn socket_read( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let buffer = unsafe { arguments[1].get_mut::() }.value_mut(); - let amount = unsafe { Int::read(arguments[2]) } as usize; - let deadline = arguments[3]; - let result = sock - .read(buffer, amount) - .map(|size| Int::alloc(state.permanent_space.int_class(), size as i64)); - - ret(result, state, thread, process, sock, Interest::Read, deadline) -} - -pub(crate) fn socket_listen( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let backlog = unsafe { Int::read(arguments[1]) } as i32; - - sock.listen(backlog)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_bind( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let addr = unsafe { InkoString::read(&arguments[1]) }; - let port = unsafe { Int::read(arguments[2]) } as u16; - - // POSX states that bind(2) _can_ produce EINPROGRESS, but in practise it - // seems no system out there actually does this. - sock.bind(addr, port).map(|_| Pointer::nil_singleton()) -} - -pub(crate) fn socket_connect( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let addr = unsafe { InkoString::read(&arguments[1]) }; - let port = unsafe { Int::read(arguments[2]) } as u16; - let deadline = arguments[3]; - let result = sock.connect(addr, port).map(|_| Pointer::nil_singleton()); - - ret(result, state, thread, process, sock, Interest::Write, deadline) -} - -pub(crate) fn socket_accept_ip( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let deadline = arguments[1]; - - handle_timeout(state, sock, process)?; - - let result = sock.accept().map(Pointer::boxed); - - ret(result, state, thread, process, sock, Interest::Read, deadline) -} - -pub(crate) fn socket_accept_unix( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let deadline = arguments[1]; - - handle_timeout(state, sock, process)?; - - let result = sock.accept().map(Pointer::boxed); - - ret(result, state, thread, process, sock, Interest::Read, deadline) -} - -pub(crate) fn socket_receive_from( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let buffer = unsafe { arguments[1].get_mut::() }.value_mut(); - let amount = unsafe { Int::read(arguments[2]) } as usize; - let deadline = arguments[3]; - let result = sock - .recv_from(buffer, amount) - .map(|(addr, port)| allocate_address_pair(state, addr, port)); - - ret(result, state, thread, process, sock, Interest::Read, deadline) -} - -pub(crate) fn socket_send_bytes_to( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let buffer = unsafe { arguments[1].get::() }.value(); - let address = unsafe { InkoString::read(&arguments[2]) }; - let port = unsafe { Int::read(arguments[3]) } as u16; - let deadline = arguments[4]; - let result = sock - .send_to(buffer, address, port) - .map(|size| Int::alloc(state.permanent_space.int_class(), size as i64)); - - ret(result, state, thread, process, sock, Interest::Write, deadline) -} - -pub(crate) fn socket_send_string_to( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - handle_timeout(state, sock, process)?; - - let buffer = unsafe { InkoString::read(&arguments[1]).as_bytes() }; - let address = unsafe { InkoString::read(&arguments[2]) }; - let port = unsafe { Int::read(arguments[3]) } as u16; - let deadline = arguments[4]; - let result = sock - .send_to(buffer, address, port) - .map(|size| Int::alloc(state.permanent_space.int_class(), size as i64)); - - ret(result, state, thread, process, sock, Interest::Write, deadline) -} - -pub(crate) fn socket_shutdown_read( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.shutdown_read().map(|_| Pointer::nil_singleton()) -} - -pub(crate) fn socket_shutdown_write( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.shutdown_write().map(|_| Pointer::nil_singleton()) -} - -pub(crate) fn socket_shutdown_read_write( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.shutdown_read_write().map(|_| Pointer::nil_singleton()) -} - -pub(crate) fn socket_local_address( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get::() }; - - sock.local_address() - .map(|(addr, port)| allocate_address_pair(state, addr, port)) -} - -pub(crate) fn socket_peer_address( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get::() }; - - sock.peer_address() - .map(|(addr, port)| allocate_address_pair(state, addr, port)) -} - -pub(crate) fn socket_get_ttl( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let value = unsafe { arguments[0].get::() }.ttl()? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -pub(crate) fn socket_get_only_v6( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - if unsafe { arguments[0].get::() }.only_v6()? { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_get_nodelay( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - if unsafe { arguments[0].get_mut::() }.nodelay()? { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_get_broadcast( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - if unsafe { arguments[0].get::() }.broadcast()? { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_get_linger( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let value = unsafe { arguments[0].get::() }.linger()?; - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -pub(crate) fn socket_get_recv_size( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - Ok(Int::alloc( - state.permanent_space.int_class(), - unsafe { arguments[0].get::() }.recv_buffer_size()? as i64, - )) -} - -pub(crate) fn socket_get_send_size( - state: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - Ok(Int::alloc( - state.permanent_space.int_class(), - unsafe { arguments[0].get::() }.send_buffer_size()? as i64, - )) -} - -pub(crate) fn socket_get_keepalive( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let value = unsafe { arguments[0].get::() }.keepalive()?; - - if value { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_get_reuse_address( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - if unsafe { arguments[0].get::() }.reuse_address()? { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_get_reuse_port( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - if unsafe { arguments[0].get::() }.reuse_port()? { - Ok(Pointer::true_singleton()) - } else { - Ok(Pointer::false_singleton()) - } -} - -pub(crate) fn socket_set_ttl( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = unsafe { Int::read(arguments[1]) } as u32; - - sock.set_ttl(value)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_only_v6( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.set_only_v6(arguments[1] == Pointer::true_singleton())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_nodelay( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.set_nodelay(arguments[1] == Pointer::true_singleton())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_broadcast( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - - sock.set_broadcast(arguments[1] == Pointer::true_singleton())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_linger( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = unsafe { Int::read_u64(arguments[1]) }; - - sock.set_linger(value)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_recv_size( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = unsafe { Int::read(arguments[1]) } as usize; - - sock.set_recv_buffer_size(value)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_send_size( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = unsafe { Int::read(arguments[1]) } as usize; - - sock.set_send_buffer_size(value)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_keepalive( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = arguments[1] == Pointer::true_singleton(); - - sock.set_keepalive(value)?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_reuse_address( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = arguments[1]; - - sock.set_reuse_address(value == Pointer::true_singleton())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_set_reuse_port( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get_mut::() }; - let value = arguments[1]; - - sock.set_reuse_port(value == Pointer::true_singleton())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_try_clone( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let sock = unsafe { arguments[0].get::() }; - let clone = sock.try_clone()?; - - Ok(Pointer::boxed(clone)) -} - -pub(crate) fn socket_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { arguments[0].drop_boxed::() }; - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn socket_address_pair_address( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let pair = unsafe { arguments[0].get::<(Pointer, Pointer)>() }; - - Ok(pair.0) -} - -pub(crate) fn socket_address_pair_port( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let pair = unsafe { arguments[0].get::<(Pointer, Pointer)>() }; - - Ok(pair.1) -} - -pub(crate) fn socket_address_pair_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { Pointer::drop_boxed::<(Pointer, Pointer)>(arguments[0]) }; - - Ok(Pointer::nil_singleton()) -} - -fn allocate_address_pair(state: &State, addr: String, port: i64) -> Pointer { - let addr = InkoString::alloc(state.permanent_space.string_class(), addr); - let port = Int::alloc(state.permanent_space.int_class(), port); - - Pointer::boxed((addr, port)) -} diff --git a/vm/src/builtin_functions/stdio.rs b/vm/src/builtin_functions/stdio.rs deleted file mode 100644 index b9904d5c9..000000000 --- a/vm/src/builtin_functions/stdio.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! Functions for working with the standard input/output streams. -use crate::builtin_functions::read_into; -use crate::mem::{ByteArray, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::io::Write; -use std::io::{stderr, stdin, stdout}; - -pub(crate) fn stdout_write_string( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let input = unsafe { InkoString::read(&arguments[0]).as_bytes() }; - let size = thread.blocking(|| stdout().write(input))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), size)) -} - -pub(crate) fn stdout_write_bytes( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let input = unsafe { arguments[0].get::() }.value(); - let size = thread.blocking(|| stdout().write(input))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), size)) -} - -pub(crate) fn stderr_write_string( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let input = unsafe { InkoString::read(&arguments[0]).as_bytes() }; - let size = thread.blocking(|| stderr().write(input))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), size)) -} - -pub(crate) fn stderr_write_bytes( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let input = unsafe { arguments[0].get::() }.value(); - let size = thread.blocking(|| stderr().write(input))? as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), size)) -} - -pub(crate) fn stdout_flush( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - thread.blocking(|| stdout().flush())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn stderr_flush( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - thread.blocking(|| stderr().flush())?; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn stdin_read( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let buff = unsafe { arguments[0].get_mut::() }.value_mut(); - let size = unsafe { Int::read(arguments[1]) }; - let result = thread.blocking(|| read_into(&mut stdin(), buff, size))?; - - Ok(Int::alloc(state.permanent_space.int_class(), result)) -} diff --git a/vm/src/builtin_functions/string.rs b/vm/src/builtin_functions/string.rs deleted file mode 100644 index 36edd7143..000000000 --- a/vm/src/builtin_functions/string.rs +++ /dev/null @@ -1,191 +0,0 @@ -use crate::mem::{Array, ByteArray, Float, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::cmp::min; -use unicode_segmentation::{Graphemes, UnicodeSegmentation}; - -pub(crate) fn string_to_lower( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let lower = unsafe { InkoString::read(&args[0]).to_lowercase() }; - let res = InkoString::alloc(state.permanent_space.string_class(), lower); - - Ok(res) -} - -pub(crate) fn string_to_upper( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let upper = unsafe { InkoString::read(&args[0]).to_uppercase() }; - let res = InkoString::alloc(state.permanent_space.string_class(), upper); - - Ok(res) -} - -pub(crate) fn string_to_byte_array( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let bytes = unsafe { InkoString::read(&args[0]).as_bytes().to_vec() }; - let res = ByteArray::alloc(state.permanent_space.byte_array_class(), bytes); - - Ok(res) -} - -pub(crate) fn string_to_float( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let string = unsafe { InkoString::read(&args[0]) }; - let start = unsafe { Int::read(args[1]) }; - let end = unsafe { Int::read(args[2]) }; - let slice = if start >= 0 && end >= 0 { - &string[start as usize..end as usize] - } else { - string - }; - - let parsed = match slice { - "Infinity" => Ok(f64::INFINITY), - "-Infinity" => Ok(f64::NEG_INFINITY), - _ => slice.parse::(), - }; - - let res = parsed - .map(|val| Float::alloc(state.permanent_space.float_class(), val)) - .unwrap_or_else(|_| Pointer::undefined_singleton()); - - Ok(res) -} - -pub(crate) fn string_to_int( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let string = unsafe { InkoString::read(&args[0]) }; - let radix = unsafe { Int::read(args[1]) }; - let start = unsafe { Int::read(args[2]) }; - let end = unsafe { Int::read(args[3]) }; - - if !(2..=36).contains(&radix) { - return Err(RuntimeError::Panic(format!( - "The radix '{}' is invalid", - radix - ))); - } - - let slice = if start >= 0 && end >= 0 { - &string[start as usize..end as usize] - } else { - string - }; - - // Rust doesn't handle parsing strings like "-0x4a3f043013b2c4d1" out of the - // box. - let parsed = if radix == 16 { - if let Some(tail) = string.strip_prefix("-0x") { - i64::from_str_radix(tail, 16).map(|v| 0_i64.wrapping_sub(v)) - } else { - i64::from_str_radix(slice, 16) - } - } else { - i64::from_str_radix(slice, radix as u32) - }; - - let res = parsed - .map(|val| Int::alloc(state.permanent_space.int_class(), val)) - .unwrap_or_else(|_| Pointer::undefined_singleton()); - - Ok(res) -} - -pub(crate) fn string_characters( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let string = unsafe { InkoString::read(&args[0]) }; - let iter = Pointer::boxed(string.graphemes(true)); - - Ok(iter) -} - -pub(crate) fn string_characters_next( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let iter = unsafe { args[0].get_mut::() }; - let class = state.permanent_space.string_class(); - let res = iter - .next() - .map(|v| InkoString::alloc(class, v.to_string())) - .unwrap_or_else(Pointer::undefined_singleton); - - Ok(res) -} - -pub(crate) fn string_characters_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - unsafe { args[0].drop_boxed::() }; - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn string_concat_array( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let array = unsafe { args[0].get::() }.value(); - let mut buffer = String::new(); - - for str_ptr in array.iter() { - buffer.push_str(unsafe { InkoString::read(str_ptr) }); - } - - Ok(InkoString::alloc(state.permanent_space.string_class(), buffer)) -} - -pub(crate) fn string_slice_bytes( - state: &State, - _: &mut Thread, - _: ProcessPointer, - args: &[Pointer], -) -> Result { - let string = unsafe { InkoString::read(&args[0]) }; - let start = unsafe { Int::read(args[1]) }; - let len = unsafe { Int::read(args[2]) }; - let end = min((start + len) as usize, string.len()); - - let new_string = if start < 0 || len <= 0 || start as usize >= end { - String::new() - } else { - String::from_utf8_lossy( - &string.as_bytes()[start as usize..end as usize], - ) - .into_owned() - }; - - Ok(InkoString::alloc(state.permanent_space.string_class(), new_string)) -} diff --git a/vm/src/builtin_functions/sys.rs b/vm/src/builtin_functions/sys.rs deleted file mode 100644 index 53f8fa625..000000000 --- a/vm/src/builtin_functions/sys.rs +++ /dev/null @@ -1,227 +0,0 @@ -//! Functions for working with the underlying system. -use crate::builtin_functions::read_into; -use crate::mem::{Array, ByteArray, Int, Pointer, String as InkoString}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::io::Write; -use std::process::{Child, Command, Stdio}; -use std::thread::available_parallelism; - -pub(crate) fn child_process_spawn( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let program = unsafe { InkoString::read(&arguments[0]) }; - let args = unsafe { arguments[1].get::() }.value(); - let env = unsafe { arguments[2].get::() }.value(); - let stdin = unsafe { Int::read(arguments[3]) }; - let stdout = unsafe { Int::read(arguments[4]) }; - let stderr = unsafe { Int::read(arguments[5]) }; - let directory = unsafe { InkoString::read(&arguments[6]) }; - let mut cmd = Command::new(program); - - for ptr in args { - cmd.arg(unsafe { InkoString::read(ptr) }); - } - - for pair in env.chunks(2) { - unsafe { - cmd.env(InkoString::read(&pair[0]), InkoString::read(&pair[1])); - } - } - - cmd.stdin(stdio_for(stdin)); - cmd.stdout(stdio_for(stdout)); - cmd.stderr(stdio_for(stderr)); - - if !directory.is_empty() { - cmd.current_dir(directory); - } - - thread.blocking(|| Ok(Pointer::boxed(cmd.spawn()?))) -} - -pub(crate) fn child_process_wait( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let status = thread.blocking(|| child.wait())?; - let code = status.code().unwrap_or(0) as i64; - - Ok(Pointer::int(code)) -} - -pub(crate) fn child_process_try_wait( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let status = child.try_wait()?; - let code = status.map(|s| s.code().unwrap_or(0)).unwrap_or(-1) as i64; - - Ok(Pointer::int(code)) -} - -pub(crate) fn child_process_stdout_read( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let buff = unsafe { arguments[1].get_mut::() }.value_mut(); - let size = unsafe { Int::read(arguments[2]) }; - let value = child - .stdout - .as_mut() - .map(|stream| thread.blocking(|| read_into(stream, buff, size))) - .unwrap_or(Ok(0))?; - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -pub(crate) fn child_process_stderr_read( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let buff = unsafe { arguments[1].get_mut::() }.value_mut(); - let size = unsafe { Int::read(arguments[2]) }; - let value = child - .stderr - .as_mut() - .map(|stream| thread.blocking(|| read_into(stream, buff, size))) - .unwrap_or(Ok(0))?; - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -pub(crate) fn child_process_stdin_write_bytes( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let input = unsafe { arguments[1].get::() }.value(); - let value = child - .stdin - .as_mut() - .map(|stream| thread.blocking(|| stream.write(input))) - .unwrap_or(Ok(0))?; - - Ok(Int::alloc(state.permanent_space.int_class(), value as i64)) -} - -pub(crate) fn child_process_stdin_write_string( - state: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - let input = unsafe { InkoString::read(&arguments[1]) }; - let value = child - .stdin - .as_mut() - .map(|stream| thread.blocking(|| stream.write(input.as_bytes()))) - .unwrap_or(Ok(0))?; - - Ok(Int::alloc(state.permanent_space.int_class(), value as i64)) -} - -pub(crate) fn child_process_stdin_flush( - _: &State, - thread: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - - child - .stdin - .as_mut() - .map(|stream| thread.blocking(|| stream.flush())) - .unwrap_or(Ok(()))?; - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn child_process_stdout_close( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - - child.stdout.take(); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn child_process_stderr_close( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - - child.stderr.take(); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn child_process_stdin_close( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - let child = unsafe { arguments[0].get_mut::() }; - - child.stdin.take(); - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn child_process_drop( - _: &State, - _: &mut Thread, - _: ProcessPointer, - arguments: &[Pointer], -) -> Result { - unsafe { - arguments[0].drop_boxed::(); - } - - Ok(Pointer::nil_singleton()) -} - -pub(crate) fn cpu_cores( - _: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - let cores = available_parallelism().map(|v| v.get()).unwrap_or(1); - - Ok(Pointer::int(cores as i64)) -} - -fn stdio_for(value: i64) -> Stdio { - match value { - 1 => Stdio::inherit(), - 2 => Stdio::piped(), - _ => Stdio::null(), - } -} diff --git a/vm/src/builtin_functions/time.rs b/vm/src/builtin_functions/time.rs deleted file mode 100644 index a52b803a4..000000000 --- a/vm/src/builtin_functions/time.rs +++ /dev/null @@ -1,158 +0,0 @@ -//! Functions for system and monotonic clocks. -use crate::mem::{Float, Int, Pointer}; -use crate::process::ProcessPointer; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use std::mem::MaybeUninit; - -#[cfg(not(windows))] -fn utc() -> f64 { - unsafe { - let mut ts = MaybeUninit::uninit(); - - if libc::clock_gettime(libc::CLOCK_REALTIME, ts.as_mut_ptr()) != 0 { - panic!("clock_gettime() failed"); - } - - let ts = ts.assume_init(); - - ts.tv_sec as f64 + (ts.tv_nsec as f64 / 1_000_000_000.0) - } -} - -#[cfg(not(windows))] -fn offset() -> i64 { - unsafe { - extern "C" { - fn tzset(); - } - - let ts = { - let mut ts = MaybeUninit::uninit(); - - if libc::clock_gettime(libc::CLOCK_REALTIME, ts.as_mut_ptr()) != 0 { - panic!("clock_gettime() failed"); - } - - ts.assume_init() - }; - - let mut tm = MaybeUninit::uninit(); - - // localtime_r() doesn't necessarily call tzset() for us. - tzset(); - - // While localtime_r() may call setenv() internally, this is not a - // problem as Inko caches environment variables upon startup. If an FFI - // call ends up racing with the setenv() call, that's a problem for the - // FFI code. - if libc::localtime_r(&ts.tv_sec, tm.as_mut_ptr()).is_null() { - panic!("localtime_r() failed"); - } - - tm.assume_init().tm_gmtoff - } -} - -#[cfg(windows)] -fn utc() -> f64 { - use windows_sys::Win32::System::SystemInformation::GetSystemTimeAsFileTime; - - unsafe { - let ft = { - let mut ft = MaybeUninit::uninit(); - - GetSystemTimeAsFileTime(ft.as_mut_ptr()); - ft.assume_init() - }; - - let intervals_per_sec = 10_000_000; - let intervals_to_unix = 11_644_473_600 * intervals_per_sec; - let win_time = - i64::from(ft.dwHighDateTime) << 32 | i64::from(ft.dwLowDateTime); - - (win_time - intervals_to_unix) as f64 / intervals_per_sec as f64 - } -} - -#[cfg(windows)] -fn offset() -> i64 { - use windows_sys::Win32::System::SystemServices::{ - TIME_ZONE_ID_DAYLIGHT, TIME_ZONE_ID_STANDARD, TIME_ZONE_ID_UNKNOWN, - }; - use windows_sys::Win32::System::Time::GetTimeZoneInformation; - - unsafe { - let mut tz = MaybeUninit::uninit(); - let bias = match GetTimeZoneInformation(tz.as_mut_ptr()) { - TIME_ZONE_ID_UNKNOWN => tz.assume_init().Bias as i64, - TIME_ZONE_ID_STANDARD => { - let tz = tz.assume_init(); - - tz.Bias as i64 + tz.StandardBias as i64 - } - TIME_ZONE_ID_DAYLIGHT => { - let tz = tz.assume_init(); - - tz.Bias as i64 + tz.DaylightBias as i64 - } - _ => 0, - }; - - // The bias (in minutes) is the result of `UTC - local time`, so if - // you're ahead of UTC the bias is negative. - bias * -60 - } -} - -pub(crate) fn time_monotonic( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - // An i64 gives us roughly 292 years of time. That should be more than - // enough for a monotonic clock, as an Inko program is unlikely to run for - // that long. - let nanos = state.start_time.elapsed().as_nanos() as i64; - - Ok(Int::alloc(state.permanent_space.int_class(), nanos)) -} - -pub(crate) fn time_system( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - Ok(Float::alloc(state.permanent_space.float_class(), utc())) -} - -pub(crate) fn time_system_offset( - state: &State, - _: &mut Thread, - _: ProcessPointer, - _: &[Pointer], -) -> Result { - Ok(Int::alloc(state.permanent_space.int_class(), offset())) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::time::{SystemTime, UNIX_EPOCH}; - - #[test] - fn test_utc() { - let expected = - SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64(); - let given = utc(); - - // We can't assert equality, for there may be time between the two - // function calls. We also can't assert the utc() time is greater in the - // event of clock changes. Instead we just assert the two times are - // within 5 seconds of each other, which should be sufficient. - assert!((given - expected).abs() < 5.0); - } -} diff --git a/vm/src/chunk.rs b/vm/src/chunk.rs deleted file mode 100644 index 6686beb5d..000000000 --- a/vm/src/chunk.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Fixed-size chunks of memory. -use std::alloc::{self, Layout}; -use std::mem; -use std::ops::Drop; -use std::ptr; - -/// A fixed-size amount of memory. -/// -/// Chunks are a bit like a Vec, but with far fewer features and no bounds -/// checking. This makes them useful for cases where reads and writes are very -/// frequent, and an external source (e.g. a compiler) verifies if these -/// operations are within bounds. -/// -/// A Chunk does not drop the values stored within, simply because we don't need -/// this at this time. -pub(crate) struct Chunk { - ptr: *mut T, - capacity: usize, -} - -unsafe fn layout_for(capacity: usize) -> Layout { - Layout::from_size_align_unchecked( - mem::size_of::() * capacity, - mem::align_of::(), - ) -} - -#[cfg_attr(feature = "cargo-clippy", allow(clippy::len_without_is_empty))] -impl Chunk { - pub(crate) fn new(capacity: usize) -> Self { - if capacity == 0 { - return Chunk { ptr: ptr::null_mut(), capacity: 0 }; - } - - let layout = unsafe { layout_for::(capacity) }; - let ptr = unsafe { alloc::alloc_zeroed(layout) as *mut T }; - - if ptr.is_null() { - alloc::handle_alloc_error(layout); - } - - Chunk { ptr, capacity } - } - - pub(crate) fn len(&self) -> usize { - self.capacity - } - - pub(crate) unsafe fn get(&self, index: usize) -> &T { - &*self.ptr.add(index) - } - - pub(crate) unsafe fn set(&mut self, index: usize, value: T) { - self.ptr.add(index).write(value); - } -} - -impl Drop for Chunk { - fn drop(&mut self) { - unsafe { - if !self.ptr.is_null() { - alloc::dealloc( - self.ptr as *mut u8, - layout_for::(self.len()), - ); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mem::Pointer; - - #[test] - fn test_empty_chunk() { - let chunk = Chunk::<()>::new(0); - - assert_eq!(chunk.len(), 0); - assert!(chunk.ptr.is_null()); - } - - #[test] - fn test_len() { - let chunk = Chunk::::new(4); - - assert_eq!(chunk.len(), 4); - } - - #[test] - fn test_get_set() { - let mut chunk = Chunk::::new(2); - - unsafe { - chunk.set(0, Pointer::int(5)); - - assert!(*chunk.get(0) == Pointer::int(5)); - } - } -} diff --git a/vm/src/execution_context.rs b/vm/src/execution_context.rs deleted file mode 100644 index 225c69434..000000000 --- a/vm/src/execution_context.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Process Execution Contexts -//! -//! An execution context contains the registers, bindings, and other information -//! needed by a process in order to execute bytecode. -use crate::mem::MethodPointer; -use crate::mem::Pointer; -use crate::registers::Registers; - -/// A single call frame, its variables, registers, and more. -pub(crate) struct ExecutionContext { - /// The code that we're currently running. - pub method: MethodPointer, - - /// The parent execution context. - pub parent: Option>, - - /// The index of the instruction to store prior to suspending a process. - pub index: usize, - - /// The registers for this context. - registers: Registers, -} - -/// Struct for iterating over an ExecutionContext and its parent contexts. -pub(crate) struct ExecutionContextIterator<'a> { - current: Option<&'a ExecutionContext>, -} - -impl ExecutionContext { - pub(crate) fn new(method: MethodPointer) -> Self { - ExecutionContext { - registers: Registers::new(method.registers), - method, - parent: None, - index: 0, - } - } - - /// Returns the method that is being executed. - pub(crate) fn method(&self) -> MethodPointer { - self.method - } - - /// Returns the value of a single register. - pub(crate) fn get_register(&self, register: u16) -> Pointer { - self.registers.get(register) - } - - /// Sets the value of a single register. - pub(crate) fn set_register(&mut self, register: u16, value: Pointer) { - self.registers.set(register, value); - } - - /// Returns an iterator for traversing the context chain, including the - /// current context. - pub(crate) fn contexts(&self) -> ExecutionContextIterator { - ExecutionContextIterator { current: Some(self) } - } -} - -impl<'a> Iterator for ExecutionContextIterator<'a> { - type Item = &'a ExecutionContext; - - fn next(&mut self) -> Option<&'a ExecutionContext> { - if let Some(ctx) = self.current { - if let Some(parent) = ctx.parent.as_ref() { - self.current = Some(&**parent); - } else { - self.current = None; - } - - return Some(ctx); - } - - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mem::Method; - use crate::test::empty_method; - use std::mem; - - fn with_context(callback: F) { - let method = empty_method(); - let ctx = ExecutionContext::new(method); - - callback(ctx); - Method::drop_and_deallocate(method); - } - - #[test] - fn test_new() { - let method = empty_method(); - let ctx = ExecutionContext::new(method); - - assert!(ctx.method.as_ptr() == method.as_ptr()); - assert!(ctx.parent.is_none()); - assert_eq!(ctx.index, 0); - - Method::drop_and_deallocate(method); - } - - #[test] - fn test_for_method() { - let method = empty_method(); - let ctx = ExecutionContext::new(method); - - assert!(ctx.method.as_ptr() == method.as_ptr()); - assert!(ctx.parent.is_none()); - assert_eq!(ctx.index, 0); - - Method::drop_and_deallocate(method); - } - - #[test] - fn test_get_set_register() { - with_context(|mut ctx| { - let ptr = Pointer::int(42); - - ctx.set_register(0, ptr); - - assert_eq!(ctx.get_register(0), ptr); - }); - } - - #[test] - fn test_contexts() { - with_context(|ctx| { - let mut iter = ctx.contexts(); - - assert!(iter.next().is_some()); - assert!(iter.next().is_none()); - }); - } - - #[test] - fn test_type_size() { - assert_eq!(mem::size_of::(), 40); - } -} diff --git a/vm/src/ffi.rs b/vm/src/ffi.rs deleted file mode 100644 index abcc27226..000000000 --- a/vm/src/ffi.rs +++ /dev/null @@ -1,731 +0,0 @@ -use crate::mem::{ - ByteArray, Float, Int, Pointer as InkoPointer, String as InkoString, -}; -use crate::state::State; -use libffi::low::{ - call as ffi_call, ffi_abi_FFI_DEFAULT_ABI as ABI, ffi_cif, ffi_type, - prep_cif, types, CodePtr, Error as FFIError, -}; -use std::convert::Into; -use std::ffi::{CStr, OsStr}; -use std::fmt::{Debug, Display}; -use std::mem; -use std::os::raw::{c_char, c_double, c_float, c_int, c_long, c_short, c_void}; -use std::ptr; - -#[repr(i64)] -#[derive(Copy, Clone)] -enum Type { - /// The numeric identifier of the C `void` type. - Void, - - /// The numeric identifier of the C `void*` type. - Pointer, - - /// The numeric identifier of the C `double` type. - F64, - - /// The numeric identifier of the C `float` type. - F32, - - /// The numeric identifier of the C `signed char` type. - I8, - - /// The numeric identifier of the C `short` type. - I16, - - /// The numeric identifier of the C `int` type. - I32, - - /// The numeric identifier of the C `long` type. - I64, - - /// The numeric identifier of the C `unsigned char` type. - U8, - - /// The numeric identifier of the C `unsigned short` type. - U16, - - /// The numeric identifier of the C `unsigned int` type. - U32, - - /// The numeric identifier of the C `unsigned long` type. - U64, - - /// The numeric identifier for the C `const char*` type. - String, - - /// The numeric identifier for a C `const char*` type that should be read - /// into a byte array. - ByteArray, - - /// The numeric identifier of the C `size_t` type. - SizeT, -} - -impl Type { - fn from_i64(value: i64) -> Result { - match value { - 0 => Ok(Type::Void), - 1 => Ok(Type::Pointer), - 2 => Ok(Type::F64), - 3 => Ok(Type::F32), - 4 => Ok(Type::I8), - 5 => Ok(Type::I16), - 6 => Ok(Type::I32), - 7 => Ok(Type::I64), - 8 => Ok(Type::U8), - 9 => Ok(Type::U16), - 10 => Ok(Type::U32), - 11 => Ok(Type::U64), - 12 => Ok(Type::String), - 13 => Ok(Type::ByteArray), - 14 => Ok(Type::SizeT), - _ => Err(format!("The type identifier '{}' is invalid", value)), - } - } - - unsafe fn as_ffi_type(&self) -> *mut ffi_type { - match self { - Type::Void => &mut types::void as *mut _, - Type::Pointer => &mut types::pointer as *mut _, - Type::F64 => &mut types::double as *mut _, - Type::F32 => &mut types::float as *mut _, - Type::I8 => &mut types::sint8 as *mut _, - Type::I16 => &mut types::sint16 as *mut _, - Type::I32 => &mut types::sint32 as *mut _, - Type::I64 => &mut types::sint64 as *mut _, - Type::U8 => &mut types::uint8 as *mut _, - Type::U16 => &mut types::uint16 as *mut _, - Type::U32 => &mut types::uint32 as *mut _, - Type::U64 => &mut types::uint64 as *mut _, - Type::String => &mut types::pointer as *mut _, - Type::ByteArray => &mut types::pointer as *mut _, - Type::SizeT => match mem::size_of::() { - 64 => &mut types::uint64 as *mut _, - 32 => &mut types::uint32 as *mut _, - 8 => &mut types::uint8 as *mut _, - _ => &mut types::uint16 as *mut _, - }, - } - } -} - -/// A C library, such as libc. -/// -/// This is currently a thin wrapper around libloading's Library structure, -/// allowing us to decouple the rest of the VM code from libloading. -pub(crate) struct Library { - inner: libloading::Library, -} - -/// A pointer to an FFI type. -pub(crate) type TypePointer = *mut ffi_type; - -/// A raw C pointer. -pub(crate) type RawPointer = *mut c_void; - -/// A wrapper around a C pointer. -#[derive(Clone, Copy)] -#[repr(transparent)] -pub(crate) struct Pointer { - inner: RawPointer, -} - -unsafe impl Send for Pointer {} - -/// A function with a fixed number of arguments. -pub(crate) struct Function { - /// The pointer to the function to call. - pointer: Pointer, - - /// The CIF (Call Interface) to use for this function. - cif: ffi_cif, - - /// The argument types of the function. - argument_types: Vec, - - /// The raw FFI types of the arguments. - /// - /// A CIF maintains a reference to this array, so we have to keep it around. - argument_ffi_types: Vec, - - /// The return type of the function. - return_type: Type, -} - -/// Returns the size of a type ID. -/// -/// The size of the type is returned as a tagged integer. -pub(crate) fn type_size(id: i64) -> Result { - let size = unsafe { - match Type::from_i64(id)? { - Type::Void => types::void.size, - Type::Pointer | Type::String | Type::ByteArray => { - types::pointer.size - } - Type::F64 => types::double.size, - Type::F32 => types::float.size, - Type::I8 => types::sint8.size, - Type::I16 => types::sint16.size, - Type::I32 => types::sint32.size, - Type::I64 => types::sint64.size, - Type::U8 => types::uint8.size, - Type::U16 => types::uint16.size, - Type::U32 => types::uint32.size, - Type::U64 => types::uint64.size, - Type::SizeT => mem::size_of::(), - } - }; - - Ok(InkoPointer::int(size as i64)) -} - -/// Returns the alignment of a type ID. -/// -/// The alignment of the type is returned as a tagged integer. -pub(crate) fn type_alignment(id: i64) -> Result { - let size = unsafe { - match Type::from_i64(id)? { - Type::Void => types::void.alignment, - Type::Pointer | Type::String | Type::ByteArray => { - types::pointer.alignment - } - Type::F64 => types::double.alignment, - Type::F32 => types::float.alignment, - Type::I8 => types::sint8.alignment, - Type::I16 => types::sint16.alignment, - Type::I32 => types::sint32.alignment, - Type::I64 => types::sint64.alignment, - Type::U8 => types::uint8.alignment, - Type::U16 => types::uint16.alignment, - Type::U32 => types::uint32.alignment, - Type::U64 => types::uint64.alignment, - Type::SizeT => mem::align_of::() as u16, - } - }; - - Ok(InkoPointer::int(i64::from(size))) -} - -/// A value of some sort to be passed to a C function. -pub(crate) enum Argument { - F32(f32), - F64(f64), - I16(i16), - I32(i32), - I64(i64), - I8(i8), - Pointer(RawPointer), - U16(u16), - U32(u32), - U64(u64), - U8(u8), - Void, -} - -impl Argument { - // Creates a new Argument wrapping the value of `ptr` according to the needs - // of the FFI type specified in `ffi_type`. - unsafe fn wrap( - ffi_type: Type, - ptr: InkoPointer, - ) -> Result { - let argument = match ffi_type { - Type::Pointer => Argument::Pointer(ptr.as_ptr() as RawPointer), - Type::String => Argument::Pointer( - ptr.get::().value().as_c_char_pointer() - as RawPointer, - ), - Type::ByteArray => Argument::Pointer( - ptr.get::().value().as_ptr() as RawPointer, - ), - Type::Void => Argument::Void, - Type::F32 => Argument::F32(Float::read(ptr) as f32), - Type::F64 => Argument::F64(Float::read(ptr)), - Type::I8 => Argument::I8(Int::read(ptr) as i8), - Type::I16 => Argument::I16(Int::read(ptr) as i16), - Type::I32 => Argument::I32(Int::read(ptr) as i32), - Type::I64 => Argument::I64(Int::read(ptr) as i64), - Type::U8 => Argument::U8(Int::read(ptr) as u8), - Type::U16 => Argument::U16(Int::read(ptr) as u16), - Type::U32 => Argument::U32(Int::read(ptr) as u32), - Type::U64 => Argument::U64(Int::read(ptr) as u64), - Type::SizeT => match mem::size_of::() { - 64 => Argument::U64(Int::read(ptr) as u64), - 32 => Argument::U32(Int::read(ptr) as u32), - 8 => Argument::U8(Int::read(ptr) as u8), - _ => Argument::U16(Int::read(ptr) as u16), - }, - }; - - Ok(argument) - } - - /// Returns a C pointer to the wrapped value. - fn as_c_pointer(&mut self) -> RawPointer { - match self { - Argument::Pointer(ref mut val) => { - // When passing a pointer we shouldn't pass the pointer - // directly, instead we want a pointer to the pointer to pass to - // the underlying C function. - val as *mut RawPointer as RawPointer - } - Argument::Void => ptr::null_mut() as RawPointer, - Argument::F32(ref mut val) => val as *mut _ as RawPointer, - Argument::F64(ref mut val) => val as *mut _ as RawPointer, - Argument::I8(ref mut val) => val as *mut _ as RawPointer, - Argument::I16(ref mut val) => val as *mut _ as RawPointer, - Argument::I32(ref mut val) => val as *mut _ as RawPointer, - Argument::I64(ref mut val) => val as *mut _ as RawPointer, - Argument::U8(ref mut val) => val as *mut _ as RawPointer, - Argument::U16(ref mut val) => val as *mut _ as RawPointer, - Argument::U32(ref mut val) => val as *mut _ as RawPointer, - Argument::U64(ref mut val) => val as *mut _ as RawPointer, - } - } -} - -impl Library { - /// Opens a library using one or more possible names, stored as pointers to - /// heap allocated objects. - pub(crate) fn from_pointers(search_for: &[InkoPointer]) -> Option { - let names = search_for - .iter() - .map(|n| unsafe { InkoString::read(n) }) - .collect::>(); - - Self::open(&names) - } - - /// Opens a library using one or more possible names. - pub(crate) fn open + Debug + Display>( - search_for: &[P], - ) -> Option { - for name in search_for { - if let Ok(library) = unsafe { libloading::Library::new(name) } - .map(|inner| Library { inner }) - { - return Some(library); - } - } - - None - } - - /// Obtains a pointer to a symbol. - /// - /// This method is unsafe because the pointer could be of any type, thus it - /// is up to the caller to make sure the result is used appropriately. - pub(crate) unsafe fn get(&self, name: &str) -> Option { - self.inner - .get(name.as_bytes()) - .map(|sym: libloading::Symbol| Pointer::new(*sym)) - .ok() - } -} - -impl Pointer { - pub(crate) fn new(inner: RawPointer) -> Self { - Pointer { inner } - } - - /// Reads the value of this pointer into a particular type, based on the - /// integer specified in `kind`. - pub(crate) unsafe fn read_as( - self, - state: &State, - kind: InkoPointer, - ) -> Result { - let int = Int::read(kind); - let pointer = match Type::from_i64(int)? { - Type::Pointer => { - let ptr: RawPointer = self.read(); - - InkoPointer::new(ptr as *mut u8) - } - Type::Void => InkoPointer::new(ptr::null_mut()), - Type::String => { - let string = self.read_cstr().to_string_lossy().into_owned(); - - InkoString::alloc(state.permanent_space.string_class(), string) - } - Type::ByteArray => { - let bytes = self.read_cstr().to_bytes().to_vec(); - - ByteArray::alloc( - state.permanent_space.byte_array_class(), - bytes, - ) - } - Type::F64 => self.read_float::(state), - Type::F32 => self.read_float::(state), - Type::I8 | Type::U8 => self.read_signed_integer::(state), - Type::I16 | Type::U16 => self.read_signed_integer::(state), - Type::I32 | Type::U32 => self.read_signed_integer::(state), - Type::I64 | Type::U64 => self.read_signed_integer::(state), - Type::SizeT => match mem::size_of::() { - 64 => self.read_signed_integer::(state), - 32 => self.read_signed_integer::(state), - 8 => self.read_signed_integer::(state), - _ => self.read_signed_integer::(state), - }, - }; - - Ok(pointer) - } - - /// Writes a value to the underlying pointer. - pub(crate) unsafe fn write_as( - self, - kind: InkoPointer, - value: InkoPointer, - ) -> Result<(), String> { - let int = Int::read(kind); - - match Type::from_i64(int)? { - Type::String => { - let string = value.get::().value(); - - ptr::copy( - string.as_c_char_pointer(), - self.inner as *mut c_char, - string.len_with_null_byte(), - ); - } - Type::ByteArray => { - let byte_array = value.get::().value(); - - ptr::copy( - byte_array.as_ptr(), - self.inner as *mut _, - byte_array.len(), - ); - } - Type::Pointer => self.write(value.as_ptr() as RawPointer), - Type::Void => self.write(ptr::null_mut() as RawPointer), - Type::F64 => self.write(Float::read(value)), - Type::F32 => self.write(Float::read(value) as f32), - Type::I8 => self.write(Int::read(value) as i8), - Type::I16 => self.write(Int::read(value) as i16), - Type::I32 => self.write(Int::read(value) as i32), - Type::I64 => self.write(Int::read(value) as i64), - Type::U8 => self.write(Int::read(value) as u8), - Type::U16 => self.write(Int::read(value) as u16), - Type::U32 => self.write(Int::read(value) as u32), - Type::U64 => self.write(Int::read(value) as u64), - Type::SizeT => self.write(Int::read(value) as usize), - }; - - Ok(()) - } - - /// Returns a new Pointer, optionally starting at the given offset. - /// - /// The `offset` argument is the offset in _bytes_, not the number of - /// elements (unlike Rust's `pointer::offset`). - pub(crate) fn with_offset(self, offset_bytes: usize) -> Self { - let inner = (self.inner as usize + offset_bytes) as RawPointer; - - Pointer::new(inner) - } - - /// Returns the underlying pointer. - pub(crate) fn as_ptr(self) -> *mut u8 { - self.inner as _ - } - - unsafe fn read(self) -> R { - ptr::read(self.inner as *mut R) - } - - unsafe fn write(self, value: T) { - ptr::write(self.inner as *mut T, value); - } - - unsafe fn read_signed_integer>( - self, - state: &State, - ) -> InkoPointer { - Int::alloc(state.permanent_space.int_class(), self.read::().into()) - } - - unsafe fn read_float>(self, state: &State) -> InkoPointer { - Float::alloc( - state.permanent_space.float_class(), - self.read::().into(), - ) - } - - unsafe fn read_cstr<'a>(self) -> &'a CStr { - CStr::from_ptr(self.inner as *mut c_char) - } -} - -impl Function { - pub(crate) unsafe fn attach( - library: &Library, - name: &str, - arguments: &[InkoPointer], - return_type: InkoPointer, - ) -> Result, String> { - let func_ptr = if let Some(sym) = library.get(name) { - sym - } else { - return Ok(None); - }; - - let rtype = Type::from_i64(Int::read(return_type))?; - let mut args = Vec::with_capacity(arguments.len()); - - for ptr in arguments { - args.push(Type::from_i64(Int::read(*ptr))?); - } - - Self::create(func_ptr, args, rtype).map(Some) - } - - unsafe fn create( - pointer: Pointer, - argument_types: Vec, - return_type: Type, - ) -> Result { - let argument_ffi_types: Vec = - argument_types.iter().map(|t| t.as_ffi_type()).collect(); - let mut func = Function { - pointer, - cif: Default::default(), - argument_types, - argument_ffi_types, - return_type, - }; - - let result = prep_cif( - &mut func.cif, - ABI, - func.argument_types.len(), - return_type.as_ffi_type(), - func.argument_ffi_types.as_mut_ptr(), - ); - - match result { - Ok(_) => Ok(func), - Err(FFIError::Typedef) => { - Err("The type representation is invalid or unsupported" - .to_string()) - } - Err(FFIError::Abi) => { - Err("The ABI is invalid or unsupported".to_string()) - } - } - } - - pub(crate) unsafe fn call( - &self, - state: &State, - arg_ptrs: &[InkoPointer], - ) -> Result { - if arg_ptrs.len() != self.argument_types.len() { - return Err(format!( - "Invalid number of arguments, expected {} but got {}", - self.argument_types.len(), - arg_ptrs.len() - )); - } - - let mut arguments = Vec::with_capacity(arg_ptrs.len()); - - for (index, arg) in arg_ptrs.iter().enumerate() { - arguments.push(Argument::wrap(self.argument_types[index], *arg)?); - } - - // libffi expects an array of _pointers_ to the arguments to pass, - // instead of an array containing the arguments directly. The pointers - // and the values they point to must outlive the FFI call, otherwise we - // may end up passing pointers to invalid memory. - let mut argument_pointers: Vec = - arguments.iter_mut().map(Argument::as_c_pointer).collect(); - - // libffi requires a mutable pointer to the CIF, but "self" is immutable - // since we never actually modify the current function. To work around - // this we manually cast to a mutable pointer. - let cif_ptr = &self.cif as *const _ as *mut _; - let fun_ptr = CodePtr::from_ptr(self.pointer.inner); - let args_ptr = argument_pointers.as_mut_ptr(); - let result = match self.return_type { - Type::Void => { - ffi_call::(cif_ptr, fun_ptr, args_ptr); - InkoPointer::nil_singleton() - } - Type::Pointer => { - InkoPointer::new(ffi_call(cif_ptr, fun_ptr, args_ptr)) - } - Type::F64 | Type::F32 => { - let result: c_double = ffi_call(cif_ptr, fun_ptr, args_ptr); - - Float::alloc(state.permanent_space.float_class(), result as f64) - } - Type::I8 - | Type::I16 - | Type::I32 - | Type::I64 - | Type::U8 - | Type::U16 - | Type::U32 - | Type::U64 - | Type::SizeT => { - let result: c_long = ffi_call(cif_ptr, fun_ptr, args_ptr); - - Int::alloc(state.permanent_space.int_class(), result as i64) - } - Type::String => { - let result = - CStr::from_ptr(ffi_call(cif_ptr, fun_ptr, args_ptr)) - .to_string_lossy() - .into_owned(); - - InkoString::alloc(state.permanent_space.string_class(), result) - } - Type::ByteArray => { - let result = - CStr::from_ptr(ffi_call(cif_ptr, fun_ptr, args_ptr)) - .to_bytes(); - - ByteArray::alloc( - state.permanent_space.byte_array_class(), - result.into(), - ) - } - }; - - Ok(result) - } -} - -#[cfg(all( - test, - any(target_os = "macos", target_os = "linux", target_os = "windows") -))] -mod tests { - use super::*; - use crate::mem::Pointer as InkoPointer; - use crate::mem::String as InkoString; - use crate::test::setup; - - extern "C" { - fn calloc(amount: usize, size: usize) -> RawPointer; - fn free(pointer: RawPointer); - } - - #[cfg(target_os = "macos")] - const LIBM: &'static str = "libm.dylib"; - - #[cfg(target_os = "linux")] - const LIBM: &str = "libm.so.6"; - - #[cfg(target_os = "windows")] - const LIBM: &'static str = "msvcrt.dll"; - - #[test] - fn test_library_new() { - assert!(Library::open(&[LIBM]).is_some()); - } - - #[test] - fn test_library_get() { - let lib = Library::open(&[LIBM]).unwrap(); - let sym = unsafe { lib.get("floor") }; - - assert!(sym.is_some()); - } - - #[test] - fn test_function_new() { - let lib = Library::open(&[LIBM]).unwrap(); - - unsafe { - let sym = lib.get("floor").unwrap(); - - let fun = Function::create(sym, vec![Type::F64], Type::F64); - - assert!(fun.is_ok()); - } - } - - #[test] - fn test_function_from_pointers() { - let state = setup(); - let name = InkoString::alloc( - state.permanent_space.string_class(), - LIBM.to_string(), - ); - - let names = vec![name]; - let lib = Library::from_pointers(&names); - - assert!(lib.is_some()); - - unsafe { - InkoString::drop_and_deallocate(name); - } - } - - #[test] - fn test_function_call() { - let lib = Library::open(&[LIBM]).unwrap(); - let state = setup(); - let arg = Float::alloc(state.permanent_space.float_class(), 3.15); - - unsafe { - let sym = lib.get("floor").unwrap(); - let fun = - Function::create(sym, vec![Type::F64], Type::F64).unwrap(); - let res = fun.call(&state, &[arg]).unwrap(); - - assert_eq!(Float::read(res), 3.0); - arg.free(); - res.free(); - } - } - - #[test] - fn test_pointer_read_and_write() { - let state = setup(); - - unsafe { - let ptr = Pointer::new(calloc(1, 3)); - let kind = InkoPointer::int(12); - let val = InkoString::alloc( - state.permanent_space.string_class(), - "ab".to_string(), - ); - - ptr.write_as(kind, val).unwrap(); - - let result = ptr.read_as(&state, kind); - - free(ptr.inner); - - assert!(result.is_ok()); - - let new_string = result.unwrap(); - - assert_eq!(InkoString::read(&new_string), "ab"); - - InkoString::drop_and_deallocate(val); - InkoString::drop_and_deallocate(new_string); - } - } -} - -#[cfg(test)] -mod tests_for_all_platforms { - use super::*; - - #[test] - fn test_library_new_invalid() { - let lib = Library::open(&["inko-test-1", "inko-test-2"]); - - assert!(lib.is_none()); - } -} diff --git a/vm/src/hasher.rs b/vm/src/hasher.rs deleted file mode 100644 index e11ddce49..000000000 --- a/vm/src/hasher.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Types and methods for hashing objects. -use ahash::AHasher; -use std::hash::{Hash, Hasher as _}; -use std::i64; - -#[derive(Clone)] -pub(crate) struct Hasher { - hasher: AHasher, -} - -impl Hasher { - pub(crate) fn new(hasher: AHasher) -> Self { - Hasher { hasher } - } - - pub(crate) fn write_int(&mut self, value: i64) { - value.hash(&mut self.hasher); - } - - pub(crate) fn finish(&mut self) -> i64 { - self.hasher.finish() as i64 - } -} - -#[cfg(test)] -mod tests { - use super::*; - use ahash::RandomState; - use std::hash::BuildHasher as _; - use std::mem::size_of; - - #[test] - fn test_write_int() { - let state = RandomState::new(); - let mut hasher1 = Hasher::new(state.build_hasher()); - let mut hasher2 = Hasher::new(state.build_hasher()); - - hasher1.write_int(10); - hasher2.write_int(10); - - assert_eq!(hasher1.finish(), hasher2.finish()); - } - - #[test] - fn test_mem_size() { - assert!(size_of::() <= 48); - } -} diff --git a/vm/src/image.rs b/vm/src/image.rs deleted file mode 100644 index cb3404747..000000000 --- a/vm/src/image.rs +++ /dev/null @@ -1,1076 +0,0 @@ -//! Loading of Inko bytecode images. -//! -//! Various chunks of bytecode, such as numbers, are encoded using -//! little-endian. Since most conventional CPUs use little-endian, using the -//! same endianness means we don't have to flip bits around on these CPUs. -use crate::chunk::Chunk; -use crate::config::Config; -use crate::indexes::{ClassIndex, MethodIndex}; -use crate::location_table::LocationTable; -use crate::mem::{Class, ClassPointer, Method, Module, ModulePointer, Pointer}; -use crate::permanent_space::{ - MethodCounts, PermanentSpace, ARRAY_CLASS, BOOLEAN_CLASS, BYTE_ARRAY_CLASS, - FLOAT_CLASS, FUTURE_CLASS, INT_CLASS, NIL_CLASS, STRING_CLASS, -}; -use bytecode::{ - Instruction, Opcode, CONST_ARRAY, CONST_FLOAT, CONST_INTEGER, CONST_STRING, - SIGNATURE_BYTES, VERSION, -}; -use std::f64; -use std::fs::File; -use std::io::{BufReader, Read}; -use std::str; - -macro_rules! read_slice { - ($stream:expr, $amount:expr) => {{ - let mut buffer: [u8; $amount] = [0; $amount]; - - $stream - .read_exact(&mut buffer) - .map_err(|e| format!("Failed to read {} bytes: {}", $amount, e))?; - - buffer - }}; -} - -macro_rules! read_vec { - ($stream:expr, $amount:expr) => {{ - let mut buffer: Vec = vec![0; $amount]; - - $stream.read_exact(&mut buffer).map_err(|e| e.to_string())?; - - buffer - }}; -} - -macro_rules! read_byte { - ($stream: expr) => {{ - read_slice!($stream, 1)[0] - }}; -} - -/// The number of bytes to buffer for every read from a bytecode file. -const BUFFER_SIZE: usize = 32 * 1024; - -/// A parsed bytecode image. -pub struct Image { - /// The ID of the first class to run. - pub(crate) entry_class: ClassPointer, - - /// The index of the entry module to run. - pub(crate) entry_method: MethodIndex, - - /// The space to use for allocating permanent objects. - pub(crate) permanent_space: PermanentSpace, - - /// Configuration settings to use when parsing and running bytecode. - pub(crate) config: Config, -} - -impl Image { - /// Loads a bytecode image from a file. - pub fn load_file(config: Config, path: &str) -> Result { - let file = File::open(path).map_err(|e| e.to_string())?; - - Self::load(config, &mut BufReader::with_capacity(BUFFER_SIZE, file)) - } - - pub fn load_bytes(config: Config, bytes: Vec) -> Result { - Self::load( - config, - &mut BufReader::with_capacity(BUFFER_SIZE, bytes.as_slice()), - ) - } - - /// Loads a bytecode image from a stream of bytes. - fn load(config: Config, stream: &mut R) -> Result { - // This is a simply/naive check to make sure we're _probably_ loading an - // Inko bytecode image; instead of something random like a PNG file. - if read_slice!(stream, 4) != SIGNATURE_BYTES { - return Err("The bytecode signature is invalid".to_string()); - } - - let version = read_byte!(stream); - - if version != VERSION { - return Err(format!( - "The bytecode version {} is not supported", - version - )); - } - - let modules = read_u32(stream)?; - let classes = read_u32(stream)?; - let space = PermanentSpace::new( - modules, - classes, - read_builtin_method_counts(stream)?, - ); - - let entry_class = ClassIndex::new(read_u32(stream)?); - let entry_method = read_u16(stream)?; - - // Now we can load the bytecode for all modules, including the entry - // module. The order in which the modules are returned is unspecified. - read_modules(modules as usize, &space, stream)?; - - Ok(Image { - entry_class: unsafe { space.get_class(entry_class) }, - entry_method: MethodIndex::new(entry_method), - permanent_space: space, - config, - }) - } -} - -fn read_builtin_method_counts( - stream: &mut R, -) -> Result { - let int_class = read_u16(stream)?; - let float_class = read_u16(stream)?; - let string_class = read_u16(stream)?; - let array_class = read_u16(stream)?; - let boolean_class = read_u16(stream)?; - let nil_class = read_u16(stream)?; - let byte_array_class = read_u16(stream)?; - let future_class = read_u16(stream)?; - let counts = MethodCounts { - int_class, - float_class, - string_class, - array_class, - boolean_class, - nil_class, - byte_array_class, - future_class, - }; - - Ok(counts) -} - -fn read_modules( - num_modules: usize, - space: &PermanentSpace, - stream: &mut R, -) -> Result<(), String> { - for _ in 0..num_modules { - let amount = read_u64(stream)? as usize; - let chunk = read_vec!(stream, amount); - - read_module(space, &mut &chunk[..])?; - } - - Ok(()) -} - -fn read_module( - space: &PermanentSpace, - stream: &mut R, -) -> Result { - let index = read_u32(stream)?; - let constants = read_constants(space, stream)?; - let class_index = read_u32(stream)?; - - read_classes(space, stream, &constants)?; - - let mod_class = unsafe { space.get_class(ClassIndex::new(class_index)) }; - let module = Module::alloc(mod_class); - - unsafe { space.add_module(index, module)? }; - - Ok(module) -} - -fn read_string(stream: &mut R) -> Result { - let size = read_u32(stream)? as usize; - let buff = read_vec!(stream, size); - - String::from_utf8(buff).map_err(|e| e.to_string()) -} - -fn read_constant_array( - space: &PermanentSpace, - stream: &mut R, -) -> Result, String> { - let amount = read_u16(stream)? as usize; - let mut values = Vec::with_capacity(amount); - - for _ in 0..amount { - values.push(read_constant(space, stream)?); - } - - Ok(values) -} - -fn read_u8(stream: &mut R) -> Result { - Ok(read_byte!(stream)) -} - -fn read_u16(stream: &mut R) -> Result { - let buff = read_slice!(stream, 2); - - Ok(u16::from_le_bytes(buff)) -} - -fn read_u32(stream: &mut R) -> Result { - let buff = read_slice!(stream, 4); - - Ok(u32::from_le_bytes(buff)) -} - -fn read_i64(stream: &mut R) -> Result { - let buff = read_slice!(stream, 8); - - Ok(i64::from_le_bytes(buff)) -} - -fn read_u64(stream: &mut R) -> Result { - let buff = read_slice!(stream, 8); - - Ok(u64::from_le_bytes(buff)) -} - -fn read_f64(stream: &mut R) -> Result { - let buff = read_slice!(stream, 8); - let int = u64::from_le_bytes(buff); - - Ok(f64::from_bits(int)) -} - -fn read_instruction( - stream: &mut R, - constants: &Chunk, -) -> Result { - let opcode = Opcode::from_byte(read_u8(stream)?)?; - let mut args = [0, 0, 0, 0, 0]; - - for index in 0..opcode.arity() { - args[index] = read_u16(stream)?; - } - - let mut ins = Instruction::new(opcode, args); - - // GetConstant instructions are rewritten such that the pointer to the value - // is encoded directly into its arguments. - if let Opcode::GetConstant = opcode { - let ptr = unsafe { *constants.get(ins.u32_arg(1, 2) as usize) }; - let addr = ptr.as_ptr() as u64; - let bytes = u64::to_le_bytes(addr); - - ins.arguments[1] = u16::from_le_bytes([bytes[0], bytes[1]]); - ins.arguments[2] = u16::from_le_bytes([bytes[2], bytes[3]]); - ins.arguments[3] = u16::from_le_bytes([bytes[4], bytes[5]]); - ins.arguments[4] = u16::from_le_bytes([bytes[6], bytes[7]]); - } - - Ok(ins) -} - -fn read_instructions( - stream: &mut R, - constants: &Chunk, -) -> Result, String> { - let amount = read_u32(stream)? as usize; - let mut buff = Vec::with_capacity(amount); - - for _ in 0..amount { - buff.push(read_instruction(stream, constants)?); - } - - Ok(buff) -} - -fn read_classes( - space: &PermanentSpace, - stream: &mut R, - constants: &Chunk, -) -> Result<(), String> { - let amount = read_u16(stream)?; - - for _ in 0..amount { - read_class(space, stream, constants)?; - } - - Ok(()) -} - -fn read_class( - space: &PermanentSpace, - stream: &mut R, - constants: &Chunk, -) -> Result<(), String> { - let index = read_u32(stream)?; - let process_class = read_u8(stream)? == 1; - let name = read_string(stream)?; - let fields = read_u8(stream)? as usize; - let method_slots = read_u16(stream)?; - let class = match index as usize { - INT_CLASS => space.int_class(), - FLOAT_CLASS => space.float_class(), - STRING_CLASS => space.string_class(), - ARRAY_CLASS => space.array_class(), - BOOLEAN_CLASS => space.boolean_class(), - NIL_CLASS => space.nil_class(), - BYTE_ARRAY_CLASS => space.byte_array_class(), - FUTURE_CLASS => space.future_class(), - _ => { - let new_class = if process_class { - Class::process(name, fields, method_slots) - } else { - Class::object(name, fields, method_slots) - }; - - unsafe { space.add_class(index, new_class)? }; - new_class - } - }; - - read_methods(stream, class, constants)?; - Ok(()) -} - -fn read_method( - stream: &mut R, - class: ClassPointer, - constants: &Chunk, -) -> Result<(), String> { - let index = read_u16(stream)?; - let hash = read_u32(stream)?; - let registers = read_u16(stream)?; - let instructions = read_instructions(stream, constants)?; - let locations = read_location_table(stream, constants)?; - let jump_tables = read_jump_tables(stream)?; - let method = - Method::alloc(hash, registers, instructions, locations, jump_tables); - - unsafe { - class.set_method(MethodIndex::new(index), method); - } - - Ok(()) -} - -fn read_location_table( - stream: &mut R, - constants: &Chunk, -) -> Result { - let mut table = LocationTable::new(); - - for _ in 0..read_u16(stream)? { - let index = read_u32(stream)?; - let line = read_u16(stream)?; - let file = read_constant_index(stream, constants)?; - let name = read_constant_index(stream, constants)?; - - table.add_entry(index, line, file, name); - } - - Ok(table) -} - -fn read_jump_tables( - stream: &mut R, -) -> Result>, String> { - let num_tables = read_u16(stream)? as usize; - let mut tables = Vec::with_capacity(num_tables); - - for _ in 0..num_tables { - let num_entries = read_u16(stream)? as usize; - let mut table = Vec::with_capacity(num_entries); - - for _ in 0..num_entries { - table.push(read_u32(stream)? as usize); - } - - tables.push(table); - } - - Ok(tables) -} - -fn read_methods( - stream: &mut R, - class: ClassPointer, - constants: &Chunk, -) -> Result<(), String> { - let amount = read_u16(stream)? as usize; - - for _ in 0..amount { - read_method(stream, class, constants)?; - } - - Ok(()) -} - -fn read_constants( - space: &PermanentSpace, - stream: &mut R, -) -> Result, String> { - let amount = read_u32(stream)? as usize; - let mut buff = Chunk::new(amount); - - for _ in 0..amount { - let index = read_u32(stream)? as usize; - - unsafe { - buff.set(index, read_constant(space, stream)?); - } - } - - Ok(buff) -} - -fn read_constant_index( - stream: &mut R, - constants: &Chunk, -) -> Result { - let index = read_u32(stream)? as usize; - - if index >= constants.len() { - return Err(format!( - "The constant index {} is out of bounds (number of constants: {})", - index, - constants.len() - )); - } - - Ok(unsafe { *constants.get(index) }) -} - -fn read_constant( - space: &PermanentSpace, - stream: &mut R, -) -> Result { - let const_type = read_u8(stream)?; - let value = match const_type { - CONST_INTEGER => space.allocate_int(read_i64(stream)?), - CONST_FLOAT => space.allocate_float(read_f64(stream)?), - CONST_STRING => space.allocate_string(read_string(stream)?), - CONST_ARRAY => { - space.allocate_array(read_constant_array(space, stream)?) - } - _ => { - return Err(format!("The constant type {} is invalid", const_type)) - } - }; - - Ok(value) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::indexes::{ClassIndex, MethodIndex}; - use crate::mem::{Float, Int, String as InkoString}; - use crate::test::OwnedClass; - use bytecode::Opcode; - use std::u64; - - fn pack_signature(buffer: &mut Vec) { - buffer.extend_from_slice(&SIGNATURE_BYTES); - } - - fn pack_u8(buffer: &mut Vec, value: u8) { - buffer.push(value); - } - - fn pack_u16(buffer: &mut Vec, value: u16) { - let num = u16::to_le(value); - let bytes = num.to_ne_bytes(); - - buffer.extend_from_slice(&bytes); - } - - fn pack_u32(buffer: &mut Vec, value: u32) { - let num = u32::to_le(value); - let bytes = num.to_ne_bytes(); - - buffer.extend_from_slice(&bytes); - } - - fn pack_u64(buffer: &mut Vec, value: u64) { - let num = u64::to_le(value); - let bytes = num.to_ne_bytes(); - - buffer.extend_from_slice(&bytes); - } - - fn pack_i64(buffer: &mut Vec, value: i64) { - let num = i64::to_le(value); - let bytes = num.to_ne_bytes(); - - buffer.extend_from_slice(&bytes); - } - - fn pack_f64(buffer: &mut Vec, value: f64) { - pack_u64(buffer, value.to_bits()); - } - - fn pack_string(buffer: &mut Vec, string: &str) { - pack_u32(buffer, string.len() as u32); - - buffer.extend_from_slice(string.as_bytes()); - } - - fn pack_version(buffer: &mut Vec) { - buffer.push(VERSION); - } - - macro_rules! reader { - ($buff: expr) => { - &mut $buff.as_slice() - }; - } - - #[test] - fn test_load_empty() { - let config = Config::new(); - let buffer = Vec::new(); - let output = - Image::load(config, &mut BufReader::new(buffer.as_slice())); - - assert!(output.is_err()); - } - - #[test] - fn test_load_invalid_signature() { - let config = Config::new(); - let mut buffer = Vec::new(); - - pack_string(&mut buffer, "cats"); - - let output = - Image::load(config, &mut BufReader::new(buffer.as_slice())); - - assert!(output.is_err()); - } - - #[test] - fn test_load_invalid_version() { - let config = Config::new(); - let mut buffer = Vec::new(); - - pack_signature(&mut buffer); - - buffer.push(VERSION + 1); - - let output = - Image::load(config, &mut BufReader::new(buffer.as_slice())); - - assert!(output.is_err()); - } - - #[test] - fn test_load_valid() { - let config = Config::new(); - let mut image = Vec::new(); - let mut chunk = Vec::new(); - - pack_u32(&mut chunk, 0); // module index - - // The module's constants - pack_u32(&mut chunk, 3); - - pack_u32(&mut chunk, 0); - pack_u8(&mut chunk, CONST_STRING); - pack_string(&mut chunk, "new_counter"); - - pack_u32(&mut chunk, 1); - pack_u8(&mut chunk, CONST_STRING); - pack_string(&mut chunk, "main.inko"); - - pack_u32(&mut chunk, 2); - pack_u8(&mut chunk, CONST_STRING); - pack_string(&mut chunk, "add"); - - // The (global) index of the module's class. - pack_u32(&mut chunk, 8); - - // Classes defined in the module - pack_u16(&mut chunk, 2); // class count - - pack_u32(&mut chunk, 8); // index - pack_u8(&mut chunk, 0); - pack_string(&mut chunk, "main"); - pack_u8(&mut chunk, 0); - pack_u16(&mut chunk, 1); // Method slot count - - // The methods - pack_u16(&mut chunk, 1); - pack_u16(&mut chunk, 0); - pack_u32(&mut chunk, 456); - pack_u16(&mut chunk, 0); - - // The method instructions - pack_u32(&mut chunk, 1); - pack_u8(&mut chunk, 93); - pack_u16(&mut chunk, 2); - - // The location table - pack_u16(&mut chunk, 0); - - // The jump tables - pack_u16(&mut chunk, 0); - - pack_u32(&mut chunk, 9); // index - pack_u8(&mut chunk, 0); - pack_string(&mut chunk, "Counter"); - pack_u8(&mut chunk, 1); - pack_u16(&mut chunk, 1); // Method slot count - - // The methods of the class - pack_u16(&mut chunk, 1); - pack_u16(&mut chunk, 0); - pack_u32(&mut chunk, 123); - pack_u16(&mut chunk, 2); - - // The method instructions - pack_u32(&mut chunk, 1); - pack_u8(&mut chunk, 93); - pack_u16(&mut chunk, 2); - - // The location table - pack_u16(&mut chunk, 0); - - // The jump tables - pack_u16(&mut chunk, 0); - - // Image header - pack_signature(&mut image); - pack_version(&mut image); - - pack_u32(&mut image, 1); // Number of modules - pack_u32(&mut image, 2); // Number of classes - - // Built-in method counts - pack_u16(&mut image, 1); // Int - pack_u16(&mut image, 4); // Float - pack_u16(&mut image, 5); // String - pack_u16(&mut image, 6); // Array - pack_u16(&mut image, 9); // Bool - pack_u16(&mut image, 10); // NilType - pack_u16(&mut image, 11); // ByteArray - pack_u16(&mut image, 13); // Future - - // Entry class and method - pack_u32(&mut image, 0); - pack_u16(&mut image, 42); - - // The number of bytes for this module. - pack_u64(&mut image, chunk.len() as u64); - image.append(&mut chunk); - - let image = - Image::load(config, &mut BufReader::new(image.as_slice())).unwrap(); - - let entry_method: u16 = image.entry_method.into(); - let perm = &image.permanent_space; - - assert_eq!(perm.int_class().method_slots, 1); - assert_eq!(perm.float_class().method_slots, 4); - assert_eq!(perm.string_class().method_slots, 5); - assert_eq!(perm.array_class().method_slots, 6); - assert_eq!(perm.boolean_class().method_slots, 9); - assert_eq!(perm.nil_class().method_slots, 10); - assert_eq!(perm.byte_array_class().method_slots, 11); - assert_eq!(perm.future_class().method_slots, 13); - - assert!( - image.entry_class == unsafe { perm.get_class(ClassIndex::new(0)) } - ); - assert_eq!(entry_method, 42); - - let module_class = unsafe { perm.get_class(ClassIndex::new(8)) }; - let counter_class = unsafe { perm.get_class(ClassIndex::new(9)) }; - - assert_eq!(&module_class.name, &"main"); - assert_eq!(module_class.method_slots, 1); - - assert_eq!(&counter_class.name, &"Counter"); - assert_eq!(counter_class.method_slots, 1); - - let add_meth = unsafe { counter_class.get_method(MethodIndex::new(0)) }; - let new_meth = unsafe { module_class.get_method(MethodIndex::new(0)) }; - - assert_eq!(add_meth.hash, 123); - assert_eq!(add_meth.registers, 2); - assert_eq!(add_meth.instructions[0].opcode, Opcode::Return); - assert_eq!(add_meth.instructions[0].arg(0), 2); - - assert_eq!(new_meth.hash, 456); - assert_eq!(new_meth.registers, 0); - assert_eq!(new_meth.instructions[0].opcode, Opcode::Return); - assert_eq!(new_meth.instructions[0].arg(0), 2); - } - - #[test] - fn test_read_string() { - let mut buffer = Vec::new(); - - pack_string(&mut buffer, "inko"); - - let output = read_string(reader!(buffer)).unwrap(); - - assert_eq!(output, "inko".to_string()); - } - - #[test] - fn test_read_string_too_large() { - let mut buffer = Vec::new(); - - pack_u32(&mut buffer, u32::MAX); - - let output = read_string(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_string_longer_than_size() { - let mut buffer = Vec::new(); - - pack_u32(&mut buffer, 2); - pack_signature(&mut buffer); - - let output = read_string(reader!(buffer)).unwrap(); - - assert_eq!(output, "in".to_string()); - } - - #[test] - fn test_read_string_invalid_utf8() { - let mut buffer = Vec::new(); - - pack_u32(&mut buffer, 4); - pack_u8(&mut buffer, 0); - pack_u8(&mut buffer, 159); - pack_u8(&mut buffer, 146); - pack_u8(&mut buffer, 150); - - let output = read_string(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_string_empty() { - let buffer = Vec::new(); - let output = read_string(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_u8() { - let mut buffer = Vec::new(); - - pack_u8(&mut buffer, 2); - - let output = read_u8(reader!(buffer)).unwrap(); - - assert_eq!(output, 2); - } - - #[test] - fn test_read_u8_empty() { - let buffer = Vec::new(); - let output = read_u8(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_u16() { - let mut buffer = Vec::new(); - - pack_u16(&mut buffer, 2); - - let output = read_u16(reader!(buffer)).unwrap(); - - assert_eq!(output, 2); - } - - #[test] - fn test_read_u16_empty() { - let buffer = Vec::new(); - let output = read_u16(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_i64() { - let mut buffer = Vec::new(); - - pack_i64(&mut buffer, 2); - - let output = read_i64(reader!(buffer)).unwrap(); - - assert_eq!(output, 2); - } - - #[test] - fn test_read_i64_empty() { - let buffer = Vec::new(); - let output = read_i64(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_f64() { - let mut buffer = Vec::new(); - - pack_f64(&mut buffer, 2.123456); - - let output = read_f64(reader!(buffer)).unwrap(); - - assert!((2.123456 - output).abs() < 0.00001); - } - - #[test] - fn test_read_f64_empty() { - let buffer = Vec::new(); - let output = read_f64(reader!(buffer)); - - assert!(output.is_err()); - } - - #[test] - fn test_read_instruction() { - let mut buffer = Vec::new(); - let mut constants = Chunk::new(2); - let const1 = Pointer::int(10); - let const2 = Pointer::int(20); - - unsafe { - constants.set(0, const1); - constants.set(1, const2); - } - - let u32_bytes = u32::to_le_bytes(1); - let arg1 = u16::from_le_bytes([u32_bytes[0], u32_bytes[1]]); - let arg2 = u16::from_le_bytes([u32_bytes[2], u32_bytes[3]]); - - pack_u8(&mut buffer, 49); - pack_u16(&mut buffer, 14); - pack_u16(&mut buffer, arg1); - pack_u16(&mut buffer, arg2); - - let ins = read_instruction(reader!(buffer), &constants).unwrap(); - - assert_eq!(ins.opcode, Opcode::GetConstant); - assert_eq!(ins.arg(0), 14); - assert_eq!(ins.u64_arg(1, 2, 3, 4), const2.as_ptr() as u64); - } - - #[test] - fn test_read_instructions() { - let mut buffer = Vec::new(); - let constants = Chunk::new(0); - - pack_u32(&mut buffer, 1); - pack_u8(&mut buffer, 0); - pack_u16(&mut buffer, 0); - pack_u16(&mut buffer, 2); - pack_u16(&mut buffer, 4); - - let ins = read_instructions(reader!(buffer), &constants).unwrap(); - - assert_eq!(ins.len(), 1); - assert_eq!(ins[0].opcode, Opcode::Allocate); - assert_eq!(ins[0].arg(0), 0); - assert_eq!(ins[0].arg(1), 2); - assert_eq!(ins[0].arg(2), 4); - } - - #[test] - fn test_read_class() { - let class_index = 8; - let mut buffer = Vec::new(); - let constants = Chunk::new(0); - let perm = PermanentSpace::new(1, 1, MethodCounts::default()); - - pack_u32(&mut buffer, class_index); - pack_u8(&mut buffer, 0); - pack_string(&mut buffer, "A"); - pack_u8(&mut buffer, 1); - pack_u16(&mut buffer, 0); - pack_u16(&mut buffer, 0); - - assert!(read_class(&perm, reader!(buffer), &constants).is_ok()); - - let class = unsafe { perm.get_class(ClassIndex::new(class_index)) }; - - assert_eq!(&class.name, &"A"); - } - - #[test] - fn test_read_class_with_process_class() { - let class_index = 8; - let mut buffer = Vec::new(); - let constants = Chunk::new(0); - let perm = PermanentSpace::new(1, 1, MethodCounts::default()); - - pack_u32(&mut buffer, class_index); - pack_u8(&mut buffer, 1); - pack_string(&mut buffer, "A"); - pack_u8(&mut buffer, 1); - pack_u16(&mut buffer, 0); - pack_u16(&mut buffer, 0); - - assert!(read_class(&perm, reader!(buffer), &constants).is_ok()); - - let class = unsafe { perm.get_class(ClassIndex::new(class_index)) }; - - assert_eq!(&class.name, &"A"); - } - - #[test] - fn test_read_class_with_builtin_class() { - let mut buffer = Vec::new(); - let counts = MethodCounts { int_class: 1, ..Default::default() }; - let perm = PermanentSpace::new(0, 0, counts); - let mut constants = Chunk::new(1); - - unsafe { - constants.set(0, perm.allocate_string("add".to_string())); - } - - pack_u32(&mut buffer, 0); - pack_u8(&mut buffer, 2); - pack_string(&mut buffer, "A"); - pack_u8(&mut buffer, 0); - pack_u16(&mut buffer, 1); - - pack_u16(&mut buffer, 0); - pack_u32(&mut buffer, 123); - pack_u16(&mut buffer, 0); - - // The instructions - pack_u32(&mut buffer, 0); - - // The location table - pack_u16(&mut buffer, 0); - - // The jump tables - pack_u16(&mut buffer, 0); - - assert!(read_class(&perm, reader!(buffer), &constants).is_ok()); - assert_eq!(perm.int_class().method_slots, 1); - } - - #[test] - fn test_read_method() { - let mut buffer = Vec::new(); - let mut constants = Chunk::new(2); - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - let class = OwnedClass::new(Class::alloc("A".to_string(), 1, 0)); - - unsafe { constants.set(0, perm.allocate_string("add".to_string())) }; - unsafe { - constants.set(1, perm.allocate_string("test.inko".to_string())) - }; - - pack_u16(&mut buffer, 0); - pack_u32(&mut buffer, 123); - pack_u16(&mut buffer, 3); - - // The instructions - pack_u32(&mut buffer, 0); - - // The location table - pack_u16(&mut buffer, 1); // entries - pack_u32(&mut buffer, 0); - pack_u16(&mut buffer, 14); - pack_u32(&mut buffer, 1); - pack_u32(&mut buffer, 0); - - // The jump tables - pack_u16(&mut buffer, 1); - pack_u16(&mut buffer, 2); - pack_u32(&mut buffer, 4); - pack_u32(&mut buffer, 8); - - read_method(reader!(buffer), *class, &constants).unwrap(); - - let method = unsafe { class.get_method(MethodIndex::new(0)) }; - - assert_eq!(method.hash, 123); - assert_eq!(method.registers, 3); - - let location = method.locations.get(0).unwrap(); - - assert_eq!(unsafe { InkoString::read(&location.name) }, "add"); - assert_eq!(unsafe { InkoString::read(&location.file) }, "test.inko"); - assert_eq!(location.line, Pointer::int(14)); - assert_eq!(method.jump_tables, vec![vec![4, 8]]); - } - - #[test] - fn test_read_methods() { - let mut buffer = Vec::new(); - let mut constants = Chunk::new(1); - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - let class = OwnedClass::new(Class::alloc("A".to_string(), 2, 1)); - - unsafe { constants.set(0, perm.allocate_string("add".to_string())) }; - - pack_u16(&mut buffer, 1); // The number of methods to read - - pack_u16(&mut buffer, 1); - pack_u32(&mut buffer, 123); - pack_u16(&mut buffer, 1); - - // The instructions - pack_u32(&mut buffer, 0); - - // The location table - pack_u16(&mut buffer, 0); - - // The jump tables - pack_u16(&mut buffer, 0); - - assert!(read_methods(reader!(buffer), *class, &constants).is_ok()); - } - - #[test] - fn test_read_constants() { - let mut buffer = Vec::new(); - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - - pack_u32(&mut buffer, 3); - - pack_u32(&mut buffer, 0); - pack_u8(&mut buffer, CONST_INTEGER); - pack_i64(&mut buffer, -2); - - pack_u32(&mut buffer, 1); - pack_u8(&mut buffer, CONST_FLOAT); - pack_f64(&mut buffer, 2.0); - - pack_u32(&mut buffer, 2); - pack_u8(&mut buffer, CONST_STRING); - pack_string(&mut buffer, "inko"); - - let output = read_constants(&perm, reader!(buffer)).unwrap(); - - assert_eq!(output.len(), 3); - - unsafe { - assert_eq!(Int::read(*output.get(0)), -2); - assert_eq!(Float::read(*output.get(1)), 2.0); - assert_eq!(InkoString::read(output.get(2)), "inko"); - } - } - - #[test] - fn test_read_constant_invalid() { - let mut buffer = Vec::new(); - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - - pack_u8(&mut buffer, 255); - - assert!(read_constant(&perm, reader!(buffer)).is_err()); - } -} diff --git a/vm/src/indexes.rs b/vm/src/indexes.rs deleted file mode 100644 index a30232bce..000000000 --- a/vm/src/indexes.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Types for indexing various tables such as method tables. - -/// An index used for accessing classes. -#[repr(transparent)] -#[derive(Copy, Clone)] -pub(crate) struct ClassIndex(u32); - -impl ClassIndex { - pub(crate) fn new(index: u32) -> Self { - Self(index) - } -} - -impl From for u32 { - fn from(index: ClassIndex) -> u32 { - index.0 - } -} - -impl From for usize { - fn from(index: ClassIndex) -> usize { - index.0 as usize - } -} - -/// An index used for accessing methods. -#[repr(transparent)] -#[derive(Copy, Clone)] -pub(crate) struct MethodIndex(u16); - -impl MethodIndex { - pub(crate) fn new(index: u16) -> Self { - Self(index) - } -} - -impl From for u16 { - fn from(index: MethodIndex) -> u16 { - index.0 - } -} - -impl From for usize { - fn from(index: MethodIndex) -> usize { - index.0 as usize - } -} - -/// An index used for accessing fields. -#[repr(transparent)] -#[derive(Copy, Clone)] -pub(crate) struct FieldIndex(u8); - -impl FieldIndex { - pub(crate) fn new(index: u8) -> Self { - Self(index) - } -} - -impl From for u8 { - fn from(index: FieldIndex) -> u8 { - index.0 - } -} - -impl From for usize { - fn from(index: FieldIndex) -> usize { - index.0 as usize - } -} diff --git a/vm/src/instructions/array.rs b/vm/src/instructions/array.rs deleted file mode 100644 index bab4ef803..000000000 --- a/vm/src/instructions/array.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! VM functions for working with Inko arrays. -use crate::mem::Pointer; -use crate::mem::{Array, Int}; -use crate::process::TaskPointer; -use crate::state::State; - -#[inline(always)] -pub(crate) fn allocate(state: &State, mut task: TaskPointer) -> Pointer { - // We use this approach so our array's capacity isn't dictated by the - // stack's capacity. - let mut values = Vec::with_capacity(task.stack.len()); - - values.append(&mut task.stack); - Array::alloc(state.permanent_space.array_class(), values) -} - -#[inline(always)] -pub(crate) fn push(array_ptr: Pointer, value_ptr: Pointer) { - let array = unsafe { array_ptr.get_mut::() }; - let vector = array.value_mut(); - - vector.push(value_ptr); -} - -#[inline(always)] -pub(crate) fn pop(array_ptr: Pointer) -> Pointer { - let array = unsafe { array_ptr.get_mut::() }; - let vector = array.value_mut(); - - vector.pop().unwrap_or_else(Pointer::undefined_singleton) -} - -#[inline(always)] -pub(crate) fn set( - array_ptr: Pointer, - index_ptr: Pointer, - value_ptr: Pointer, -) -> Pointer { - unsafe { - let array = array_ptr.get_mut::(); - let vector = array.value_mut(); - let index = Int::read(index_ptr) as usize; - let index_ref = vector.get_unchecked_mut(index); - let old_value = *index_ref; - - *index_ref = value_ptr; - old_value - } -} - -#[inline(always)] -pub(crate) fn get(array_ptr: Pointer, index_ptr: Pointer) -> Pointer { - unsafe { - let array = array_ptr.get::(); - let vector = array.value(); - let index = Int::read(index_ptr) as usize; - - *vector.get_unchecked(index) - } -} - -#[inline(always)] -pub(crate) fn remove(array_ptr: Pointer, index_ptr: Pointer) -> Pointer { - let array = unsafe { array_ptr.get_mut::() }; - let vector = array.value_mut(); - let index = unsafe { Int::read(index_ptr) as usize }; - - vector.remove(index) -} - -#[inline(always)] -pub(crate) fn length(state: &State, pointer: Pointer) -> Pointer { - let array = unsafe { pointer.get::() }; - let vector = array.value(); - - Int::alloc(state.permanent_space.int_class(), vector.len() as i64) -} - -#[inline(always)] -pub(crate) fn clear(array_ptr: Pointer) { - unsafe { array_ptr.get_mut::() }.value_mut().clear(); -} - -#[inline(always)] -pub(crate) fn drop(array_ptr: Pointer) { - unsafe { - Array::drop(array_ptr); - } -} diff --git a/vm/src/instructions/builtin_functions.rs b/vm/src/instructions/builtin_functions.rs deleted file mode 100644 index bb2b9794c..000000000 --- a/vm/src/instructions/builtin_functions.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! VM functions for handling builtin functions. -use crate::mem::Pointer; -use crate::process::{ProcessPointer, TaskPointer}; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; - -#[inline(always)] -pub(crate) fn call( - state: &State, - thread: &mut Thread, - process: ProcessPointer, - task: TaskPointer, - func_index: u16, -) -> Result { - let func = state.builtin_functions.get(func_index); - - // We keep the arguments as-is, as the function may suspend the current - // process and require retrying the current instruction. - let args = &task.stack; - - func(state, thread, process, args) -} diff --git a/vm/src/instructions/byte_array.rs b/vm/src/instructions/byte_array.rs deleted file mode 100644 index 6b4f72793..000000000 --- a/vm/src/instructions/byte_array.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! VM functions for working with Inko byte arrays. -use crate::mem::Pointer; -use crate::mem::{ByteArray, Int}; -use crate::state::State; - -#[inline(always)] -pub(crate) fn allocate(state: &State) -> Pointer { - ByteArray::alloc(state.permanent_space.byte_array_class(), Vec::new()) -} - -#[inline(always)] -pub(crate) fn push(array_ptr: Pointer, value_ptr: Pointer) { - let array = unsafe { array_ptr.get_mut::() }; - let vector = array.value_mut(); - let value = unsafe { Int::read(value_ptr) } as u8; - - vector.push(value); -} - -#[inline(always)] -pub(crate) fn pop(array_ptr: Pointer) -> Pointer { - let array = unsafe { array_ptr.get_mut::() }; - let vector = array.value_mut(); - - if let Some(value) = vector.pop() { - Pointer::int(value as i64) - } else { - Pointer::undefined_singleton() - } -} - -#[inline(always)] -pub(crate) fn set( - _: &State, - array_ptr: Pointer, - index_ptr: Pointer, - value_ptr: Pointer, -) -> Pointer { - unsafe { - let byte_array = array_ptr.get_mut::(); - let bytes = byte_array.value_mut(); - let index = Int::read(index_ptr) as usize; - let index_ref = bytes.get_unchecked_mut(index); - let old_value = *index_ref; - - *index_ref = Int::read(value_ptr) as u8; - Pointer::int(old_value as i64) - } -} - -#[inline(always)] -pub(crate) fn get(array_ptr: Pointer, index_ptr: Pointer) -> Pointer { - unsafe { - let byte_array = array_ptr.get::(); - let bytes = byte_array.value(); - let index = Int::read(index_ptr) as usize; - - Pointer::int(*bytes.get_unchecked(index) as i64) - } -} - -#[inline(always)] -pub(crate) fn remove(array_ptr: Pointer, index_ptr: Pointer) -> Pointer { - let byte_array = unsafe { array_ptr.get_mut::() }; - let bytes = byte_array.value_mut(); - let index = unsafe { Int::read(index_ptr) as usize }; - - Pointer::int(bytes.remove(index) as i64) -} - -#[inline(always)] -pub(crate) fn length(state: &State, array_ptr: Pointer) -> Pointer { - let byte_array = unsafe { array_ptr.get::() }; - let bytes = byte_array.value(); - - Int::alloc(state.permanent_space.int_class(), bytes.len() as i64) -} - -#[inline(always)] -pub(crate) fn equals( - compare_ptr: Pointer, - compare_with_ptr: Pointer, -) -> Pointer { - let compare = unsafe { compare_ptr.get::() }; - let compare_with = unsafe { compare_with_ptr.get::() }; - - if compare.value() == compare_with.value() { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn clear(pointer: Pointer) { - let bytes = unsafe { pointer.get_mut::() }; - - bytes.value_mut().clear(); -} - -#[inline(always)] -pub(crate) fn clone(state: &State, pointer: Pointer) -> Pointer { - let bytes = unsafe { pointer.get_mut::() }.value().clone(); - - ByteArray::alloc(state.permanent_space.byte_array_class(), bytes) -} - -#[inline(always)] -pub(crate) fn drop(pointer: Pointer) { - unsafe { - ByteArray::drop(pointer); - } -} diff --git a/vm/src/instructions/float.rs b/vm/src/instructions/float.rs deleted file mode 100644 index 8d8d8c0d4..000000000 --- a/vm/src/instructions/float.rs +++ /dev/null @@ -1,246 +0,0 @@ -//! VM functions for working with Inko floats. -use crate::mem::Pointer; -use crate::mem::{Float, Int, String as InkoString}; -use crate::state::State; - -/// The maximum difference between two floats for them to be considered equal, -/// as expressed in "Units in the Last Place" (ULP). -const ULP_DIFF: i64 = 1; - -#[inline(always)] -pub(crate) fn add( - state: &State, - left_ptr: Pointer, - right_ptr: Pointer, -) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - let value = left + right; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn mul( - state: &State, - left_ptr: Pointer, - right_ptr: Pointer, -) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - let value = left * right; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn div( - state: &State, - left_ptr: Pointer, - right_ptr: Pointer, -) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - let value = left / right; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn sub( - state: &State, - left_ptr: Pointer, - right_ptr: Pointer, -) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - let value = left - right; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn modulo(state: &State, left: Pointer, right: Pointer) -> Pointer { - let lhs = unsafe { Float::read(left) }; - let rhs = unsafe { Float::read(right) }; - let value = ((lhs % rhs) + rhs) % rhs; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn eq(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - // For float equality we use ULPs. See - // https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ - // for more details. - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - - if left == right { - // Handle cases such as `-0.0 == 0.0`. - return Pointer::true_singleton(); - } - - if left.is_sign_positive() != right.is_sign_positive() { - return Pointer::false_singleton(); - } - - if left.is_nan() || right.is_nan() { - return Pointer::false_singleton(); - } - - let left_bits = left.to_bits() as i64; - let right_bits = right.to_bits() as i64; - let diff = left_bits.wrapping_sub(right_bits); - - if (-ULP_DIFF..=ULP_DIFF).contains(&diff) { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn lt(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - - if left < right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn gt(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - - if left > right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn ge(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - - if left >= right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn le(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - let left = unsafe { Float::read(left_ptr) }; - let right = unsafe { Float::read(right_ptr) }; - - if left <= right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn clone(state: &State, ptr: Pointer) -> Pointer { - if ptr.is_permanent() { - return ptr; - } - - let value = unsafe { Float::read(ptr) }; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn ceil(state: &State, pointer: Pointer) -> Pointer { - let float = unsafe { Float::read(pointer) }; - let value = float.ceil(); - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn floor(state: &State, pointer: Pointer) -> Pointer { - let float = unsafe { Float::read(pointer) }; - let value = float.floor(); - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn round( - state: &State, - pointer: Pointer, - precision_ptr: Pointer, -) -> Pointer { - let float = unsafe { Float::read(pointer) }; - let precision = unsafe { Int::read(precision_ptr) }; - let result = if precision == 0 { - float.round() - } else if precision <= i64::from(u32::MAX) { - let power = 10.0_f64.powi(precision as i32); - let multiplied = float * power; - - // Certain very large numbers (e.g. f64::MAX) would produce Infinity - // when multiplied with the power. In this case we just return the input - // float directly. - if multiplied.is_finite() { - multiplied.round() / power - } else { - float - } - } else { - float - }; - - Float::alloc(state.permanent_space.float_class(), result) -} - -#[inline(always)] -pub(crate) fn to_int(state: &State, pointer: Pointer) -> Pointer { - let float = unsafe { Float::read(pointer) }; - - Int::alloc(state.permanent_space.int_class(), float as i64) -} - -#[inline(always)] -pub(crate) fn to_string(state: &State, pointer: Pointer) -> Pointer { - let value = unsafe { Float::read(pointer) }; - let string = if value.is_infinite() && value.is_sign_positive() { - "Infinity".to_string() - } else if value.is_infinite() { - "-Infinity".to_string() - } else if value.is_nan() { - "NaN".to_string() - } else { - format!("{:?}", value) - }; - - InkoString::alloc(state.permanent_space.string_class(), string) -} - -#[inline(always)] -pub(crate) fn is_nan(pointer: Pointer) -> Pointer { - if unsafe { Float::read(pointer) }.is_nan() { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn is_inf(pointer: Pointer) -> Pointer { - if unsafe { Float::read(pointer) }.is_infinite() { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} diff --git a/vm/src/instructions/future.rs b/vm/src/instructions/future.rs deleted file mode 100644 index 5b8b5d953..000000000 --- a/vm/src/instructions/future.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! VM functions for working with Inko futures. -use crate::mem::{Array, Int, Pointer}; -use crate::process::{Future, FutureResult, ProcessPointer}; -use crate::scheduler::timeouts::Timeout; -use crate::state::State; -use std::time::Duration; - -#[inline(always)] -pub(crate) fn get( - _: &State, - mut process: ProcessPointer, - future_ptr: Pointer, -) -> bool { - let fut = unsafe { future_ptr.get::() }; - - match fut.get(process, None) { - FutureResult::Returned(val) => { - process.set_return_value(val); - true - } - FutureResult::Thrown(val) => { - process.set_throw_value(val); - true - } - FutureResult::None => false, - } -} - -#[inline(always)] -pub(crate) fn get_for( - state: &State, - mut process: ProcessPointer, - future_ptr: Pointer, - time_ptr: Pointer, -) -> bool { - if process.timeout_expired() { - state.timeout_worker.increase_expired_timeouts(); - process.set_return_value(Pointer::undefined_singleton()); - - return true; - } - - let nanos = unsafe { Int::read_u64(time_ptr) }; - let timeout = Timeout::with_rc(Duration::from_nanos(nanos)); - let fut_ref = unsafe { future_ptr.get::() }; - - match fut_ref.get(process, Some(timeout.clone())) { - FutureResult::Returned(val) => { - process.set_return_value(val); - true - } - FutureResult::Thrown(val) => { - process.set_throw_value(val); - true - } - FutureResult::None => { - state.timeout_worker.suspend(process, timeout); - false - } - } -} - -#[inline(always)] -pub(crate) fn drop(pointer: Pointer) -> Pointer { - unsafe { - let result = pointer.get::().disconnect(); - - Future::drop(pointer); - result - } -} - -#[inline(always)] -pub(crate) fn poll( - state: &State, - process: ProcessPointer, - pending_ptr: Pointer, -) -> Option { - let pending = unsafe { pending_ptr.get_mut::() }; - let mut ready = Vec::new(); - - if pending.value().is_empty() { - // If the input is already empty we can just skip all the work below. - // This way polling an empty Array won't result in the process hanging - // forever. - return Some(Array::alloc(state.permanent_space.array_class(), ready)); - } - - let mut locks: Vec<_> = pending - .value() - .iter() - .map(|p| (*p, unsafe { p.get::() }.lock())) - .collect(); - - pending.value_mut().clear(); - - for (p, lock) in &mut locks { - // We _must_ ensure the consumer is cleared. If we don't, the following - // could happen: - // - // 1. We return a bunch of ready futures, but a few are not ready yet. - // 2. We wait for an unrelated future. - // 3. One of the futures is now ready and reschedules us. - // 4. All hell breaks loose. - lock.consumer = None; - - let target = - if lock.has_result() { &mut ready } else { pending.value_mut() }; - - target.push(*p); - } - - if ready.is_empty() { - process.state().waiting_for_future(None); - - for (_, lock) in &mut locks { - lock.consumer = Some(process); - } - - None - } else { - Some(Array::alloc(state.permanent_space.array_class(), ready)) - } -} diff --git a/vm/src/instructions/general.rs b/vm/src/instructions/general.rs deleted file mode 100644 index 6ad3dece0..000000000 --- a/vm/src/instructions/general.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! VM functions for which no better category/module exists. -use crate::execution_context::ExecutionContext; -use crate::indexes::{ClassIndex, FieldIndex, MethodIndex}; -use crate::mem::{Class, Header, Int, Object, Pointer}; -use crate::process::TaskPointer; -use crate::state::State; -use bytecode::{REF_ATOMIC, REF_OWNED, REF_PERMANENT, REF_REF}; - -#[inline(always)] -pub(crate) fn allocate(state: &State, idx: u32) -> Pointer { - let index = ClassIndex::new(idx); - let class = unsafe { state.permanent_space.get_class(index) }; - - Object::alloc(class) -} - -#[inline(always)] -pub(crate) fn get_field(receiver: Pointer, index: u16) -> Pointer { - unsafe { receiver.get::().get_field(FieldIndex::new(index as u8)) } -} - -#[inline(always)] -pub(crate) fn set_field(receiver: Pointer, index: u16, value: Pointer) { - unsafe { - receiver - .get_mut::() - .set_field(FieldIndex::new(index as u8), value); - } -} - -#[inline(always)] -pub(crate) fn equals(compare: Pointer, compare_with: Pointer) -> Pointer { - if compare == compare_with { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn call_virtual( - state: &State, - mut task: TaskPointer, - receiver: Pointer, - method_idx: u16, -) { - let class = Class::of(&state.permanent_space, receiver); - let method = unsafe { class.get_method(MethodIndex::new(method_idx)) }; - - task.push_context(ExecutionContext::new(method)); -} - -#[inline(always)] -pub(crate) fn call_static( - state: &State, - mut task: TaskPointer, - class_idx: u32, - method_idx: u16, -) { - let class = - unsafe { state.permanent_space.get_class(ClassIndex::new(class_idx)) }; - let method = unsafe { class.get_method(MethodIndex::new(method_idx)) }; - - task.push_context(ExecutionContext::new(method)); -} - -#[inline(always)] -pub(crate) fn call_dynamic( - state: &State, - mut task: TaskPointer, - receiver: Pointer, - hash: u32, -) { - let class = Class::of(&state.permanent_space, receiver); - let method = unsafe { class.get_hashed_method(hash) }; - - task.push_context(ExecutionContext::new(method)); -} - -#[inline(always)] -pub(crate) fn exit(state: &State, status_ptr: Pointer) -> Result<(), String> { - let status = unsafe { Int::read(status_ptr) as i32 }; - - state.set_exit_status(status); - state.terminate(); - Ok(()) -} - -#[inline(always)] -pub(crate) fn increment(pointer: Pointer) -> Pointer { - if !pointer.is_local_heap_object() { - return pointer; - } - - let header = unsafe { pointer.get_mut::
() }; - - if header.is_atomic() { - header.increment_atomic(); - pointer - } else { - header.increment(); - pointer.as_ref() - } -} - -#[inline(always)] -pub(crate) fn decrement(pointer: Pointer) { - if !pointer.is_local_heap_object() { - return; - } - - unsafe { pointer.get_mut::
() }.decrement(); -} - -#[inline(always)] -pub(crate) fn decrement_atomic(pointer: Pointer) -> Pointer { - if !pointer.is_local_heap_object() { - return Pointer::false_singleton(); - } - - let header = unsafe { pointer.get_mut::
() }; - - if header.decrement_atomic() { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn check_refs(pointer: Pointer) -> Result<(), String> { - if !pointer.is_local_heap_object() { - return Ok(()); - } - - let header = unsafe { pointer.get::
() }; - let refs = header.references(); - - if refs == 0 { - return Ok(()); - } - - Err(format!( - "Can't drop a value of type '{}' as it still has {} references", - &header.class.name, refs - )) -} - -#[inline(always)] -pub(crate) fn ref_kind(pointer: Pointer) -> Pointer { - if !pointer.is_local_heap_object() { - Pointer::int(REF_PERMANENT as i64) - } else if unsafe { pointer.get_mut::
().is_atomic() } { - Pointer::int(REF_ATOMIC as i64) - } else if pointer.is_ref() { - Pointer::int(REF_REF as i64) - } else { - Pointer::int(REF_OWNED as i64) - } -} - -#[inline(always)] -pub(crate) fn free(ptr: Pointer) { - if ptr.is_local_heap_object() { - unsafe { - ptr.free(); - } - } -} - -#[inline(always)] -pub(crate) fn is_undefined(pointer: Pointer) -> Pointer { - if pointer == Pointer::undefined_singleton() { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} diff --git a/vm/src/instructions/integer.rs b/vm/src/instructions/integer.rs deleted file mode 100644 index 86b0bbaae..000000000 --- a/vm/src/instructions/integer.rs +++ /dev/null @@ -1,326 +0,0 @@ -// VM functions for working with Inko integers. -use crate::mem::Pointer; -use crate::mem::{Float, Int, String as InkoString}; -use crate::numeric::Modulo; -use crate::state::State; -use std::ops::{BitAnd, BitOr, BitXor}; - -macro_rules! overflow_error { - ($left: expr, $right: expr) => {{ - return Err(format!( - "Integer overflow, left: {}, right: {}", - $left, $right - )); - }}; -} - -macro_rules! int_overflow_op { - ($kind: ident, $left: expr, $right: expr, $op: ident) => {{ - let left = unsafe { $kind::read($left) }; - let right = unsafe { $kind::read($right) }; - - if let Some(result) = left.$op(right) { - result - } else { - overflow_error!(left, right); - } - }}; -} - -macro_rules! int_op { - ($left: expr, $right: expr, $op: ident) => {{ - let left = unsafe { Int::read($left) }; - let right = unsafe { Int::read($right) }; - - left.$op(right) - }}; -} - -macro_rules! int_shift { - ($kind: ident, $left: expr, $right: expr, $op: ident) => {{ - let left = unsafe { $kind::read($left) }; - let right = unsafe { $kind::read($right) }; - - if let Some(result) = left.$op(right as u32) { - result - } else { - overflow_error!(left, right); - } - }}; -} - -macro_rules! int_bool { - ($kind: ident, $left: expr, $right: expr, $op: tt) => {{ - let left = unsafe { $kind::read($left) }; - let right = unsafe { $kind::read($right) }; - - if left $op right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } - }}; -} - -#[inline(always)] -pub(crate) fn add( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_overflow_op!(Int, left, right, checked_add); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn wrapping_add( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) }; - let value = lhs.wrapping_add(rhs); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn div( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - // This implements floored division, rather than rounding towards zero. This - // makes division work more natural when using negative numbers. - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) }; - - if rhs == 0 { - return Err(format!( - "Integer division failed, left: {}, right: {}", - lhs, rhs - )); - } - - // Taken from the upcoming div_floor() implementation in the standard - // library: https://github.com/rust-lang/rust/pull/88582. - let d = lhs / rhs; - let r = lhs % rhs; - let value = - if (r > 0 && rhs < 0) || (r < 0 && rhs > 0) { d - 1 } else { d }; - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn mul( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_overflow_op!(Int, left, right, checked_mul); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn wrapping_mul( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) }; - let value = lhs.wrapping_mul(rhs); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn sub( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_overflow_op!(Int, left, right, checked_sub); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn wrapping_sub( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) }; - let value = lhs.wrapping_sub(rhs); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn modulo( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_overflow_op!(Int, left, right, checked_modulo); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn and(state: &State, left: Pointer, right: Pointer) -> Pointer { - let value = int_op!(left, right, bitand); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn or(state: &State, left: Pointer, right: Pointer) -> Pointer { - let value = int_op!(left, right, bitor); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn xor(state: &State, left: Pointer, right: Pointer) -> Pointer { - let value = int_op!(left, right, bitxor); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn not(state: &State, ptr: Pointer) -> Pointer { - let value = unsafe { !Int::read(ptr) }; - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn rotate_left( - state: &State, - left: Pointer, - right: Pointer, -) -> Pointer { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) as u32 }; - let value = lhs.rotate_left(rhs); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn rotate_right( - state: &State, - left: Pointer, - right: Pointer, -) -> Pointer { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) as u32 }; - let value = lhs.rotate_right(rhs); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn shl( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_shift!(Int, left, right, checked_shl); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn shr( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let value = int_shift!(Int, left, right, checked_shr); - - Ok(Int::alloc(state.permanent_space.int_class(), value)) -} - -#[inline(always)] -pub(crate) fn unsigned_shr( - state: &State, - left: Pointer, - right: Pointer, -) -> Result { - let lhs = unsafe { Int::read(left) as u64 }; - let rhs = unsafe { Int::read(right) }; - let res = if let Some(result) = lhs.checked_shr(rhs as u32) { - result as i64 - } else { - overflow_error!(lhs, rhs) - }; - - Ok(Int::alloc(state.permanent_space.int_class(), res)) -} - -#[inline(always)] -pub(crate) fn pow(state: &State, left: Pointer, right: Pointer) -> Pointer { - let lhs = unsafe { Int::read(left) }; - let rhs = unsafe { Int::read(right) }; - let value = lhs.pow(rhs as u32); - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn lt(left: Pointer, right: Pointer) -> Pointer { - int_bool!(Int, left, right, <) -} - -#[inline(always)] -pub(crate) fn gt(left: Pointer, right: Pointer) -> Pointer { - int_bool!(Int, left, right, >) -} - -#[inline(always)] -pub(crate) fn eq(left: Pointer, right: Pointer) -> Pointer { - int_bool!(Int, left, right, ==) -} - -#[inline(always)] -pub(crate) fn ge(left: Pointer, right: Pointer) -> Pointer { - int_bool!(Int, left, right, >=) -} - -#[inline(always)] -pub(crate) fn le(left: Pointer, right: Pointer) -> Pointer { - int_bool!(Int, left, right, <=) -} - -#[inline(always)] -pub(crate) fn clone(state: &State, ptr: Pointer) -> Pointer { - if ptr.is_tagged_int() { - return ptr; - } - - let value = unsafe { Int::read(ptr) }; - - Int::alloc(state.permanent_space.int_class(), value) -} - -#[inline(always)] -pub(crate) fn to_float(state: &State, pointer: Pointer) -> Pointer { - let value = unsafe { Int::read(pointer) } as f64; - - Float::alloc(state.permanent_space.float_class(), value) -} - -#[inline(always)] -pub(crate) fn to_string(state: &State, pointer: Pointer) -> Pointer { - let value = unsafe { Int::read(pointer) }.to_string(); - - InkoString::alloc(state.permanent_space.string_class(), value) -} diff --git a/vm/src/instructions/mod.rs b/vm/src/instructions/mod.rs deleted file mode 100644 index 802f953f1..000000000 --- a/vm/src/instructions/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod array; -pub mod builtin_functions; -pub mod byte_array; -pub mod float; -pub mod future; -pub mod general; -pub mod integer; -pub mod process; -pub mod string; diff --git a/vm/src/instructions/process.rs b/vm/src/instructions/process.rs deleted file mode 100644 index f487315b9..000000000 --- a/vm/src/instructions/process.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! VM functions for working with Inko processes. -use crate::indexes::{ClassIndex, FieldIndex, MethodIndex}; -use crate::mem::{Int, Pointer}; -use crate::process::{ - Future, FutureState, Process, ProcessPointer, RescheduleRights, - TaskPointer, Write, WriteResult, -}; -use crate::scheduler::process::Thread; -use crate::scheduler::timeouts::Timeout; -use crate::state::State; -use std::time::Duration; - -const SEND_ERROR: &str = "Processes can't send messages to themselves, \ - as this could result in deadlocks"; - -#[inline(always)] -pub(crate) fn allocate(state: &State, class_idx: u32) -> Pointer { - let class_index = ClassIndex::new(class_idx); - let class = unsafe { state.permanent_space.get_class(class_index) }; - let process = Process::alloc(class); - - process.as_pointer() -} - -#[inline(always)] -pub(crate) fn send_message( - state: &State, - thread: &mut Thread, - mut task: TaskPointer, - sender: ProcessPointer, - receiver_ptr: Pointer, - method: u16, - wait: bool, -) -> Result { - let mut receiver = unsafe { ProcessPointer::from_pointer(receiver_ptr) }; - - if sender == receiver { - return Err(SEND_ERROR.to_string()); - } - - let args = task.take_arguments(); - - match receiver.send_message(MethodIndex::new(method), sender, args, wait) { - RescheduleRights::AcquiredWithTimeout => { - state.timeout_worker.increase_expired_timeouts(); - } - RescheduleRights::Acquired => {} - _ => return Ok(false), - } - - let switch = if wait { - // When awaiting the result immediately we want to keep latency as small - // as possible. To achieve this we reschedule the receiver (if allowed) - // onto the current worker with a high priority. - thread.schedule_priority(receiver); - true - } else { - thread.schedule(receiver); - false - }; - - Ok(switch) -} - -#[inline(always)] -pub(crate) fn send_async_message( - state: &State, - thread: &mut Thread, - sender: ProcessPointer, - mut task: TaskPointer, - receiver_ptr: Pointer, - method: u16, -) -> Result { - let mut receiver = unsafe { ProcessPointer::from_pointer(receiver_ptr) }; - - if sender == receiver { - return Err(SEND_ERROR.to_string()); - } - - let fut_state = FutureState::new(); - let fut = - Future::alloc(state.permanent_space.future_class(), fut_state.clone()); - let args = task.take_arguments(); - - match receiver.send_async_message(MethodIndex::new(method), fut_state, args) - { - RescheduleRights::AcquiredWithTimeout => { - state.timeout_worker.increase_expired_timeouts(); - thread.schedule(receiver); - } - RescheduleRights::Acquired => { - thread.schedule(receiver); - } - _ => {} - } - - Ok(fut) -} - -#[inline(always)] -pub(crate) fn suspend( - state: &State, - mut process: ProcessPointer, - time_ptr: Pointer, -) { - let nanos = unsafe { Int::read_u64(time_ptr) }; - let timeout = Timeout::with_rc(Duration::from_nanos(nanos)); - - process.suspend(timeout.clone()); - state.timeout_worker.suspend(process, timeout); -} - -#[inline(always)] -pub(crate) fn get_field(process: Pointer, index: u16) -> Pointer { - unsafe { process.get::().get_field(FieldIndex::new(index as u8)) } -} - -#[inline(always)] -pub(crate) fn set_field(process: Pointer, index: u16, value: Pointer) { - unsafe { - process - .get_mut::() - .set_field(FieldIndex::new(index as u8), value) - } -} - -#[inline(always)] -pub(crate) fn write_result( - state: &State, - thread: &mut Thread, - task: TaskPointer, - result: Pointer, - thrown: bool, -) -> Pointer { - match &task.write { - Write::Discard => Pointer::false_singleton(), - Write::Direct(mut rec) => { - if thrown { - rec.set_throw_value(result); - } else { - rec.set_return_value(result); - } - - thread.schedule(rec); - Pointer::true_singleton() - } - Write::Future(fut) => match fut.write(result, thrown) { - WriteResult::Continue => Pointer::true_singleton(), - WriteResult::Reschedule(consumer) => { - thread.schedule(consumer); - Pointer::true_singleton() - } - WriteResult::RescheduleWithTimeout(consumer) => { - state.timeout_worker.increase_expired_timeouts(); - thread.schedule(consumer); - Pointer::true_singleton() - } - WriteResult::Discard => Pointer::false_singleton(), - }, - } -} diff --git a/vm/src/instructions/string.rs b/vm/src/instructions/string.rs deleted file mode 100644 index 2118dddda..000000000 --- a/vm/src/instructions/string.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! VM functions for working with Inko strings. -use crate::mem::Pointer; -use crate::mem::{Int, String as InkoString}; -use crate::process::TaskPointer; -use crate::state::State; - -#[inline(always)] -pub(crate) fn equals(left_ptr: Pointer, right_ptr: Pointer) -> Pointer { - if left_ptr.is_permanent() && right_ptr.is_permanent() { - if left_ptr == right_ptr { - return Pointer::true_singleton(); - } else { - return Pointer::false_singleton(); - } - } - - let left = unsafe { InkoString::read(&left_ptr) }; - let right = unsafe { InkoString::read(&right_ptr) }; - - if left == right { - Pointer::true_singleton() - } else { - Pointer::false_singleton() - } -} - -#[inline(always)] -pub(crate) fn size(state: &State, ptr: Pointer) -> Pointer { - let string = unsafe { InkoString::read(&ptr) }; - let length = string.len() as i64; - - Int::alloc(state.permanent_space.int_class(), length) -} - -#[inline(always)] -pub(crate) fn concat(state: &State, mut task: TaskPointer) -> Pointer { - let mut buffer = String::new(); - - for ptr in task.take_arguments() { - buffer.push_str(unsafe { InkoString::read(&ptr) }); - } - - InkoString::alloc(state.permanent_space.string_class(), buffer) -} - -#[inline(always)] -pub(crate) fn byte(str_ptr: Pointer, index_ptr: Pointer) -> Pointer { - let byte = unsafe { - let string = InkoString::read(&str_ptr); - let index = Int::read(index_ptr) as usize; - - i64::from(*string.as_bytes().get_unchecked(index)) - }; - - Pointer::int(byte) -} - -#[inline(always)] -pub(crate) fn drop(pointer: Pointer) { - // Permanent Strings can be used as if they were regular owned Strings, so - // we must make sure not to drop these. - if pointer.is_permanent() { - return; - } - - unsafe { - InkoString::drop(pointer); - } -} diff --git a/vm/src/location_table.rs b/vm/src/location_table.rs deleted file mode 100644 index 8d116a728..000000000 --- a/vm/src/location_table.rs +++ /dev/null @@ -1,107 +0,0 @@ -//! Mapping of instruction offsets to their source locations. -use crate::mem::Pointer; - -/// A single entry in a location table. -pub(crate) struct Entry { - /// The instruction index. - index: u32, - - /// The source line number. - /// - /// We don't use line number offsets, as line numbers are not monotonically - /// increasing due to code inlining. For example, entry A may refer to line - /// 4 in file A, while entry B may refer to line 2 in file D. - /// - /// This means files are limited to (2^16)-1 lines. This should be fine in - /// practise, as files with that many lines are never a good idea. - line: u16, - - /// The file (as a permanent string) the instruction originates from. - file: Pointer, - - /// The name of the method (as a permanent string) the instruction - /// originates from. - name: Pointer, -} - -/// A location resolved using a location table. -#[derive(Eq, PartialEq, Debug)] -pub(crate) struct Location { - pub(crate) name: Pointer, - pub(crate) file: Pointer, - pub(crate) line: Pointer, -} - -/// A table that maps instruction offsets to their source locations. -/// -/// This table is used to obtain stack traces. The setup here is based on the -/// line number tables found in Python. -/// -/// File paths and scope names are stored separately from entries. This ensures -/// entries take up as little space as possible. -pub(crate) struct LocationTable { - entries: Vec, -} - -impl LocationTable { - pub(crate) fn new() -> Self { - Self { entries: Vec::new() } - } - - pub(crate) fn add_entry( - &mut self, - index: u32, - line: u16, - file: Pointer, - name: Pointer, - ) { - self.entries.push(Entry { index, line, file, name }); - } - - pub(crate) fn get(&self, index: u32) -> Option { - for entry in self.entries.iter() { - if entry.index == index { - let name = entry.name; - let file = entry.file; - let line = Pointer::int(entry.line as i64); - - return Some(Location { name, file, line }); - } - } - - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::mem::size_of; - - #[test] - fn test_type_sizes() { - assert_eq!(size_of::(), 24); - } - - #[test] - fn test_location_table_get() { - let mut table = LocationTable::new(); - let file = Pointer::int(1); - let name = Pointer::int(2); - - table.add_entry(2, 1, file, name); - table.add_entry(1, 2, file, name); - table.add_entry(1, 3, file, name); - - assert!(table.get(0).is_none()); - assert_eq!( - table.get(1), - Some(Location { name, file, line: Pointer::int(2) }) - ); - assert_eq!( - table.get(2), - Some(Location { name, file, line: Pointer::int(1) }) - ); - assert!(table.get(3).is_none()); - } -} diff --git a/vm/src/machine.rs b/vm/src/machine.rs deleted file mode 100644 index 0ce28ef1c..000000000 --- a/vm/src/machine.rs +++ /dev/null @@ -1,1138 +0,0 @@ -use crate::execution_context::ExecutionContext; -use crate::image::Image; -use crate::instructions::array; -use crate::instructions::builtin_functions; -use crate::instructions::byte_array; -use crate::instructions::float; -use crate::instructions::future; -use crate::instructions::general; -use crate::instructions::integer; -use crate::instructions::process; -use crate::instructions::string; -use crate::mem::{Int, Pointer, String as InkoString}; -use crate::network_poller::Worker as NetworkPollerWorker; -use crate::process::{Process, ProcessPointer, TaskPointer}; -use crate::runtime_error::RuntimeError; -use crate::scheduler::process::Thread; -use crate::state::State; -use bytecode::Instruction; -use bytecode::Opcode; -use std::fmt::Write; -use std::thread; - -macro_rules! reset { - ($task: expr, $loop_state: expr) => {{ - $loop_state = unsafe { LoopState::new($task) }; - }}; -} - -/// The state of an interpreter loop, such as the context being executed. -/// -/// We use a structure here so we don't need to pass around tons of variables -/// just to support suspending/resuming processes in the right place. -struct LoopState<'a> { - instructions: &'a [Instruction], - index: usize, - context: &'a mut ExecutionContext, -} - -impl<'a> LoopState<'a> { - /// Returns a new LoopState for the given task. - /// - /// This method is unsafe as we perform multiple borrows of data related to - /// the task. This is necessary to prevent additional/needless pointer reads - /// in the interpreter loop. Unfortunately, there's no way of expressing - /// this in safe Rust code, hence the use of unsafe code in this method. - /// - /// Because of this code, care must be taken to reset the loop state at the - /// right time. For example, when a new method is scheduled the state must - /// be reset; otherwise we'll continue running the current method. - /// - /// If there's a better way of going about this (without incurring runtime - /// overhead) we'd love to adopt that. Unfortunately, as of July 2021 this - /// is the best we can do :< - unsafe fn new(mut task: TaskPointer) -> Self { - let context = &mut *(&mut *task.context as *mut ExecutionContext); - let method = context.method(); - - // Accessing instructions using `context.method().instructions` would - // incur a pointer read for every instruction, followed by the offset to - // determine the current instruction. By storing the slice we can avoid - // the pointer read. - let instructions = &*(method.instructions.as_slice() as *const _); - - LoopState { index: context.index, instructions, context } - } - - fn rewind(&mut self) { - self.context.index = self.index - 1; - } - - fn save(&mut self) { - self.context.index = self.index; - } -} - -pub struct Machine<'a> { - /// The shared virtual machine state, such as the process pools and built-in - /// types. - pub(crate) state: &'a State, -} - -impl<'a> Machine<'a> { - pub(crate) fn new(state: &'a State) -> Self { - Machine { state } - } - - /// Boots up the VM and all its thread pools. - /// - /// This method blocks the calling thread until the Inko program terminates. - pub fn boot(image: Image, arguments: &[String]) -> Result { - let state = State::new(image.config, image.permanent_space, arguments); - let entry_class = image.entry_class; - let entry_method = - unsafe { entry_class.get_method(image.entry_method) }; - - { - let state = state.clone(); - - thread::Builder::new() - .name("timeout worker".to_string()) - .spawn(move || state.timeout_worker.run(&state.scheduler)) - .unwrap(); - } - - { - for id in 0..state.network_pollers.len() { - let state = state.clone(); - - thread::Builder::new() - .name(format!("netpoll {}", id)) - .spawn(move || NetworkPollerWorker::new(id, state).run()) - .unwrap(); - } - } - - state.scheduler.run(&*state, entry_class, entry_method); - Ok(state.current_exit_status()) - } - - pub(crate) fn run(&self, thread: &mut Thread, mut process: ProcessPointer) { - // When there's no task to run, clients will try to reschedule the - // process after sending it a message. This means we (here) don't need - // to do anything extra. - if let Some(task) = process.task_to_run() { - if let Err(message) = self.run_task(thread, process, task) { - self.panic(process, &message); - } - } else if process.finish_task() { - thread.schedule(process); - } - } - - fn run_task( - &self, - thread: &mut Thread, - mut process: ProcessPointer, - mut task: TaskPointer, - ) -> Result<(), String> { - let mut reductions = self.state.config.reductions as i32; - let mut state; - - reset!(task, state); - - 'ins_loop: loop { - let ins = unsafe { state.instructions.get_unchecked(state.index) }; - - state.index += 1; - - match ins.opcode { - Opcode::Allocate => { - let reg = ins.arg(0); - let idx = ins.u32_arg(1, 2); - let res = general::allocate(self.state, idx); - - state.context.set_register(reg, res); - } - Opcode::ArrayAllocate => { - let reg = ins.arg(0); - let res = array::allocate(self.state, task); - - state.context.set_register(reg, res); - } - Opcode::GetTrue => { - let reg = ins.arg(0); - let res = Pointer::true_singleton(); - - state.context.set_register(reg, res); - } - Opcode::GetFalse => { - let reg = ins.arg(0); - let res = Pointer::false_singleton(); - - state.context.set_register(reg, res); - } - Opcode::Return => { - let res = state.context.get_register(ins.arg(0)); - - process.set_return_value(res); - - // Once we're at the top-level _and_ we have no more - // instructions to process, we'll write the result to a - // future and bail out the execution loop. - if task.pop_context() { - break 'ins_loop; - } - - reset!(task, state); - } - Opcode::Branch => { - let val = state.context.get_register(ins.arg(0)); - let if_true = ins.arg(1) as usize; - let if_false = ins.arg(2) as usize; - - state.index = if val == Pointer::true_singleton() { - if_true - } else { - if_false - }; - } - Opcode::Goto => { - state.index = ins.arg(0) as usize; - } - Opcode::BranchResult => { - let if_ok = ins.arg(0) as usize; - let if_err = ins.arg(1) as usize; - - state.index = if process.thrown() { if_err } else { if_ok }; - } - Opcode::IntAdd => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::add(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntWrappingAdd => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::wrapping_add(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntDiv => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::div(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntMul => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::mul(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntWrappingMul => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::wrapping_mul(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntSub => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::sub(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntWrappingSub => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::wrapping_sub(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntMod => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::modulo(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntBitAnd => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::and(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::IntBitOr => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::or(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::IntBitXor => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::xor(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::IntBitNot => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = integer::not(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::IntRotateLeft => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::rotate_left(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::IntRotateRight => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::rotate_right(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::IntShl => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::shl(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntShr => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::shr(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntUnsignedShr => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::unsigned_shr(self.state, a, b)?; - - state.context.set_register(reg, res); - } - Opcode::IntLt => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::lt(a, b); - - state.context.set_register(reg, res); - } - Opcode::IntGt => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::gt(a, b); - - state.context.set_register(reg, res); - } - Opcode::IntEq => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::eq(a, b); - - state.context.set_register(reg, res); - } - Opcode::IntGe => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::ge(a, b); - - state.context.set_register(reg, res); - } - Opcode::IntLe => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::le(a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatAdd => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::add(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatMul => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::mul(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatDiv => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::div(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatSub => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::sub(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatMod => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::modulo(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatLt => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::lt(a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatGt => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::gt(a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatEq => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::eq(a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatGe => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::ge(a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatLe => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = float::le(a, b); - - state.context.set_register(reg, res); - } - Opcode::ArraySet => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let val = state.context.get_register(ins.arg(3)); - let res = array::set(ary, idx, val); - - state.context.set_register(reg, res); - } - Opcode::ArrayPush => { - let ary = state.context.get_register(ins.arg(0)); - let val = state.context.get_register(ins.arg(1)); - - array::push(ary, val); - } - Opcode::ArrayPop => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let res = array::pop(ary); - - state.context.set_register(reg, res); - } - Opcode::ArrayGet => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let res = array::get(ary, idx); - - state.context.set_register(reg, res); - } - Opcode::ArrayRemove => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let res = array::remove(ary, idx); - - state.context.set_register(reg, res); - } - Opcode::ArrayLength => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let res = array::length(self.state, ary); - - state.context.set_register(reg, res); - } - Opcode::StringEq => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = string::equals(a, b); - - state.context.set_register(reg, res); - } - Opcode::StringSize => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = string::size(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::SetField => { - let rec = state.context.get_register(ins.arg(0)); - let idx = ins.arg(1); - let val = state.context.get_register(ins.arg(2)); - - general::set_field(rec, idx, val); - } - Opcode::GetField => { - let reg = ins.arg(0); - let rec = state.context.get_register(ins.arg(1)); - let idx = ins.arg(2); - let res = general::get_field(rec, idx); - - state.context.set_register(reg, res); - } - Opcode::ProcessAllocate => { - let reg = ins.arg(0); - let idx = ins.u32_arg(1, 2); - let res = process::allocate(self.state, idx); - - state.context.set_register(reg, res); - } - Opcode::ProcessSend => { - let rec = state.context.get_register(ins.arg(0)); - let method = ins.arg(1); - let wait = ins.arg(2) == 1; - - state.save(); - - let switch = process::send_message( - self.state, thread, task, process, rec, method, wait, - )?; - - if switch { - return Ok(()); - } - } - Opcode::ProcessSendAsync => { - let reg = ins.arg(0); - let rec = state.context.get_register(ins.arg(1)); - let method = ins.arg(2); - - // We save in case of a panic, otherwise we may be missing - // stack frames in the resulting stack trace. - state.save(); - - let res = process::send_async_message( - self.state, thread, process, task, rec, method, - )?; - - state.context.set_register(reg, res); - } - Opcode::ProcessWriteResult => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = process::write_result( - self.state, - thread, - task, - val, - ins.arg(2) == 1, - ); - - state.context.set_register(reg, res); - } - Opcode::ProcessSuspend => { - let time = state.context.get_register(ins.arg(0)); - - state.save(); - process::suspend(self.state, process, time); - - return Ok(()); - } - Opcode::ProcessGetField => { - let reg = ins.arg(0); - let rec = state.context.get_register(ins.arg(1)); - let idx = ins.arg(2); - let res = process::get_field(rec, idx); - - state.context.set_register(reg, res); - } - Opcode::ProcessSetField => { - let rec = state.context.get_register(ins.arg(0)); - let idx = ins.arg(1); - let val = state.context.get_register(ins.arg(2)); - - process::set_field(rec, idx, val); - } - Opcode::ObjectEq => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = general::equals(a, b); - - state.context.set_register(reg, res); - } - Opcode::GetNil => { - let res = Pointer::nil_singleton(); - - state.context.set_register(ins.arg(0), res); - } - Opcode::GetUndefined => { - let res = Pointer::undefined_singleton(); - - state.context.set_register(ins.arg(0), res); - } - Opcode::GetConstant => { - let reg = ins.arg(0); - let addr = ins.u64_arg(1, 2, 3, 4); - let res = Pointer::new(addr as *mut u8); - - state.context.set_register(reg, res); - } - Opcode::Throw => { - let value = state.context.get_register(ins.arg(0)); - let unwind = ins.arg(1) == 1; - - process.set_throw_value(value); - - if unwind && task.pop_context() { - break 'ins_loop; - } - - reset!(task, state); - } - Opcode::MoveRegister => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - - state.context.set_register(reg, val); - } - Opcode::Panic => { - let msg = state.context.get_register(ins.arg(0)); - - return Err(unsafe { InkoString::read(&msg).to_string() }); - } - Opcode::Exit => { - let status = state.context.get_register(ins.arg(0)); - - general::exit(self.state, status)?; - - // This is just a best-case effort to clean up the current - // process. If it still has unfinished tasks or live - // objects, those are all left as-is. - Process::drop_and_deallocate(process); - - return Ok(()); - } - Opcode::StringConcat => { - let reg = ins.arg(0); - let res = string::concat(self.state, task); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayAllocate => { - let reg = ins.arg(0); - let res = byte_array::allocate(self.state); - - state.context.set_register(reg, res); - } - Opcode::ByteArraySet => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let val = state.context.get_register(ins.arg(3)); - let res = byte_array::set(self.state, ary, idx, val); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayGet => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let res = byte_array::get(ary, idx); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayPush => { - let ary = state.context.get_register(ins.arg(0)); - let val = state.context.get_register(ins.arg(1)); - - byte_array::push(ary, val); - } - Opcode::ByteArrayPop => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let res = byte_array::pop(ary); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayRemove => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let res = byte_array::remove(ary, idx); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayLength => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let res = byte_array::length(self.state, ary); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayEquals => { - let reg = ins.arg(0); - let cmp = state.context.get_register(ins.arg(1)); - let cmp_with = state.context.get_register(ins.arg(2)); - let res = byte_array::equals(cmp, cmp_with); - - state.context.set_register(reg, res); - } - Opcode::StringByte => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let idx = state.context.get_register(ins.arg(2)); - let res = string::byte(val, idx); - - state.context.set_register(reg, res); - } - Opcode::MoveResult => { - let reg = ins.arg(0); - let res = process.move_result(); - - state.context.set_register(reg, res); - } - Opcode::BuiltinFunctionCall => { - let func = ins.arg(0); - - // When an operation would block, the file descriptor/thing - // is already registered, and the process may already be - // running again in another thread. This means that when a - // WouldBlock is produced it is not safe to access any - // process data. - // - // To ensure blocking operations are retried properly, we - // _first_ set the instruction index, then advance it again - // if it is safe to do so. - state.rewind(); - - match builtin_functions::call( - self.state, thread, process, task, func, - ) { - Ok(val) => { - state.save(); - task.clear_arguments(); - process.set_return_value(val); - } - Err(RuntimeError::Panic(msg)) => { - task.clear_arguments(); - - return Err(msg); - } - Err(RuntimeError::Error(value)) => { - state.save(); - task.clear_arguments(); - process.set_throw_value(value); - } - Err(RuntimeError::WouldBlock) => { - // *DO NOT* use the task or process at this point, - // as it may have been invalidated if the process is - // already running again in another thread. - return Ok(()); - } - } - - // If the function took too long to run (e.g. an IO - // operation took too long), we have to give up running the - // process. If we continue running we could mess up whatever - // thread has taken over our queue/work, and we'd be using - // the OS thread even longer than we already have. - // - // We schedule onto the global queue because if another - // thread took over but found no other work, it may have - // gone to sleep. In that case scheduling onto the local - // queue may result in the work never getting picked up - // (e.g. if all other threads are also sleeping). - if thread.backup { - thread.schedule_global(process); - - return Ok(()); - } - } - Opcode::FutureGet => { - let fut = state.context.get_register(ins.arg(0)); - - state.rewind(); - - if future::get(self.state, process, fut) { - state.save(); - } else { - return Ok(()); - } - } - Opcode::FutureGetFor => { - let fut = state.context.get_register(ins.arg(0)); - let time = state.context.get_register(ins.arg(1)); - - state.rewind(); - - if future::get_for(self.state, process, fut, time) { - state.save(); - } else { - return Ok(()); - } - } - Opcode::FuturePoll => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - - state.rewind(); - - if let Some(res) = future::poll(self.state, process, ary) { - state.save(); - state.context.set_register(reg, res); - } else { - return Ok(()); - } - } - Opcode::CallVirtual => { - let rec = state.context.get_register(ins.arg(0)); - let method = ins.arg(1); - - state.save(); - // TODO: remove - if rec.as_ptr().is_null() { - return Err("CallVirtual with NULL".to_string()); - } - general::call_virtual(self.state, task, rec, method); - reset!(task, state); - } - Opcode::CallDynamic => { - let rec = state.context.get_register(ins.arg(0)); - let hash = ins.u32_arg(1, 2); - - state.save(); - general::call_dynamic(self.state, task, rec, hash); - reset!(task, state); - } - Opcode::CallStatic => { - let class = ins.u32_arg(0, 1); - let method = ins.arg(2); - - state.save(); - general::call_static(self.state, task, class, method); - reset!(task, state); - } - Opcode::RefKind => { - let reg = ins.arg(0); - let ptr = state.context.get_register(ins.arg(1)); - let res = general::ref_kind(ptr); - - state.context.set_register(reg, res); - } - Opcode::Increment => { - let reg = ins.arg(0); - let ptr = state.context.get_register(ins.arg(1)); - let res = general::increment(ptr); - - state.context.set_register(reg, res); - } - Opcode::Decrement => { - let ptr = state.context.get_register(ins.arg(0)); - - general::decrement(ptr); - } - Opcode::DecrementAtomic => { - let reg = ins.arg(0); - let ptr = state.context.get_register(ins.arg(1)); - let res = general::decrement_atomic(ptr); - - state.context.set_register(reg, res); - } - Opcode::CheckRefs => { - let ptr = state.context.get_register(ins.arg(0)); - - general::check_refs(ptr)?; - } - Opcode::Free => { - let obj = state.context.get_register(ins.arg(0)); - - general::free(obj); - } - Opcode::IntClone => { - let reg = ins.arg(0); - let obj = state.context.get_register(ins.arg(1)); - let res = integer::clone(self.state, obj); - - state.context.set_register(reg, res); - } - Opcode::FloatClone => { - let reg = ins.arg(0); - let obj = state.context.get_register(ins.arg(1)); - let res = float::clone(self.state, obj); - - state.context.set_register(reg, res); - } - Opcode::Reduce => { - reductions -= ins.arg(0) as i32; - - // We don't need an overflow check here, as a single u16 - // (combined with this check) can't overflow an i32. - if reductions <= 0 { - state.save(); - thread.schedule(process); - - return Ok(()); - } - } - Opcode::ArrayClear => { - let ary = state.context.get_register(ins.arg(0)); - - array::clear(ary); - } - Opcode::ArrayDrop => { - let ary = state.context.get_register(ins.arg(0)); - - array::drop(ary); - } - Opcode::ByteArrayClear => { - let ary = state.context.get_register(ins.arg(0)); - - byte_array::clear(ary) - } - Opcode::ByteArrayClone => { - let reg = ins.arg(0); - let ary = state.context.get_register(ins.arg(1)); - let res = byte_array::clone(self.state, ary); - - state.context.set_register(reg, res); - } - Opcode::ByteArrayDrop => { - let ary = state.context.get_register(ins.arg(0)); - - byte_array::drop(ary); - } - Opcode::IntToFloat => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = integer::to_float(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::IntToString => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = integer::to_string(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::IntPow => { - let reg = ins.arg(0); - let a = state.context.get_register(ins.arg(1)); - let b = state.context.get_register(ins.arg(2)); - let res = integer::pow(self.state, a, b); - - state.context.set_register(reg, res); - } - Opcode::FloatCeil => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::ceil(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::FloatFloor => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::floor(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::FloatRound => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let prec = state.context.get_register(ins.arg(2)); - let res = float::round(self.state, val, prec); - - state.context.set_register(reg, res); - } - Opcode::FloatToInt => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::to_int(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::FloatToString => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::to_string(self.state, val); - - state.context.set_register(reg, res); - } - Opcode::FloatIsNan => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::is_nan(val); - - state.context.set_register(reg, res); - } - Opcode::FloatIsInf => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = float::is_inf(val); - - state.context.set_register(reg, res); - } - Opcode::FutureDrop => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = future::drop(val); - - state.context.set_register(reg, res); - } - Opcode::StringDrop => { - let val = state.context.get_register(ins.arg(0)); - - string::drop(val); - } - Opcode::IsUndefined => { - let reg = ins.arg(0); - let val = state.context.get_register(ins.arg(1)); - let res = general::is_undefined(val); - - state.context.set_register(reg, res); - } - Opcode::ProcessFinishTask => { - let terminate = ins.arg(0) == 1; - - if terminate { - if process.is_main() { - self.state.terminate(); - } - - // Processes drop/free themselves as this must be - // deferred until all messages (including any - // destructors) have finished running. If we did this in - // a destructor we'd end up releasing memory of a - // process while still using it. - Process::drop_and_deallocate(process); - - return Ok(()); - } - - break 'ins_loop; - } - Opcode::JumpTable => { - let val = state.context.get_register(ins.arg(0)); - let val_idx = unsafe { Int::read(val) } as usize; - let tbl_idx = ins.arg(1) as usize; - - state.index = - state.context.method.jump_tables[tbl_idx][val_idx]; - } - Opcode::Push => { - let val = state.context.get_register(ins.arg(0)); - - task.stack.push(val); - } - Opcode::Pop => { - let reg = ins.arg(0); - - if let Some(val) = task.stack.pop() { - state.context.set_register(reg, val); - } - } - }; - } - - if process.finish_task() { - thread.schedule(process); - } - - Ok(()) - } - - /// Produces an Inko panic (not a Rust panic) and terminates the current - /// program. - /// - /// This function is marked as cold as we expect it to be called rarely, if - /// ever (in a correct program). This should also ensure any branches - /// leading to this function are treated as unlikely. - #[cold] - #[inline(never)] - fn panic(&self, process: ProcessPointer, message: &str) { - let mut buffer = String::new(); - - buffer.push_str("Stack trace (the most recent call comes last):"); - - for location in process.stacktrace() { - unsafe { - let _ = write!( - buffer, - "\n {} line {}, in '{}'", - InkoString::read(&location.file), - Int::read(location.line), - InkoString::read(&location.name) - ); - } - } - - let _ = write!( - buffer, - "\nProcess {:#x} panicked: {}", - process.identifier(), - message - ); - - eprintln!("{}", buffer); - self.state.set_exit_status(1); - self.state.terminate(); - } -} diff --git a/vm/src/mem.rs b/vm/src/mem.rs deleted file mode 100644 index fbbb1b81c..000000000 --- a/vm/src/mem.rs +++ /dev/null @@ -1,1164 +0,0 @@ -use crate::immutable_string::ImmutableString; -use crate::indexes::*; -use crate::location_table::LocationTable; -use crate::permanent_space::PermanentSpace; -use crate::process::Process; -use bytecode::Instruction; -use std::alloc::{alloc, alloc_zeroed, dealloc, handle_alloc_error, Layout}; -use std::mem::{align_of, size_of, swap, transmute}; -use std::ops::Deref; -use std::ptr::drop_in_place; -use std::string::String as RustString; -use std::sync::atomic::{AtomicU16, Ordering}; - -/// The alignment to use for Inko objects. -const ALIGNMENT: usize = align_of::(); - -/// The number of bits to shift for tagged integers. -/// -/// We shift by two bits, limiting tagged integers to 62 bits. This frees up the -/// lower two bits for non tagged values. We need two bits instead of one so we -/// can efficiently tell the various immediate values apart. -const INT_SHIFT_BITS: usize = 2; - -/// The minimum integer value that can be stored as a tagged signed integer. -pub(crate) const MIN_INTEGER: i64 = i64::MIN >> INT_SHIFT_BITS; - -/// The maximum integer value that can be stored as a tagged signed integer. -pub(crate) const MAX_INTEGER: i64 = i64::MAX >> INT_SHIFT_BITS; - -/// The bit set for all immediate values. -const IMMEDIATE_BIT: usize = 0b1; - -/// The mask to use for tagged integers. -const INT_MASK: usize = 0b00_0011; - -/// The mask to use for detecting booleans. -const BOOL_MASK: usize = 0b00_0101; - -/// The address of the singleton `False`. -const FALSE_ADDRESS: usize = 0b00_0101; - -/// The address of the singleton `True`. -const TRUE_ADDRESS: usize = 0b00_1101; - -/// The address of the `Nil` singleton. -const NIL_ADDRESS: usize = 0b00_0001; - -/// The address of the `undefined` singleton. -const UNDEFINED_ADDRESS: usize = 0b00_1001; - -/// The mask to apply for permanent objects. -const PERMANENT_MASK: usize = 0b00_0010; - -/// The mask to apply for references. -const REF_MASK: usize = 0b00_0100; - -/// The mask to use for detecting values that are not immediate or permanent -/// values. -const LOCAL_OWNED_MASK: usize = 0b00_0011; - -/// The mask to use for untagging a pointer. -const UNTAG_MASK: usize = (!0b111) as usize; - -pub(crate) fn allocate(layout: Layout) -> *mut u8 { - unsafe { - let ptr = alloc(layout); - - if ptr.is_null() { - handle_alloc_error(layout); - } else { - ptr - } - } -} - -/// A pointer to an object managed by the Inko runtime. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) struct Pointer(*mut u8); - -unsafe impl Sync for Pointer {} -unsafe impl Send for Pointer {} - -impl Pointer { - /// Creates a new Pointer from the raw address. - pub(crate) fn new(raw: *mut u8) -> Self { - Self(raw) - } - - /// Creates a pointer to a regular boxed value. - /// - /// This method is intended to be used when we want to pretend a Rust value - /// is an Inko object. This allows exposing of Rust data to Inko, without - /// having to wrap it in a heap object. - pub(crate) fn boxed(value: T) -> Self { - Self::new(Box::into_raw(Box::new(value)) as *mut u8) - } - - pub(crate) fn with_mask(raw: *mut u8, mask: usize) -> Self { - Self::new((raw as usize | mask) as _) - } - - pub(crate) fn int(value: i64) -> Self { - Self::with_mask((value << INT_SHIFT_BITS) as _, INT_MASK) - } - - pub(crate) fn true_singleton() -> Self { - Self::new(TRUE_ADDRESS as _) - } - - pub(crate) fn false_singleton() -> Self { - Self::new(FALSE_ADDRESS as _) - } - - pub(crate) fn nil_singleton() -> Self { - Self::new(NIL_ADDRESS as _) - } - - pub(crate) fn undefined_singleton() -> Self { - Self::new(UNDEFINED_ADDRESS as _) - } - - pub(crate) fn is_regular(self) -> bool { - (self.as_ptr() as usize & IMMEDIATE_BIT) == 0 - } - - pub(crate) fn is_boolean(self) -> bool { - self.mask_is_set(BOOL_MASK) - } - - pub(crate) fn is_tagged_int(self) -> bool { - self.mask_is_set(INT_MASK) - } - - pub(crate) fn is_permanent(self) -> bool { - self.mask_is_set(PERMANENT_MASK) - } - - pub(crate) fn is_ref(self) -> bool { - self.mask_is_set(REF_MASK) - } - - /// Returns a boolean indicating if the pointer points to a non-permanent - /// heap object. - pub(crate) fn is_local_heap_object(self) -> bool { - (self.as_ptr() as usize & LOCAL_OWNED_MASK) == 0 - } - - pub(crate) fn as_permanent(self) -> Pointer { - Self::with_mask(self.as_ptr(), PERMANENT_MASK) - } - - pub(crate) fn as_ref(self) -> Pointer { - Self::with_mask(self.as_ptr(), REF_MASK) - } - - pub(crate) fn as_ptr(self) -> *mut u8 { - self.0 - } - - pub(crate) unsafe fn get<'a, T>(self) -> &'a T { - &*(self.untagged_ptr() as *const T) - } - - pub(crate) unsafe fn get_mut<'a, T>(self) -> &'a mut T { - &mut *(self.untagged_ptr() as *mut T) - } - - /// Drops and deallocates the object this pointer points to. - /// - /// This method is meant to be used only when a Pointer points to a value - /// allocated using Rust's Box type. - pub(crate) unsafe fn drop_boxed(self) { - drop(Box::from_raw(self.as_ptr() as *mut T)); - } - - pub(crate) fn untagged_ptr(self) -> *mut u8 { - (self.as_ptr() as usize & UNTAG_MASK) as _ - } - - pub(crate) unsafe fn as_int(self) -> i64 { - self.as_ptr() as i64 >> INT_SHIFT_BITS - } - - pub(crate) unsafe fn free(self) { - let header = self.get::
(); - let layout = header.class.instance_layout(); - - dealloc(self.untagged_ptr(), layout); - } - - pub(crate) fn mask_is_set(self, mask: usize) -> bool { - (self.as_ptr() as usize & mask) == mask - } -} - -/// The header used by heap allocated objects. -/// -/// The layout is fixed to ensure we can safely assume certain fields are at -/// certain offsets in an object, even when not knowing what type of object -/// we're dealing with. -#[repr(C)] -pub(crate) struct Header { - /// The class of the object. - pub(crate) class: ClassPointer, - - /// A flag indicating the object uses atomic reference counting. - atomic: bool, - - /// The number of references to the object of this header. - /// - /// A 16-bits integer should be enough for every program. - /// - /// Not all objects use reference counting, but we still reserve the space - /// in a header. This makes it easier for generic code to handle both - /// objects that do and don't use reference counting. - references: u16, -} - -impl Header { - pub(crate) fn init(&mut self, class: ClassPointer) { - self.class = class; - self.atomic = false; - self.references = 0; - } - - pub(crate) fn init_atomic(&mut self, class: ClassPointer) { - self.class = class; - self.atomic = true; - - // Atomic values start with a reference count of 1, so - // `decrement_atomic()` returns the correct result for a value for which - // no extra references have been created (instead of overflowing). - self.references = 1; - } - - pub(crate) fn is_atomic(&self) -> bool { - self.atomic - } - - pub(crate) fn references(&self) -> u16 { - self.references - } - - pub(crate) fn increment(&mut self) { - self.references += 1; - } - - pub(crate) fn decrement(&mut self) { - debug_assert_ne!(self.references, 0); - - self.references -= 1; - } - - pub(crate) fn increment_atomic(&self) { - self.references_as_atomic().fetch_add(1, Ordering::AcqRel); - } - - pub(crate) fn decrement_atomic(&self) -> bool { - let old = self.references_as_atomic().fetch_sub(1, Ordering::AcqRel); - - // fetch_sub() overflows, making it harder to detect errors during - // development. - debug_assert_ne!(old, 0); - - old == 1 - } - - fn references_as_atomic(&self) -> &AtomicU16 { - unsafe { transmute::<_, &AtomicU16>(&self.references) } - } -} - -/// A method bound to an object. -/// -/// Methods aren't values and can't be passed around, nor can you call methods -/// on them. As such, methods don't have headers or classes. -#[repr(C)] -pub(crate) struct Method { - /// The hash of this method, used when performing dynamic dispatch. - /// - /// We use a u32 as this is easier to encode into an instruction compared to - /// a u64. - pub(crate) hash: u32, - pub(crate) registers: u16, - pub(crate) instructions: Vec, - pub(crate) locations: LocationTable, - pub(crate) jump_tables: Vec>, -} - -impl Method { - pub(crate) fn drop_and_deallocate(ptr: MethodPointer) { - unsafe { - drop_in_place(ptr.as_ptr()); - dealloc(ptr.as_ptr() as *mut u8, Self::layout()); - } - } - - pub(crate) fn alloc( - hash: u32, - registers: u16, - instructions: Vec, - locations: LocationTable, - jump_tables: Vec>, - ) -> MethodPointer { - unsafe { - let ptr = allocate(Self::layout()) as *mut Self; - let obj = &mut *ptr; - - init!(obj.hash => hash); - init!(obj.registers => registers); - init!(obj.instructions => instructions); - init!(obj.locations => locations); - init!(obj.jump_tables => jump_tables); - - MethodPointer(ptr) - } - } - - unsafe fn layout() -> Layout { - Layout::from_size_align_unchecked( - size_of::(), - align_of::(), - ) - } -} - -/// A pointer to an immutable method. -#[repr(transparent)] -#[derive(Copy, Clone)] -pub(crate) struct MethodPointer(*mut Method); - -impl MethodPointer { - /// Returns the MethodPointer as a regular Pointer. - pub(crate) fn as_ptr(self) -> *mut Method { - self.0 - } -} - -impl Deref for MethodPointer { - type Target = Method; - - fn deref(&self) -> &Method { - unsafe { &*(self.0 as *const Method) } - } -} - -/// An Inko class. -/// -/// Classes come in a variety of sizes, and we don't drop them while the program -/// is running. To make managing memory easier, classes are always allocated -/// using the system allocator. -/// -/// Due to the size of this type being variable, it's used/allocated using the -/// Class type, which acts like an owned pointer to this data. -#[repr(C)] -pub(crate) struct Class { - /// The header of this class. - /// - /// The class pointer in this header will point to this class itself. - header: Header, - - /// The name of the class. - pub(crate) name: RustString, - - /// The size (in bytes) of instances of this class. - pub(crate) instance_size: usize, - - /// The number of method slots this class has. - /// - /// The actual number of methods may be less than this value. - pub(crate) method_slots: u16, - - /// All the methods of this class. - /// - /// Methods are accessed frequently, and we want to do so with as little - /// indirection and as cache-friendly as possible. For this reason we use a - /// flexible array member, instead of a Vec. - /// - /// The length of this table is always a power of two, which means some - /// slots are NULL. - methods: [MethodPointer; 0], -} - -impl Class { - pub(crate) fn drop(ptr: ClassPointer) { - unsafe { - let layout = Self::layout(ptr.method_slots); - let raw_ptr = ptr.as_ptr(); - - drop_in_place(raw_ptr); - dealloc(raw_ptr as *mut u8, layout); - } - } - - pub(crate) fn alloc( - name: RustString, - methods: u16, - size: usize, - ) -> ClassPointer { - let mut class_ptr = unsafe { - let layout = Self::layout(methods); - - // For classes we zero memory out, so unused method slots are set to - // zeroed memory, instead of random garbage. - let ptr = alloc_zeroed(layout) as *mut Class; - - if ptr.is_null() { - handle_alloc_error(layout); - } - - ClassPointer::new(ptr) - }; - let class_ptr_copy = class_ptr; - let class = unsafe { class_ptr.get_mut() }; - - class.header.init(class_ptr_copy); - - init!(class.name => name); - init!(class.instance_size => size); - init!(class.method_slots => methods); - - class_ptr - } - - /// Returns a new class for a regular object. - pub(crate) fn object( - name: RustString, - fields: usize, - methods: u16, - ) -> ClassPointer { - let size = size_of::() + (fields * size_of::()); - - Self::alloc(name, methods, size) - } - - /// Returns a new class for a process. - pub(crate) fn process( - name: RustString, - fields: usize, - methods: u16, - ) -> ClassPointer { - let size = size_of::() + (fields * size_of::()); - - Self::alloc(name, methods, size) - } - - /// Returns a pointer to the class of the given pointer. - pub(crate) fn of(space: &PermanentSpace, ptr: Pointer) -> ClassPointer { - if ptr.is_regular() { - unsafe { ptr.get::
().class } - } else if ptr.is_tagged_int() { - space.int_class() - } else if ptr.is_boolean() { - space.boolean_class() - } else { - space.nil_class() - } - } - - /// Returns the `Layout` for a class itself. - unsafe fn layout(methods: u16) -> Layout { - let size = - size_of::() + (methods as usize * size_of::()); - - Layout::from_size_align_unchecked(size, align_of::()) - } - - pub(crate) unsafe fn instance_layout(&self) -> Layout { - Layout::from_size_align_unchecked(self.instance_size, ALIGNMENT) - } - - pub(crate) fn set_method( - &mut self, - index: MethodIndex, - value: MethodPointer, - ) { - unsafe { self.methods.as_mut_ptr().add(index.into()).write(value) }; - } - - pub(crate) unsafe fn get_method( - &self, - index: MethodIndex, - ) -> MethodPointer { - *self.methods.as_ptr().add(index.into()) - } - - /// Look up a method using hashing. - /// - /// This method is useful for dynamic dispatch, as an exact offset isn't - /// known in such cases. For this to work, each unique method name must have - /// its own unique hash. This method won't work if two different methods - /// have the same hash. - /// - /// In addition, we require that the number of methods in our class is a - /// power of 2, as this allows the use of a bitwise AND instead of the - /// modulo operator. - /// - /// Finally, similar to `get_method()` we expect there to be a method for - /// the given hash. In practise this is always the case as the compiler - /// enforces this, hence we don't check for this explicitly. - /// - /// For more information on this technique, refer to - /// https://thume.ca/2019/07/29/shenanigans-with-hash-tables/. - pub(crate) unsafe fn get_hashed_method( - &self, - input_hash: u32, - ) -> MethodPointer { - let len = (self.method_slots - 1) as u32; - let mut index = input_hash; - - loop { - index &= len; - - // The cast to a u16 is safe here, as the above &= ensures we limit - // the hash value to the method count. - let ptr = self.get_method(MethodIndex::new(index as u16)); - - if ptr.hash == input_hash { - return ptr; - } - - index += 1; - } - } -} - -impl Drop for Class { - fn drop(&mut self) { - for index in 0..self.method_slots { - let method = unsafe { self.get_method(MethodIndex::new(index)) }; - - if method.as_ptr().is_null() { - // Because the table size is always a power of two, some slots - // may be NULL. - continue; - } - - Method::drop_and_deallocate(method); - } - } -} - -/// A pointer to a class. -#[repr(transparent)] -#[derive(Eq, PartialEq, Copy, Clone)] -pub(crate) struct ClassPointer(*mut Class); - -impl ClassPointer { - /// Returns a new ClassPointer from a raw Pointer. - /// - /// This method is unsafe as it doesn't perform any checks to ensure the raw - /// pointer actually points to a class. - pub(crate) unsafe fn new(pointer: *mut Class) -> Self { - Self(pointer) - } - - /// Sets a method in the given index. - pub(crate) unsafe fn set_method( - mut self, - index: MethodIndex, - value: MethodPointer, - ) { - self.get_mut().set_method(index, value); - } - - pub(crate) fn as_ptr(self) -> *mut Class { - self.0 - } - - /// Returns a mutable reference to the underlying class. - /// - /// This method is unsafe because no synchronisation is applied, nor do we - /// guarantee there's only a single writer. - unsafe fn get_mut(&mut self) -> &mut Class { - &mut *(self.0 as *mut Class) - } -} - -impl Deref for ClassPointer { - type Target = Class; - - fn deref(&self) -> &Class { - unsafe { &*(self.0 as *const Class) } - } -} - -/// A resizable array. -#[repr(C)] -pub(crate) struct Array { - header: Header, - value: Vec, -} - -impl Array { - /// Drops the given Array. - /// - /// This method is unsafe as it doesn't check if the object is actually an - /// Array. - pub(crate) unsafe fn drop(ptr: Pointer) { - drop_in_place(ptr.untagged_ptr() as *mut Self); - } - - pub(crate) fn alloc(class: ClassPointer, value: Vec) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - init!(obj.value => value); - ptr - } - - pub(crate) fn value(&self) -> &Vec { - &self.value - } - - pub(crate) fn value_mut(&mut self) -> &mut Vec { - &mut self.value - } -} - -/// A resizable array of bytes. -#[repr(C)] -pub(crate) struct ByteArray { - header: Header, - value: Vec, -} - -impl ByteArray { - /// Drops the given ByteArray. - /// - /// This method is unsafe as it doesn't check if the object is actually an - /// ByteArray. - pub(crate) unsafe fn drop(ptr: Pointer) { - drop_in_place(ptr.untagged_ptr() as *mut Self); - } - - pub(crate) fn alloc(class: ClassPointer, value: Vec) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - init!(obj.value => value); - ptr - } - - pub(crate) fn value(&self) -> &Vec { - &self.value - } - - pub(crate) fn value_mut(&mut self) -> &mut Vec { - &mut self.value - } - - pub(crate) fn take_bytes(&mut self) -> Vec { - let mut bytes = Vec::new(); - - swap(&mut bytes, &mut self.value); - bytes - } -} - -/// A signed 64-bits integer. -#[repr(C)] -pub(crate) struct Int { - header: Header, - value: i64, -} - -impl Int { - pub(crate) fn alloc(class: ClassPointer, value: i64) -> Pointer { - if (MIN_INTEGER..=MAX_INTEGER).contains(&value) { - return Pointer::int(value); - } - - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - init!(obj.value => value); - ptr - } - - pub(crate) unsafe fn read(ptr: Pointer) -> i64 { - if ptr.is_tagged_int() { - ptr.as_int() - } else { - ptr.get::().value - } - } - - pub(crate) unsafe fn read_u64(ptr: Pointer) -> u64 { - let val = Self::read(ptr); - - if val < 0 { - 0 - } else { - val as u64 - } - } -} - -/// A heap allocated float. -#[repr(C)] -pub(crate) struct Float { - header: Header, - value: f64, -} - -impl Float { - pub(crate) fn alloc(class: ClassPointer, value: f64) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - init!(obj.value => value); - ptr - } - - /// Reads the float value from the pointer. - /// - /// If the pointer doesn't actually point to a float, the behaviour is - /// undefined. - pub(crate) unsafe fn read(ptr: Pointer) -> f64 { - ptr.get::().value - } -} - -/// A heap allocated string. -/// -/// Strings use atomic reference counting as they are treated as value types, -/// and this removes the need for cloning the string's contents (at the cost of -/// atomic operations). -#[repr(C)] -pub(crate) struct String { - header: Header, - value: ImmutableString, -} - -impl String { - pub(crate) unsafe fn drop(ptr: Pointer) { - drop_in_place(ptr.untagged_ptr() as *mut Self); - } - - pub(crate) unsafe fn drop_and_deallocate(ptr: Pointer) { - Self::drop(ptr); - ptr.free(); - } - - pub(crate) unsafe fn read(ptr: &Pointer) -> &str { - ptr.get::().value().as_slice() - } - - pub(crate) fn alloc(class: ClassPointer, value: RustString) -> Pointer { - Self::from_immutable_string(class, ImmutableString::from(value)) - } - - pub(crate) fn from_immutable_string( - class: ClassPointer, - value: ImmutableString, - ) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init_atomic(class); - init!(obj.value => value); - ptr - } - - pub(crate) fn value(&self) -> &ImmutableString { - &self.value - } -} - -/// A module containing classes, methods, and code to run. -#[repr(C)] -pub(crate) struct Module { - header: Header, -} - -unsafe impl Send for Module {} - -impl Module { - pub(crate) fn drop_and_deallocate(ptr: ModulePointer) { - unsafe { - drop_in_place(ptr.as_pointer().untagged_ptr() as *mut Self); - ptr.as_pointer().free(); - } - } - - pub(crate) fn alloc(class: ClassPointer) -> ModulePointer { - let ptr = allocate(Layout::new::()); - - unsafe { &mut *(ptr as *mut Self) }.header.init(class); - ModulePointer(ptr) - } - - pub(crate) fn name(&self) -> &RustString { - &self.header.class.name - } -} - -/// A pointer to a module. -#[repr(transparent)] -#[derive(Copy, Clone)] -pub(crate) struct ModulePointer(*mut u8); - -impl ModulePointer { - pub(crate) fn as_pointer(self) -> Pointer { - Pointer::new(self.0).as_permanent() - } -} - -impl Deref for ModulePointer { - type Target = Module; - - fn deref(&self) -> &Module { - unsafe { &*(self.0 as *const Module) } - } -} - -/// A regular object that can store zero or more fields. -/// -/// The size of this object varies based on the number of fields it has to -/// store. -#[repr(C)] -pub(crate) struct Object { - header: Header, - - /// The fields of this object. - /// - /// The length of this flexible array is derived from the number of - /// fields defined in this object's class. - fields: [Pointer; 0], -} - -impl Object { - /// Bump allocates a user-defined object of a variable size. - pub(crate) fn alloc(class: ClassPointer) -> Pointer { - let ptr = Pointer::new(allocate(unsafe { class.instance_layout() })); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - ptr - } - - pub(crate) unsafe fn set_field( - &mut self, - index: FieldIndex, - value: Pointer, - ) { - self.fields.as_mut_ptr().add(index.into()).write(value) - } - - pub(crate) unsafe fn get_field(&self, index: FieldIndex) -> Pointer { - *self.fields.as_ptr().add(index.into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test::{empty_method, empty_module, OwnedClass}; - use std::mem::{align_of, size_of}; - - #[test] - fn test_type_sizes() { - assert_eq!(size_of::
(), 16); - assert_eq!(size_of::(), 16); // variable, based on the fields - - assert_eq!(size_of::(), 24); - assert_eq!(size_of::(), 24); - assert_eq!(size_of::(), 40); - - assert_eq!(size_of::(), 40); - assert_eq!(size_of::(), 40); - - // Permanent objects - assert_eq!(size_of::(), 80); - assert_eq!(size_of::(), 56); - } - - #[test] - fn test_type_alignments() { - assert_eq!(align_of::
(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - assert_eq!(align_of::(), ALIGNMENT); - } - - #[test] - fn test_pointer_with_mask() { - let ptr = Pointer::with_mask(0x4 as _, 0b10); - - assert_eq!(ptr.as_ptr() as usize, 0x6); - } - - #[test] - fn test_pointer_integer_tagging() { - unsafe { - assert_eq!(Pointer::int(3).as_int(), 3); - assert_eq!(Pointer::int(0).as_int(), 0); - assert_eq!(Pointer::int(-3).as_int(), -3); - - assert!(Pointer::int(3).is_tagged_int()); - } - } - - #[test] - fn test_pointer_is_permanent_object() { - let space = PermanentSpace::new(0, 0, Default::default()); - let ptr = Float::alloc(space.float_class(), 2.4); - - assert!(ptr.as_permanent().is_permanent()); - assert!(!ptr.is_permanent()); - - unsafe { ptr.free() }; - } - - #[test] - fn test_pointer_as_ref() { - let space = PermanentSpace::new(0, 0, Default::default()); - let ptr = Float::alloc(space.float_class(), 2.4); - - assert!(!ptr.mask_is_set(REF_MASK)); - assert!(ptr.as_ref().mask_is_set(REF_MASK)); - - unsafe { ptr.free() }; - } - - #[test] - fn test_pointer_is_local_heap_object() { - let space = PermanentSpace::new(0, 0, Default::default()); - let float = Float::alloc(space.float_class(), 8.0); - - assert!(!Pointer::int(42).is_local_heap_object()); - assert!(!Pointer::int(42).is_local_heap_object()); - assert!(!float.as_permanent().is_local_heap_object()); - assert!(!Pointer::true_singleton().is_local_heap_object()); - assert!(!Pointer::false_singleton().is_local_heap_object()); - assert!(float.is_local_heap_object()); - - unsafe { float.free() }; - } - - #[test] - fn test_pointer_get() { - let space = PermanentSpace::new(0, 0, Default::default()); - let ptr = Int::alloc(space.int_class(), MAX_INTEGER + 1); - - unsafe { - assert_eq!(ptr.get::().value, MAX_INTEGER + 1); - ptr.free(); - } - } - - #[test] - fn test_pointer_get_mut() { - let space = PermanentSpace::new(0, 0, Default::default()); - let ptr = Int::alloc(space.int_class(), MAX_INTEGER + 1); - - unsafe { - ptr.get_mut::().value = MAX_INTEGER; - - assert_eq!(ptr.get::().value, MAX_INTEGER); - ptr.free(); - } - } - - #[test] - fn test_pointer_drop_boxed() { - unsafe { - let ptr = Pointer::boxed(42_u64); - - // This is just a smoke test to make sure dropping a Box doesn't - // crash or leak. - ptr.drop_boxed::(); - } - } - - #[test] - fn test_pointer_untagged_ptr() { - assert_eq!( - Pointer::new(0x8 as _).as_permanent().untagged_ptr(), - 0x8 as *mut u8 - ); - } - - #[test] - fn test_pointer_true_singleton() { - assert_eq!(Pointer::true_singleton().as_ptr() as usize, TRUE_ADDRESS); - } - - #[test] - fn test_pointer_false_singleton() { - assert_eq!(Pointer::false_singleton().as_ptr() as usize, FALSE_ADDRESS); - } - - #[test] - fn test_pointer_nil_singleton() { - assert_eq!(Pointer::nil_singleton().as_ptr() as usize, NIL_ADDRESS); - } - - #[test] - fn test_pointer_undefined_singleton() { - assert_eq!( - Pointer::undefined_singleton().as_ptr() as usize, - UNDEFINED_ADDRESS - ); - } - - #[test] - fn test_pointer_is_regular() { - let space = PermanentSpace::new(0, 0, Default::default()); - let ptr = Float::alloc(space.float_class(), 2.4); - - assert!(ptr.is_regular()); - assert!(!Pointer::true_singleton().is_regular()); - assert!(!Pointer::false_singleton().is_regular()); - assert!(!Pointer::nil_singleton().is_regular()); - assert!(!Pointer::undefined_singleton().is_regular()); - assert!(!Pointer::int(42).is_regular()); - - unsafe { ptr.free() }; - } - - #[test] - fn test_pointer_is_tagged_int() { - assert!(!Pointer::true_singleton().is_tagged_int()); - assert!(!Pointer::false_singleton().is_tagged_int()); - assert!(!Pointer::nil_singleton().is_tagged_int()); - assert!(!Pointer::undefined_singleton().is_tagged_int()); - assert!(Pointer::int(42).is_tagged_int()); - assert!(Pointer::int(42).is_tagged_int()); - } - - #[test] - fn test_pointer_is_boolean() { - assert!(Pointer::true_singleton().is_boolean()); - assert!(Pointer::false_singleton().is_boolean()); - assert!(!Pointer::nil_singleton().is_boolean()); - assert!(!Pointer::int(24).is_boolean()); - assert!(!Pointer::int(24).is_boolean()); - } - - #[test] - fn test_class_new() { - let class = Class::alloc("A".to_string(), 0, 24); - - assert_eq!(class.method_slots, 0); - assert_eq!(class.instance_size, 24); - - Class::drop(class); - } - - #[test] - fn test_class_new_object() { - let class = Class::object("A".to_string(), 1, 0); - - assert_eq!(class.method_slots, 0); - assert_eq!(class.instance_size, 24); - - Class::drop(class); - } - - #[test] - fn test_class_new_process() { - let class = Class::process("A".to_string(), 1, 0); - - assert_eq!(class.method_slots, 0); - - Class::drop(class); - } - - #[test] - fn test_class_of() { - let space = PermanentSpace::new(0, 0, Default::default()); - let string = String::alloc(space.string_class(), "A".to_string()); - let perm_string = space.allocate_string("B".to_string()); - - assert!(Class::of(&space, Pointer::int(42)) == space.int_class()); - assert!( - Class::of(&space, Pointer::true_singleton()) - == space.boolean_class() - ); - assert!( - Class::of(&space, Pointer::false_singleton()) - == space.boolean_class() - ); - assert!( - Class::of(&space, Pointer::nil_singleton()) == space.nil_class() - ); - assert!(Class::of(&space, string) == space.string_class()); - assert!(Class::of(&space, perm_string) == space.string_class()); - - unsafe { - String::drop_and_deallocate(string); - }; - } - - #[test] - fn test_class_methods() { - let mod_class = - OwnedClass::new(Class::object("foo_mod".to_string(), 0, 2)); - let module = empty_module(*mod_class); - let foo = empty_method(); - let bar = empty_method(); - let index0 = MethodIndex::new(0); - let index1 = MethodIndex::new(1); - - unsafe { - module.header.class.set_method(index0, foo); - module.header.class.set_method(index1, bar); - - assert_eq!( - module.header.class.get_method(index0).as_ptr(), - foo.as_ptr() - ); - - assert_eq!( - module.header.class.get_method(index1).as_ptr(), - bar.as_ptr() - ); - } - } - - #[test] - fn test_int_read() { - let class = OwnedClass::new(Class::object("Int".to_string(), 0, 0)); - let tagged = Int::alloc(*class, 42); - let max = Int::alloc(*class, i64::MAX); - let min = Int::alloc(*class, i64::MIN); - - assert_eq!(unsafe { Int::read(tagged) }, 42); - assert_eq!(unsafe { Int::read(max) }, i64::MAX); - assert_eq!(unsafe { Int::read(min) }, i64::MIN); - - unsafe { - min.free(); - max.free(); - } - } - - #[test] - fn test_int_read_u64() { - let class = OwnedClass::new(Class::object("Int".to_string(), 0, 0)); - let tagged = Int::alloc(*class, 42); - let max = Int::alloc(*class, i64::MAX); - let min = Int::alloc(*class, i64::MIN); - - assert_eq!(unsafe { Int::read_u64(tagged) }, 42); - assert_eq!(unsafe { Int::read_u64(max) }, i64::MAX as u64); - assert_eq!(unsafe { Int::read_u64(min) }, 0); - - unsafe { - min.free(); - max.free(); - } - } -} diff --git a/vm/src/numeric.rs b/vm/src/numeric.rs deleted file mode 100644 index 3be9392e9..000000000 --- a/vm/src/numeric.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Traits and functions for various numerical operations. - -/// The Modulo trait is used for getting the modulo (instead of remainder) of a -/// number. -pub(crate) trait Modulo { - fn checked_modulo(self, rhs: T) -> Option; -} - -impl Modulo for i64 { - fn checked_modulo(self, rhs: i64) -> Option { - self.checked_rem(rhs) - .and_then(|res| res.checked_add(rhs)) - .and_then(|res| res.checked_rem(rhs)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_modulo_i64() { - assert_eq!((-5_i64).checked_modulo(86_400_i64), Some(86395_i64)); - assert_eq!(i64::MIN.checked_modulo(-1_i64), None); - } -} diff --git a/vm/src/permanent_space.rs b/vm/src/permanent_space.rs deleted file mode 100644 index 4b6f3eb51..000000000 --- a/vm/src/permanent_space.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! Types for memory that stays around permanently. -use crate::chunk::Chunk; -use crate::indexes::ClassIndex; -use crate::mem::{ - Array, ByteArray, Class, ClassPointer, Float, Int, Module, ModulePointer, - Object, Pointer, String as InkoString, -}; -use crate::process::Future; -use ahash::AHashMap; -use std::mem::size_of; -use std::ops::Drop; -use std::sync::Mutex; - -pub(crate) const INT_CLASS: usize = 0; -pub(crate) const FLOAT_CLASS: usize = 1; -pub(crate) const STRING_CLASS: usize = 2; -pub(crate) const ARRAY_CLASS: usize = 3; -pub(crate) const BOOLEAN_CLASS: usize = 4; -pub(crate) const NIL_CLASS: usize = 5; -pub(crate) const BYTE_ARRAY_CLASS: usize = 6; -pub(crate) const FUTURE_CLASS: usize = 7; - -/// The total number of built-in classes. -const BUILTIN_CLASS_COUNT: usize = FUTURE_CLASS + 1; - -/// Allocates a new class, returning a tuple containing the owned pointer and a -/// permanent reference pointer. -macro_rules! class { - ($name: expr, $methods: expr, $size_source: ident) => {{ - Class::alloc($name.to_string(), $methods, size_of::<$size_source>()) - }}; -} - -macro_rules! get_class { - ($space: expr, $index: expr) => { - unsafe { *$space.classes.get($index) } - }; -} - -/// The number of methods used for the various built-in classes. -/// -/// These counts are used to determine how much memory is needed for allocating -/// the various built-in classes. -#[derive(Default)] -pub(crate) struct MethodCounts { - pub int_class: u16, - pub float_class: u16, - pub string_class: u16, - pub array_class: u16, - pub boolean_class: u16, - pub nil_class: u16, - pub byte_array_class: u16, - pub future_class: u16, -} - -/// Memory that sticks around for a program's lifetime (aka permanently). -pub(crate) struct PermanentSpace { - /// All classes defined by the running program. - /// - /// Classes are all stored in the same list, making it easy to efficiently - /// access them from any module; regardless of what module defined the - /// class. - /// - /// We use a Chunk here for the following reasons: - /// - /// 1. The list never grows beyond the size specified in the bytecode. - /// 2. We need to be able to (concurrently set classes in any order while - /// parsing bytecode. - /// 3. We don't want locking. While parsing bytecode we only set values, and - /// after parsing bytecode we only read values. - /// - /// The first N (see the value of BUILTIN_CLASS_COUNT) fields are reserved - /// for built-in classes. Some of these values may be left empty to - /// accomodate for potential future built-in classes. - classes: Chunk, - - /// All modules that are available to the current program. - modules: Chunk, - - /// A map of strings and their heap allocated Inko strings. - /// - /// This map is used to ensure that different occurrences of the same string - /// literal all use the same heap object. - interned_strings: Mutex>, - - /// Permanently allocated objects (excluding classes) to deallocate when the - /// program terminates. - /// - /// This list doesn't include interned strings, as those are stored - /// separately. - permanent_objects: Mutex>, -} - -unsafe impl Sync for PermanentSpace {} - -impl PermanentSpace { - pub(crate) fn new( - modules: u32, - classes: u32, - counts: MethodCounts, - ) -> Self { - let int_class = class!("Int", counts.int_class, Int); - let float_class = class!("Float", counts.float_class, Float); - let str_class = class!("String", counts.string_class, InkoString); - let ary_class = class!("Array", counts.array_class, Array); - let bool_class = class!("Bool", counts.boolean_class, Object); - let nil_class = class!("Nil", counts.nil_class, Object); - let bary_class = - class!("ByteArray", counts.byte_array_class, ByteArray); - - let fut_class = class!("Future", counts.future_class, Future); - let mut classes = Chunk::new(classes as usize + BUILTIN_CLASS_COUNT); - let modules = Chunk::new(modules as usize); - - unsafe { - classes.set(INT_CLASS, int_class); - classes.set(FLOAT_CLASS, float_class); - classes.set(STRING_CLASS, str_class); - classes.set(ARRAY_CLASS, ary_class); - classes.set(BOOLEAN_CLASS, bool_class); - classes.set(NIL_CLASS, nil_class); - classes.set(BYTE_ARRAY_CLASS, bary_class); - classes.set(FUTURE_CLASS, fut_class); - } - - Self { - interned_strings: Mutex::new(AHashMap::default()), - modules, - classes, - permanent_objects: Mutex::new(Vec::new()), - } - } - - pub(crate) fn int_class(&self) -> ClassPointer { - get_class!(self, INT_CLASS) - } - - pub(crate) fn float_class(&self) -> ClassPointer { - get_class!(self, FLOAT_CLASS) - } - - pub(crate) fn string_class(&self) -> ClassPointer { - get_class!(self, STRING_CLASS) - } - - pub(crate) fn array_class(&self) -> ClassPointer { - get_class!(self, ARRAY_CLASS) - } - - pub(crate) fn boolean_class(&self) -> ClassPointer { - get_class!(self, BOOLEAN_CLASS) - } - - pub(crate) fn nil_class(&self) -> ClassPointer { - get_class!(self, NIL_CLASS) - } - - pub(crate) fn byte_array_class(&self) -> ClassPointer { - get_class!(self, BYTE_ARRAY_CLASS) - } - - pub(crate) fn future_class(&self) -> ClassPointer { - get_class!(self, FUTURE_CLASS) - } - - /// Interns a permanent string. - /// - /// If an Inko String has already been allocated for the given Rust String, - /// the existing Inko String is returned; otherwise a new one is created. - pub(crate) fn allocate_string(&self, string: String) -> Pointer { - let mut strings = self.interned_strings.lock().unwrap(); - - if let Some(ptr) = strings.get(&string) { - return *ptr; - } - - let ptr = InkoString::alloc(self.string_class(), string.clone()) - .as_permanent(); - - strings.insert(string, ptr); - ptr - } - - pub(crate) fn allocate_int(&self, value: i64) -> Pointer { - let ptr = Int::alloc(self.int_class(), value); - - if ptr.is_tagged_int() { - ptr - } else { - let ptr = ptr.as_permanent(); - - self.permanent_objects.lock().unwrap().push(ptr); - ptr - } - } - - pub(crate) fn allocate_float(&self, value: f64) -> Pointer { - let ptr = Float::alloc(self.float_class(), value).as_permanent(); - - self.permanent_objects.lock().unwrap().push(ptr); - ptr - } - - pub(crate) fn allocate_array(&self, value: Vec) -> Pointer { - let ptr = Array::alloc(self.array_class(), value).as_permanent(); - - self.permanent_objects.lock().unwrap().push(ptr); - ptr - } - - /// Adds a class in the global class list. - /// - /// This method is unsafe because it allows unsynchronised write access to - /// the class list. In practise this is fine because the bytecode parser - /// doesn't set the same class twice, and we never resize the class list. - /// For the sake of clarity we have marked this method as unsafe anyway. - #[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ref_to_mut))] - pub(crate) unsafe fn add_class( - &self, - raw_index: u32, - class: ClassPointer, - ) -> Result<(), String> { - let index = raw_index as usize; - - if index >= self.classes.len() { - return Err(format!( - "Unable to define class {:?}, as the class index {} is out of bounds", - class.name, - index - )); - } - - let existing = *self.classes.get(index); - - if !existing.as_ptr().is_null() { - return Err(format!( - "Can't store class {:?} in index {}, as it's already used by class {:?}", - class.name, - index, - existing.name - )); - } - - let self_mut = &mut *(self as *const _ as *mut PermanentSpace); - - self_mut.classes.set(index, class); - Ok(()) - } - - /// Adds a module in the global module list. - /// - /// This method is unsafe for the same reasons as - /// `PermanentSpace::add_class()`. - #[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ref_to_mut))] - pub(crate) unsafe fn add_module( - &self, - raw_index: u32, - module: ModulePointer, - ) -> Result<(), String> { - let index = raw_index as usize; - - if index >= self.modules.len() { - return Err(format!( - "Unable to define module {:?}, as the module index {} is out of bounds", - module.name(), - index - )); - } - - let existing = *self.modules.get(index); - - if !existing.as_pointer().untagged_ptr().is_null() { - return Err(format!( - "Can't store module {:?} in index {}, as it's already used by module {:?}", - module.name(), - index, - existing.name() - )); - } - - let self_mut = &mut *(self as *const _ as *mut PermanentSpace); - - self_mut.modules.set(index, module); - Ok(()) - } - - pub unsafe fn get_class(&self, index: ClassIndex) -> ClassPointer { - *self.classes.get(index.into()) - } -} - -impl Drop for PermanentSpace { - fn drop(&mut self) { - unsafe { - for pointer in self.interned_strings.lock().unwrap().values() { - InkoString::drop_and_deallocate(*pointer); - } - - for ptr in self.permanent_objects.lock().unwrap().iter() { - ptr.free(); - } - - for index in 0..self.modules.len() { - let ptr = *self.modules.get(index); - - if !ptr.as_pointer().untagged_ptr().is_null() { - Module::drop_and_deallocate(ptr); - } - } - - for index in 0..self.classes.len() { - let ptr = *self.classes.get(index); - - if !ptr.as_ptr().is_null() { - Class::drop(ptr); - } - } - } - - // The singleton objects can't contain any sub values, so they don't - // need to be dropped explicitly. - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mem::{ - Array, ByteArray, Float, Int, Object, String as InkoString, - }; - use crate::process::Future; - use std::mem::size_of; - - #[test] - fn test_class_instance_sizes() { - let perm = PermanentSpace::new(0, 0, MethodCounts::default()); - - assert_eq!(perm.int_class().instance_size, size_of::()); - assert_eq!(perm.float_class().instance_size, size_of::()); - assert_eq!(perm.string_class().instance_size, size_of::()); - assert_eq!(perm.array_class().instance_size, size_of::()); - assert_eq!(perm.boolean_class().instance_size, size_of::()); - assert_eq!(perm.nil_class().instance_size, size_of::()); - assert_eq!( - perm.byte_array_class().instance_size, - size_of::() - ); - assert_eq!(perm.future_class().instance_size, size_of::()); - } -} diff --git a/vm/src/platform.rs b/vm/src/platform.rs deleted file mode 100644 index 57066ec16..000000000 --- a/vm/src/platform.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Basic platform detection. -//! -//! This module provides basic functionality for detecting the underlying -//! platform. - -/// Returns an identifier for the underlying operating system. -pub(crate) fn operating_system() -> i64 { - if cfg!(target_os = "windows") { - 0 - } else if cfg!(target_os = "macos") { - 1 - } else if cfg!(target_os = "ios") { - 2 - } else if cfg!(target_os = "linux") { - 3 - } else if cfg!(target_os = "android") { - 4 - } else if cfg!(target_os = "freebsd") { - 5 - } else if cfg!(target_os = "dragonfly") { - 6 - } else if cfg!(target_os = "bitrig") { - 7 - } else if cfg!(target_os = "openbsd") { - 8 - } else if cfg!(target_os = "netbsd") { - 9 - } else if cfg!(unix) { - 10 - } else { - 11 - } -} - -#[cfg(test)] -mod tests { - macro_rules! test_operating_system { - ($platform: expr, $code: expr) => { - #[cfg(target_os = $platform)] - #[test] - fn test_operating_system() { - assert_eq!(super::operating_system(), $code); - } - }; - } - - test_operating_system!("windows", 0); - test_operating_system!("macos", 1); - test_operating_system!("ios", 2); - test_operating_system!("linux", 3); - test_operating_system!("android", 4); - test_operating_system!("freebsd", 5); - test_operating_system!("dragonfly", 6); - test_operating_system!("bitrig", 7); - test_operating_system!("openbsd", 8); - test_operating_system!("netbsd", 9); - - #[cfg(not(any( - target_os = "windows", - target_os = "macos", - target_os = "ios", - target_os = "linux", - target_os = "android", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "bitrig", - target_os = "openbsd", - target_os = "netbsd" - )))] - #[test] - fn test_operating_system() { - assert_eq!(super::operating_system(), 11); - } -} diff --git a/vm/src/process.rs b/vm/src/process.rs deleted file mode 100644 index 40d67d3c5..000000000 --- a/vm/src/process.rs +++ /dev/null @@ -1,1601 +0,0 @@ -use crate::arc_without_weak::ArcWithoutWeak; -use crate::execution_context::ExecutionContext; -use crate::indexes::{FieldIndex, MethodIndex}; -use crate::location_table::Location; -use crate::mem::{allocate, ClassPointer, Header, MethodPointer, Pointer}; -use crate::scheduler::timeouts::Timeout; -use std::alloc::{alloc, dealloc, handle_alloc_error, Layout}; -use std::collections::VecDeque; -use std::mem::{align_of, size_of, swap}; -use std::ops::Drop; -use std::ops::{Deref, DerefMut}; -use std::ptr::{copy_nonoverlapping, drop_in_place, NonNull}; -use std::slice; -use std::sync::{Mutex, MutexGuard}; - -/// The shared state of a future. -pub(crate) struct FutureState { - /// The process that's waiting for this future to complete. - pub(crate) consumer: Option, - - /// The result of a message. - /// - /// This defaults to the undefined singleton. - result: Pointer, - - /// A boolean indicating that the result was produced by throwing an error, - /// instead of returning it. - thrown: bool, - - /// A boolean indicating the future is disconnected. - /// - /// This flag is set whenever a reader or writer is dropped. - disconnected: bool, -} - -impl FutureState { - pub(crate) fn new() -> RcFutureState { - let state = Self { - consumer: None, - result: Pointer::undefined_singleton(), - thrown: false, - disconnected: false, - }; - - ArcWithoutWeak::new(Mutex::new(state)) - } - - pub(crate) fn has_result(&self) -> bool { - self.result != Pointer::undefined_singleton() - } -} - -type RcFutureState = ArcWithoutWeak>; - -impl RcFutureState { - /// Writes the result of a message to this future. - /// - /// If the future has been disconnected, an Err is returned that contains - /// the value that we tried to write. - /// - /// The returned tuple contains a value that indicates if a process needs to - /// be rescheduled, and a pointer to the process that sent the message. - pub(crate) fn write(&self, result: Pointer, thrown: bool) -> WriteResult { - let mut future = self.lock().unwrap(); - - if future.disconnected { - return WriteResult::Discard; - } - - future.thrown = thrown; - future.result = result; - - if let Some(consumer) = future.consumer.take() { - match consumer.state.lock().unwrap().try_reschedule_for_future() { - RescheduleRights::Failed => WriteResult::Continue, - RescheduleRights::Acquired => WriteResult::Reschedule(consumer), - RescheduleRights::AcquiredWithTimeout => { - WriteResult::RescheduleWithTimeout(consumer) - } - } - } else { - WriteResult::Continue - } - } - - /// Gets the value from this future. - /// - /// When a None is returned, the consumer must stop running any code. It's - /// then up to a producer to reschedule the consumer when writing a value to - /// the future. - pub(crate) fn get( - &self, - consumer: ProcessPointer, - timeout: Option>, - ) -> FutureResult { - // The locking order is important here: we _must_ lock the future - // _before_ locking the consumer. If we lock the consumer first, we - // could deadlock with processes writing to this future. - let mut future = self.lock().unwrap(); - let mut state = consumer.state.lock().unwrap(); - let result = future.result; - - if result != Pointer::undefined_singleton() { - future.consumer = None; - future.result = Pointer::undefined_singleton(); - - state.status.set_waiting_for_future(false); - - return if future.thrown { - FutureResult::Thrown(result) - } else { - FutureResult::Returned(result) - }; - } - - future.consumer = Some(consumer); - - state.waiting_for_future(timeout); - FutureResult::None - } -} - -/// A method scheduled for execution at some point in the future. -/// -/// The size of this type depends on the number of arguments. Using this -/// approach allows us to keep the size of a message as small as possible, -/// without the need for allocating arguments separately. -#[repr(C)] -struct ScheduledMethodInner { - /// The method to run. - method: MethodIndex, - - /// The number of arguments of this message. - length: u8, - - /// The arguments of the message. - arguments: [Pointer; 0], -} - -/// An owned pointer to a ScheduledMethodInner. -struct ScheduledMethod(NonNull); - -impl ScheduledMethod { - fn new(method: MethodIndex, arguments: Vec) -> Self { - unsafe { - let layout = Self::layout(arguments.len() as u8); - let raw_ptr = alloc(layout) as *mut ScheduledMethodInner; - - if raw_ptr.is_null() { - handle_alloc_error(layout); - } - - let msg = &mut *raw_ptr; - - init!(msg.method => method); - init!(msg.length => arguments.len() as u8); - - copy_nonoverlapping( - arguments.as_ptr(), - msg.arguments.as_mut_ptr(), - arguments.len(), - ); - - Self(NonNull::new_unchecked(raw_ptr)) - } - } - - unsafe fn layout(length: u8) -> Layout { - let size = size_of::() - + (length as usize * size_of::()); - - // Messages are sent often, so we don't want the overhead of size and - // alignment checks. - Layout::from_size_align_unchecked(size, align_of::()) - } -} - -impl Deref for ScheduledMethod { - type Target = ScheduledMethodInner; - - fn deref(&self) -> &ScheduledMethodInner { - unsafe { self.0.as_ref() } - } -} - -impl Drop for ScheduledMethod { - fn drop(&mut self) { - unsafe { - let layout = Self::layout(self.0.as_ref().length); - - drop_in_place(self.0.as_ptr()); - dealloc(self.0.as_ptr() as *mut u8, layout); - } - } -} - -/// A message sent between two processes. -struct Message { - write: Write, - scheduled: ScheduledMethod, -} - -impl Message { - fn new( - method: MethodIndex, - write: Write, - arguments: Vec, - ) -> Message { - let scheduled = ScheduledMethod::new(method, arguments); - - Message { write, scheduled } - } -} - -/// A collection of messages to be processed by a process. -struct Mailbox { - messages: VecDeque, -} - -impl Mailbox { - fn new() -> Self { - Mailbox { messages: VecDeque::new() } - } - - fn send(&mut self, message: Message) { - self.messages.push_back(message); - } - - fn receive(&mut self) -> Option { - self.messages.pop_front() - } -} - -/// A type indicating how the results of a task should be communicated with the -/// consumer. -pub(crate) enum Write { - /// The result of a task is to be discarded. - Discard, - - /// The consumer/sender is suspended and waiting for a result, without using - /// a future. - Direct(ProcessPointer), - - /// The consumer scheduled the message without immediately waiting for it. - /// Instead of writing the result directly to the consumer, we write it to a - /// future. - Future(RcFutureState), -} - -/// A task to run in response to a message. -pub(crate) struct Task { - /// The execution context/call stack of this task. - pub(crate) context: Box, - - /// The stack of arguments for the next instruction. - /// - /// Certain instructions use a variable number of arguments, such as method - /// calls. For these instructions we use a stack, as the VM's instructions - /// are of a fixed size. - /// - /// The ordering of values may differ per instruction: for method calls - /// arguments should be pushed in reverse order, so the first pop() returns - /// the first argument instead of the last one. For other instructions the - /// values are in-order, meaning the first value in the stack is the first - /// value to use. - /// - /// In the past we used to put registers next to each other, and specified - /// the first register and the number of arguments. The VM would then read - /// all arguments in sequence. While this removes the need for a stack, it - /// complicated code generation enough that we decided to move away from it. - pub(crate) stack: Vec, - pub(crate) write: Write, -} - -impl Task { - pub(crate) fn new(context: ExecutionContext, write: Write) -> Self { - Self { context: Box::new(context), write, stack: Vec::new() } - } - - pub(crate) fn take_arguments(&mut self) -> Vec { - let mut stack = Vec::new(); - - swap(&mut stack, &mut self.stack); - stack - } - - pub(crate) fn clear_arguments(&mut self) { - self.stack.clear(); - } - - /// Adds a new execution context to the stack. - /// - /// The parent of the new context is set to the context before the push. - pub(crate) fn push_context(&mut self, new_context: ExecutionContext) { - let mut boxed = Box::new(new_context); - let target = &mut self.context; - - swap(target, &mut boxed); - target.parent = Some(boxed); - } - - /// Pops the current execution context off the stack. - /// - /// If all contexts have been popped, `true` is returned. - pub(crate) fn pop_context(&mut self) -> bool { - let context = &mut self.context; - - if let Some(parent) = context.parent.take() { - *context = parent; - false - } else { - true - } - } - - pub(crate) fn contexts(&self) -> Vec<&ExecutionContext> { - self.context.contexts().collect::>() - } -} - -/// A pointer to a Task. -/// -/// In various places we borrow a Task while also borrowing its fields, or while -/// borrowing fields of the owning Process. This can't be expressed safely in -/// Rust's type system, and working around this requires fiddling with pointer -/// casts and unsafe code. -/// -/// To make this less painful to deal with, instead of using a `&mut Task` in -/// various places we use a `TaskPointer`. This won't give us any extra safety, -/// but at least it's less annoying to deal with. -#[repr(transparent)] -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) struct TaskPointer(Pointer); - -impl TaskPointer { - fn new(pointer: &Task) -> Self { - Self(Pointer::new(pointer as *const _ as *mut _)) - } -} - -impl Deref for TaskPointer { - type Target = Task; - - fn deref(&self) -> &Task { - unsafe { &*(self.0.as_ptr() as *mut Task) } - } -} - -impl DerefMut for TaskPointer { - fn deref_mut(&mut self) -> &mut Task { - unsafe { &mut *(self.0.as_ptr() as *mut Task) } - } -} - -/// The status of a process, represented as a set of bits. -pub(crate) struct ProcessStatus { - /// The bits used to indicate the status of the process. - /// - /// Multiple bits may be set in order to combine different statuses. - bits: u8, -} - -impl ProcessStatus { - /// A regular process. - const NORMAL: u8 = 0b00_0000; - - /// The main process. - const MAIN: u8 = 0b00_0001; - - /// The process is waiting for a message. - const WAITING_FOR_MESSAGE: u8 = 0b00_0010; - - /// The process is waiting for a future. - const WAITING_FOR_FUTURE: u8 = 0b00_0100; - - /// The process is waiting for an IO operation to complete. - const WAITING_FOR_IO: u8 = 0b00_1000; - - /// The process is simply sleeping for a certain amount of time. - const SLEEPING: u8 = 0b01_0000; - - /// The process was rescheduled after a timeout expired. - const TIMEOUT_EXPIRED: u8 = 0b10_0000; - - /// The process is waiting for something, or suspended for a period of time. - const WAITING: u8 = - Self::WAITING_FOR_FUTURE | Self::SLEEPING | Self::WAITING_FOR_IO; - - pub(crate) fn new() -> Self { - Self { bits: Self::NORMAL } - } - - fn set_main(&mut self) { - self.update_bits(Self::MAIN, true); - } - - fn is_main(&self) -> bool { - self.bit_is_set(Self::MAIN) - } - - fn set_waiting_for_message(&mut self, enable: bool) { - self.update_bits(Self::WAITING_FOR_MESSAGE, enable); - } - - fn is_waiting_for_message(&self) -> bool { - self.bit_is_set(Self::WAITING_FOR_MESSAGE) - } - - fn set_waiting_for_future(&mut self, enable: bool) { - self.update_bits(Self::WAITING_FOR_FUTURE, enable); - } - - fn set_waiting_for_io(&mut self, enable: bool) { - self.update_bits(Self::WAITING_FOR_IO, enable); - } - - fn is_waiting_for_io(&self) -> bool { - self.bit_is_set(Self::WAITING_FOR_IO) - } - - fn is_waiting_for_future(&self) -> bool { - self.bit_is_set(Self::WAITING_FOR_FUTURE) - } - - fn is_waiting(&self) -> bool { - (self.bits & Self::WAITING) != 0 - } - - fn no_longer_waiting(&mut self) { - self.update_bits(Self::WAITING, false); - } - - fn set_timeout_expired(&mut self, enable: bool) { - self.update_bits(Self::TIMEOUT_EXPIRED, enable) - } - - fn set_sleeping(&mut self, enable: bool) { - self.update_bits(Self::SLEEPING, enable); - } - - fn timeout_expired(&self) -> bool { - self.bit_is_set(Self::TIMEOUT_EXPIRED) - } - - fn update_bits(&mut self, mask: u8, enable: bool) { - self.bits = if enable { self.bits | mask } else { self.bits & !mask }; - } - - fn bit_is_set(&self, bit: u8) -> bool { - self.bits & bit == bit - } -} - -/// An enum describing what rights a thread was given when trying to reschedule -/// a process. -#[derive(Eq, PartialEq, Debug)] -pub(crate) enum RescheduleRights { - /// The rescheduling rights were not obtained. - Failed, - - /// The rescheduling rights were obtained. - Acquired, - - /// The rescheduling rights were obtained, and the process was using a - /// timeout. - AcquiredWithTimeout, -} - -impl RescheduleRights { - pub(crate) fn are_acquired(&self) -> bool { - !matches!(self, RescheduleRights::Failed) - } -} - -/// The shared state of a process. -/// -/// This state is shared by both the process and its clients. -pub(crate) struct ProcessState { - /// The mailbox of this process. - mailbox: Mailbox, - - /// The status of the process. - status: ProcessStatus, - - /// The timeout this process is suspended with, if any. - /// - /// If missing and the process is suspended, it means the process is - /// suspended indefinitely. - timeout: Option>, -} - -impl ProcessState { - pub(crate) fn new() -> Self { - Self { - mailbox: Mailbox::new(), - status: ProcessStatus::new(), - timeout: None, - } - } - - pub(crate) fn has_same_timeout( - &self, - timeout: &ArcWithoutWeak, - ) -> bool { - self.timeout - .as_ref() - .map(|t| t.as_ptr() == timeout.as_ptr()) - .unwrap_or(false) - } - - pub(crate) fn try_reschedule_after_timeout(&mut self) -> RescheduleRights { - if !self.status.is_waiting() { - return RescheduleRights::Failed; - } - - if self.status.is_waiting_for_future() - || self.status.is_waiting_for_io() - { - // We may be suspended for some time without actually waiting for - // anything, in that case we don't want to update the process - // status. - self.status.set_timeout_expired(true); - } - - self.status.no_longer_waiting(); - - if self.timeout.take().is_some() { - RescheduleRights::AcquiredWithTimeout - } else { - RescheduleRights::Acquired - } - } - - pub(crate) fn waiting_for_future( - &mut self, - timeout: Option>, - ) { - self.timeout = timeout; - - self.status.set_waiting_for_future(true); - } - - pub(crate) fn waiting_for_io( - &mut self, - timeout: Option>, - ) { - self.timeout = timeout; - - self.status.set_waiting_for_io(true); - } - - fn try_reschedule_for_message(&mut self) -> RescheduleRights { - if !self.status.is_waiting_for_message() { - return RescheduleRights::Failed; - } - - self.status.set_waiting_for_message(false); - RescheduleRights::Acquired - } - - fn try_reschedule_for_future(&mut self) -> RescheduleRights { - if !self.status.is_waiting_for_future() { - return RescheduleRights::Failed; - } - - self.status.set_waiting_for_future(false); - - if self.timeout.take().is_some() { - RescheduleRights::AcquiredWithTimeout - } else { - RescheduleRights::Acquired - } - } - - pub(crate) fn try_reschedule_for_io(&mut self) -> RescheduleRights { - if !self.status.is_waiting_for_io() { - return RescheduleRights::Failed; - } - - self.status.set_waiting_for_io(false); - - if self.timeout.take().is_some() { - RescheduleRights::AcquiredWithTimeout - } else { - RescheduleRights::Acquired - } - } -} - -/// A lightweight process. -#[repr(C)] -pub(crate) struct Process { - pub(crate) header: Header, - - /// A boolean indicating that the result was thrown rather than returned. - thrown: bool, - - /// The currently running task, if any. - task: Option, - - /// The last value returned or thrown. - result: Pointer, - - /// The internal shared state of the process. - state: Mutex, - - /// The fields of this process. - /// - /// The length of this flexible array is derived from the number of - /// fields defined in this process' class. - fields: [Pointer; 0], -} - -impl Process { - pub(crate) fn drop_and_deallocate(ptr: ProcessPointer) { - unsafe { - drop_in_place(ptr.as_pointer().as_ptr() as *mut Self); - ptr.as_pointer().free(); - } - } - - pub(crate) fn alloc(class: ClassPointer) -> ProcessPointer { - let ptr = Pointer::new(allocate(unsafe { class.instance_layout() })); - let obj = unsafe { ptr.get_mut::() }; - let mut state = ProcessState::new(); - - // Processes start without any messages, so we must ensure their status - // is set accordingly. - state.status.set_waiting_for_message(true); - - obj.header.init_atomic(class); - - init!(obj.thrown => false); - init!(obj.result => Pointer::undefined_singleton()); - init!(obj.state => Mutex::new(state)); - init!(obj.task => None); - - unsafe { ProcessPointer::from_pointer(ptr) } - } - - /// Returns a new Process acting as the main process. - /// - /// This process always runs on the main thread. - pub(crate) fn main( - class: ClassPointer, - method: MethodPointer, - ) -> ProcessPointer { - let mut process = Self::alloc(class); - let mut task = Task::new(ExecutionContext::new(method), Write::Discard); - - task.stack.push(process.as_pointer()); - - process.task = Some(task); - - // The main process always has an initial message, so we need to reset - // this particular status. - process.state().status.set_waiting_for_message(false); - process.set_main(); - process - } - - pub(crate) fn set_main(&mut self) { - self.state.lock().unwrap().status.set_main(); - } - - pub(crate) fn is_main(&self) -> bool { - self.state.lock().unwrap().status.is_main() - } - - /// Suspends this process for a period of time. - /// - /// A process is sleeping when it simply isn't to be scheduled for a while, - /// without waiting for a message, future, or something else. - pub(crate) fn suspend(&mut self, timeout: ArcWithoutWeak) { - let mut state = self.state.lock().unwrap(); - - state.timeout = Some(timeout); - - state.status.set_sleeping(true); - } - - /// Sends a synchronous message to this process. - pub(crate) fn send_message( - &mut self, - method: MethodIndex, - sender: ProcessPointer, - arguments: Vec, - wait: bool, - ) -> RescheduleRights { - let write = if wait { Write::Direct(sender) } else { Write::Discard }; - let message = Message::new(method, write, arguments); - let mut state = self.state.lock().unwrap(); - - state.mailbox.send(message); - state.try_reschedule_for_message() - } - - /// Sends an asynchronous message to this process. - pub(crate) fn send_async_message( - &mut self, - method: MethodIndex, - future: RcFutureState, - arguments: Vec, - ) -> RescheduleRights { - let message = Message::new(method, Write::Future(future), arguments); - let mut state = self.state.lock().unwrap(); - - state.mailbox.send(message); - state.try_reschedule_for_message() - } - - /// Schedules a task to run, if none is scheduled already. - pub(crate) fn task_to_run(&mut self) -> Option { - let mut proc_state = self.state.lock().unwrap(); - - if let Some(task) = self.task.as_ref() { - return Some(TaskPointer::new(task)); - } - - let message = if let Some(message) = proc_state.mailbox.receive() { - message - } else { - proc_state.status.set_waiting_for_message(true); - - return None; - }; - - drop(proc_state); - - let method = - unsafe { self.header.class.get_method(message.scheduled.method) }; - let ctx = ExecutionContext::new(method); - let len = message.scheduled.length as usize; - let values = unsafe { - slice::from_raw_parts(message.scheduled.arguments.as_ptr(), len) - }; - - let mut task = Task::new(ctx, message.write); - - task.stack.extend_from_slice(values); - - self.task = Some(task); - - self.task.as_ref().map(TaskPointer::new) - } - - /// Finishes the exection of a task, and decides what to do next with this - /// process. - /// - /// If the return value is `true`, the process should be rescheduled. - pub(crate) fn finish_task(&mut self) -> bool { - let mut state = self.state.lock().unwrap(); - - self.task.take(); - - if state.mailbox.messages.is_empty() { - state.status.set_waiting_for_message(true); - false - } else { - true - } - } - - pub(crate) unsafe fn set_field( - &mut self, - index: FieldIndex, - value: Pointer, - ) { - self.fields.as_mut_ptr().add(index.into()).write(value); - } - - pub(crate) unsafe fn get_field(&self, index: FieldIndex) -> Pointer { - *self.fields.as_ptr().add(index.into()) - } - - pub(crate) fn timeout_expired(&self) -> bool { - let mut state = self.state.lock().unwrap(); - - if state.status.timeout_expired() { - state.status.set_timeout_expired(false); - true - } else { - false - } - } - - pub(crate) fn state(&self) -> MutexGuard { - self.state.lock().unwrap() - } - - pub(crate) fn stacktrace(&self) -> Vec { - let mut locations = Vec::new(); - - if let Some(task) = self.task.as_ref() { - for context in task.contexts() { - let mut index = context.index; - let ins = &context.method.instructions[index]; - - // When entering methods the index points to the instruction - // _after_ the call. For built-in function calls this isn't the - // case, as we store the current index instead so the call can - // be retried (e.g. when a socket operation) would block. - if index > 0 && !ins.opcode.rewind_before_call() { - index -= 1; - } - - if let Some(loc) = context.method.locations.get(index as u32) { - locations.push(loc); - } - } - - // The most recent frame should come first, not last. - locations.reverse(); - } - - locations - } - - pub(crate) fn thrown(&self) -> bool { - self.thrown - } - - pub(crate) fn set_return_value(&mut self, value: Pointer) { - self.result = value; - } - - pub(crate) fn set_throw_value(&mut self, value: Pointer) { - self.thrown = true; - self.result = value; - } - - pub(crate) fn move_result(&mut self) -> Pointer { - let result = self.result; - - self.result = Pointer::undefined_singleton(); - self.thrown = false; - - result - } -} - -/// A pointer to a process. -#[repr(transparent)] -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub(crate) struct ProcessPointer(NonNull); - -unsafe impl Sync for ProcessPointer {} -unsafe impl Send for ProcessPointer {} - -impl ProcessPointer { - pub(crate) unsafe fn from_pointer(pointer: Pointer) -> Self { - Self::new(pointer.as_ptr() as *mut Process) - } - - /// Returns a new pointer from a raw Pointer. - pub(crate) unsafe fn new(pointer: *mut Process) -> Self { - Self(NonNull::new_unchecked(pointer)) - } - - pub(crate) fn identifier(self) -> usize { - self.0.as_ptr() as usize - } - - pub(crate) fn as_pointer(self) -> Pointer { - Pointer::new(self.0.as_ptr() as *mut u8) - } -} - -impl Deref for ProcessPointer { - type Target = Process; - - fn deref(&self) -> &Process { - unsafe { &*self.0.as_ptr() } - } -} - -impl DerefMut for ProcessPointer { - fn deref_mut(&mut self) -> &mut Process { - unsafe { &mut *self.0.as_mut() } - } -} - -/// An enum describing the value produced by a future, and how it was produced. -#[derive(PartialEq, Eq, Debug)] -pub(crate) enum FutureResult { - /// No values has been produced so far. - None, - - /// The value was returned. - Returned(Pointer), - - /// The value was thrown and should be thrown again. - Thrown(Pointer), -} - -/// An enum that describes what a producer should do after writing to a future. -#[derive(PartialEq, Eq, Debug)] -pub(crate) enum WriteResult { - /// The future is disconnected, and the writer should discard the value it - /// tried to write. - Discard, - - /// A value was written, but no further action is needed. - Continue, - - /// A value was written, and the given process needs to be rescheduled. - Reschedule(ProcessPointer), - - /// A value was written, the given process needs to be rescheduled, and a - /// timeout needs to be invalidated. - RescheduleWithTimeout(ProcessPointer), -} - -/// Storage for a value to be computed some time in the future. -/// -/// A Future is essentially just a single-producer single-consumer queue, with -/// support for only writing a value once, and only reading it once. Futures are -/// used to send message results between processes. -#[repr(C)] -pub(crate) struct Future { - header: Header, - state: RcFutureState, -} - -impl Future { - pub(crate) fn alloc(class: ClassPointer, state: RcFutureState) -> Pointer { - let ptr = Pointer::new(allocate(Layout::new::())); - let obj = unsafe { ptr.get_mut::() }; - - obj.header.init(class); - init!(obj.state => state); - ptr - } - - pub(crate) unsafe fn drop(ptr: Pointer) { - drop_in_place(ptr.untagged_ptr() as *mut Self); - } - - pub(crate) fn get( - &self, - consumer: ProcessPointer, - timeout: Option>, - ) -> FutureResult { - self.state.get(consumer, timeout) - } - - pub(crate) fn lock(&self) -> MutexGuard { - self.state.lock().unwrap() - } - - pub(crate) fn disconnect(&self) -> Pointer { - let mut future = self.state.lock().unwrap(); - let result = future.result; - - future.disconnected = true; - future.result = Pointer::undefined_singleton(); - - result - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::location_table::Location; - use crate::mem::{Class, Method}; - use crate::test::{ - empty_class, empty_method, empty_process_class, new_process, - OwnedClass, OwnedProcess, - }; - use std::time::Duration; - - #[test] - fn test_message_new() { - let method = MethodIndex::new(0); - let future = FutureState::new(); - let write = Write::Future(future); - let message = - Message::new(method, write, vec![Pointer::int(1), Pointer::int(2)]); - - assert_eq!(0_u16, message.scheduled.method.into()); - assert_eq!(message.scheduled.length, 2); - - unsafe { - assert_eq!( - *message.scheduled.arguments.as_ptr().add(0), - Pointer::int(1) - ); - assert_eq!( - *message.scheduled.arguments.as_ptr().add(1), - Pointer::int(2) - ); - } - } - - #[test] - fn test_mailbox_send_receive() { - let method = MethodIndex::new(0); - let future = FutureState::new(); - let write = Write::Future(future); - let message = - Message::new(method, write, vec![Pointer::int(1), Pointer::int(2)]); - let mut mailbox = Mailbox::new(); - - mailbox.send(message); - - let message = mailbox.receive().unwrap(); - - assert_eq!(message.scheduled.length, 2); - } - - #[test] - fn test_process_status_new() { - let status = ProcessStatus::new(); - - assert_eq!(status.bits, 0); - } - - #[test] - fn test_process_status_set_main() { - let mut status = ProcessStatus::new(); - - status.set_main(); - - assert!(status.is_main()); - } - - #[test] - fn test_process_status_set_waiting_for_message() { - let mut status = ProcessStatus::new(); - - status.set_waiting_for_message(true); - assert!(status.is_waiting_for_message()); - - status.set_waiting_for_message(false); - assert!(!status.is_waiting_for_message()); - } - - #[test] - fn test_process_status_set_waiting_for_future() { - let mut status = ProcessStatus::new(); - - status.set_waiting_for_future(true); - assert!(status.is_waiting_for_future()); - - status.set_waiting_for_future(false); - assert!(!status.is_waiting_for_future()); - } - - #[test] - fn test_process_status_is_waiting() { - let mut status = ProcessStatus::new(); - - status.set_sleeping(true); - assert!(status.is_waiting()); - - status.set_sleeping(false); - status.set_waiting_for_future(true); - assert!(status.is_waiting()); - - status.no_longer_waiting(); - - assert!(!status.is_waiting_for_future()); - assert!(!status.is_waiting()); - } - - #[test] - fn test_process_status_timeout_expired() { - let mut status = ProcessStatus::new(); - - status.set_timeout_expired(true); - assert!(status.timeout_expired()); - - status.set_timeout_expired(false); - assert!(!status.timeout_expired()); - } - - #[test] - fn test_reschedule_rights_are_acquired() { - assert!(!RescheduleRights::Failed.are_acquired()); - assert!(RescheduleRights::Acquired.are_acquired()); - assert!(RescheduleRights::AcquiredWithTimeout.are_acquired()); - } - - #[test] - fn test_process_state_has_same_timeout() { - let mut state = ProcessState::new(); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - assert!(!state.has_same_timeout(&timeout)); - - state.timeout = Some(timeout.clone()); - - assert!(state.has_same_timeout(&timeout)); - } - - #[test] - fn test_process_state_try_reschedule_after_timeout() { - let mut state = ProcessState::new(); - - assert_eq!( - state.try_reschedule_after_timeout(), - RescheduleRights::Failed - ); - - state.waiting_for_future(None); - - assert_eq!( - state.try_reschedule_after_timeout(), - RescheduleRights::Acquired - ); - - assert!(!state.status.is_waiting_for_future()); - assert!(!state.status.is_waiting()); - - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - state.waiting_for_future(Some(timeout)); - - assert_eq!( - state.try_reschedule_after_timeout(), - RescheduleRights::AcquiredWithTimeout - ); - - assert!(!state.status.is_waiting_for_future()); - assert!(!state.status.is_waiting()); - } - - #[test] - fn test_process_state_waiting_for_future() { - let mut state = ProcessState::new(); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - state.waiting_for_future(None); - - assert!(state.status.is_waiting_for_future()); - assert!(state.timeout.is_none()); - - state.waiting_for_future(Some(timeout)); - - assert!(state.status.is_waiting_for_future()); - assert!(state.timeout.is_some()); - } - - #[test] - fn test_process_state_try_reschedule_for_message() { - let mut state = ProcessState::new(); - - assert_eq!( - state.try_reschedule_for_message(), - RescheduleRights::Failed - ); - - state.status.set_waiting_for_message(true); - - assert_eq!( - state.try_reschedule_for_message(), - RescheduleRights::Acquired - ); - assert!(!state.status.is_waiting_for_message()); - } - - #[test] - fn test_process_state_try_reschedule_for_future() { - let mut state = ProcessState::new(); - - assert_eq!(state.try_reschedule_for_future(), RescheduleRights::Failed); - - state.status.set_waiting_for_future(true); - assert_eq!( - state.try_reschedule_for_future(), - RescheduleRights::Acquired - ); - assert!(!state.status.is_waiting_for_future()); - - state.status.set_waiting_for_future(true); - state.timeout = Some(Timeout::with_rc(Duration::from_secs(0))); - - assert_eq!( - state.try_reschedule_for_future(), - RescheduleRights::AcquiredWithTimeout - ); - assert!(!state.status.is_waiting_for_future()); - } - - #[test] - fn test_process_new() { - let class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*class)); - - assert_eq!(process.header.class.as_ptr(), class.as_ptr()); - assert!(process.task.is_none()); - } - - #[test] - fn test_process_main() { - let proc_class = empty_process_class("A"); - let method = empty_method(); - let process = OwnedProcess::new(Process::main(*proc_class, method)); - - assert!(process.is_main()); - - Method::drop_and_deallocate(method); - } - - #[test] - fn test_process_set_main() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - - assert!(!process.is_main()); - - process.set_main(); - assert!(process.is_main()); - } - - #[test] - fn test_process_suspend() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - process.suspend(timeout); - - assert!(process.state().timeout.is_some()); - assert!(process.state().status.is_waiting()); - } - - #[test] - fn test_process_send_message() { - let proc_class = OwnedClass::new(Class::process("A".to_string(), 0, 1)); - let method = empty_method(); - let future = FutureState::new(); - let index = MethodIndex::new(0); - - unsafe { proc_class.set_method(index, method) }; - - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); - - process.send_async_message(index, future, vec![Pointer::int(42)]); - - let mut state = process.state(); - - assert_eq!(state.mailbox.messages.len(), 1); - assert_eq!(state.mailbox.receive().unwrap().scheduled.length, 1); - } - - #[test] - fn test_process_task_to_run_without_a_task() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - - assert!(process.task_to_run().is_none()); - } - - #[test] - fn test_process_task_to_run_waiting_server() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - - assert!(process.task_to_run().is_none()); - assert!(process.state().status.is_waiting_for_message()); - assert!(!process.state().status.is_waiting()); - } - - #[test] - fn test_process_task_to_run_with_message() { - let proc_class = OwnedClass::new(Class::process("A".to_string(), 0, 1)); - let method = empty_method(); - let future = FutureState::new(); - let index = MethodIndex::new(0); - - unsafe { proc_class.set_method(index, method) }; - - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); - - process.send_async_message(index, future, vec![Pointer::int(42)]); - - let mut task = process.task_to_run().unwrap(); - - assert!(process.task.is_some()); - assert!(task.stack.pop() == Some(Pointer::int(42))); - } - - #[test] - fn test_process_task_to_run_with_existing_task() { - let proc_class = OwnedClass::new(Class::process("A".to_string(), 0, 1)); - let method = empty_method(); - let ctx = ExecutionContext::new(method); - let task = Task::new(ctx, Write::Discard); - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); - - process.task = Some(task); - - assert_eq!( - process.task_to_run(), - process.task.as_ref().map(TaskPointer::new) - ); - - Method::drop_and_deallocate(method); - } - - #[test] - fn test_finish_task_with_existing_task() { - let proc_class = OwnedClass::new(Class::process("A".to_string(), 0, 1)); - let method = empty_method(); - let ctx = ExecutionContext::new(method); - let task = Task::new(ctx, Write::Discard); - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); - - process.task = Some(task); - process.finish_task(); - - assert!(process.task.is_none()); - - Method::drop_and_deallocate(method); - } - - #[test] - fn test_process_finish_task_without_pending_work() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - - process.set_main(); - - assert!(!process.finish_task()); - } - - #[test] - fn test_process_finish_task_with_clients() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - - assert!(!process.finish_task()); - assert!(process.state().status.is_waiting_for_message()); - } - - #[test] - fn test_process_finish_task_with_messages() { - let proc_class = OwnedClass::new(Class::process("A".to_string(), 0, 1)); - let method = empty_method(); - let future = FutureState::new(); - let index = MethodIndex::new(0); - - unsafe { proc_class.set_method(index, method) }; - - let mut process = OwnedProcess::new(Process::alloc(*proc_class)); - - process.send_async_message(index, future, Vec::new()); - - assert!(process.finish_task()); - } - - #[test] - fn test_process_get_set_field() { - let class = OwnedClass::new(Class::process("A".to_string(), 1, 0)); - let mut process = OwnedProcess::new(Process::alloc(*class)); - let idx = FieldIndex::new(0); - - unsafe { - process.set_field(idx, Pointer::int(4)); - - assert!(process.get_field(idx) == Pointer::int(4)); - } - } - - #[test] - fn test_process_timeout_expired() { - let class = empty_process_class("A"); - let mut process = OwnedProcess::new(Process::alloc(*class)); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - assert!(!process.timeout_expired()); - - process.suspend(timeout); - - assert!(!process.timeout_expired()); - assert!(!process.state().status.timeout_expired()); - } - - #[test] - fn test_process_pointer_identifier() { - let ptr = unsafe { ProcessPointer::new(0x4 as *mut _) }; - - assert_eq!(ptr.identifier(), 0x4); - } - - #[test] - fn test_process_pointer_as_pointer() { - let ptr = unsafe { ProcessPointer::new(0x4 as *mut _) }; - - assert_eq!(ptr.as_pointer(), Pointer::new(0x4 as *mut _)); - } - - #[test] - fn test_future_new() { - let fut_class = empty_class("Future"); - let state = FutureState::new(); - let future = Future::alloc(*fut_class, state); - - unsafe { - assert_eq!( - future.get::
().class.as_ptr(), - fut_class.as_ptr() - ); - } - - unsafe { - Future::drop(future); - future.free(); - } - } - - #[test] - fn test_future_write_without_consumer() { - let state = FutureState::new(); - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::Continue); - assert!(!state.lock().unwrap().thrown); - } - - #[test] - fn test_future_write_thrown() { - let state = FutureState::new(); - let result = state.write(Pointer::int(42), true); - - assert_eq!(result, WriteResult::Continue); - assert!(state.lock().unwrap().thrown); - } - - #[test] - fn test_future_write_disconnected() { - let state = FutureState::new(); - - state.lock().unwrap().disconnected = true; - - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::Discard); - } - - #[test] - fn test_future_write_with_consumer() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - - state.lock().unwrap().consumer = Some(*process); - - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::Continue); - } - - #[test] - fn test_future_write_with_waiting_consumer() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - - process.state().waiting_for_future(None); - state.lock().unwrap().consumer = Some(*process); - - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::Reschedule(*process)); - } - - #[test] - fn test_future_write_with_waiting_consumer_with_timeout() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - process.state().waiting_for_future(Some(timeout)); - state.lock().unwrap().consumer = Some(*process); - - let result = state.write(Pointer::int(42), false); - - assert_eq!(result, WriteResult::RescheduleWithTimeout(*process)); - assert!(!process.state().status.is_waiting_for_future()); - assert!(process.state().timeout.is_none()); - } - - #[test] - fn test_future_get_without_result() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - - assert_eq!(state.get(*process, None), FutureResult::None); - assert_eq!(state.lock().unwrap().consumer, Some(*process)); - assert!(process.state().status.is_waiting_for_future()); - } - - #[test] - fn test_future_get_without_result_with_timeout() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - let timeout = Timeout::with_rc(Duration::from_secs(0)); - - assert_eq!(state.get(*process, Some(timeout)), FutureResult::None); - assert_eq!(state.lock().unwrap().consumer, Some(*process)); - assert!(process.state().status.is_waiting_for_future()); - assert!(process.state().timeout.is_some()); - } - - #[test] - fn test_future_get_with_result() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - let value = Pointer::int(42); - - process.state().waiting_for_future(None); - state.lock().unwrap().result = value; - - assert_eq!(state.get(*process, None), FutureResult::Returned(value)); - assert!(state.lock().unwrap().consumer.is_none()); - assert!(!process.state().status.is_waiting_for_future()); - } - - #[test] - fn test_future_get_with_thrown_result() { - let proc_class = empty_process_class("A"); - let process = OwnedProcess::new(Process::alloc(*proc_class)); - let state = FutureState::new(); - let value = Pointer::int(42); - - process.state().waiting_for_future(None); - state.lock().unwrap().result = value; - state.lock().unwrap().thrown = true; - - assert_eq!(state.get(*process, None), FutureResult::Thrown(value)); - assert!(state.lock().unwrap().consumer.is_none()); - assert!(!process.state().status.is_waiting_for_future()); - } - - #[test] - fn test_future_disconnect() { - let fut_class = empty_class("Future"); - let state = FutureState::new(); - let fut = Future::alloc(*fut_class, state.clone()); - let result = unsafe { fut.get::() }.disconnect(); - - assert!(state.lock().unwrap().disconnected); - assert!(result == Pointer::undefined_singleton()); - - unsafe { - Future::drop(fut); - fut.free(); - } - } - - #[test] - fn test_process_stacktrace() { - let proc_class = empty_process_class("B"); - let mut proc = new_process(*proc_class); - let method1 = empty_method(); - let method2 = empty_method(); - let m1 = unsafe { &mut *method1.as_ptr() }; - let m2 = unsafe { &mut *method2.as_ptr() }; - - m1.locations.add_entry(0, 4, Pointer::int(2), Pointer::int(1)); - m2.locations.add_entry(0, 12, Pointer::int(4), Pointer::int(3)); - - let ctx1 = ExecutionContext::new(method1); - let ctx2 = ExecutionContext::new(method2); - let mut task = Task::new(ctx1, Write::Discard); - - task.push_context(ctx2); - - proc.task = Some(task); - - let trace = proc.stacktrace(); - - assert_eq!(trace.len(), 2); - assert_eq!( - trace.get(0), - Some(&Location { - name: Pointer::int(1), - file: Pointer::int(2), - line: Pointer::int(4) - }) - ); - assert_eq!( - trace.get(1), - Some(&Location { - name: Pointer::int(3), - file: Pointer::int(4), - line: Pointer::int(12) - }) - ); - - Method::drop_and_deallocate(method1); - Method::drop_and_deallocate(method2); - } -} diff --git a/vm/src/registers.rs b/vm/src/registers.rs deleted file mode 100644 index 4cd41e816..000000000 --- a/vm/src/registers.rs +++ /dev/null @@ -1,43 +0,0 @@ -///! Virtual machine registers -use crate::chunk::Chunk; -use crate::mem::Pointer; - -/// A collection of virtual machine registers. -pub(crate) struct Registers { - pub values: Chunk, -} - -impl Registers { - /// Creates a new Registers. - pub(crate) fn new(amount: u16) -> Registers { - Registers { values: Chunk::new(amount as usize) } - } - - /// Sets the value of the given register. - pub(crate) fn set(&mut self, register: u16, value: Pointer) { - unsafe { self.values.set(register as usize, value) }; - } - - /// Returns the value of a register. - pub(crate) fn get(&self, register: u16) -> Pointer { - unsafe { *self.values.get(register as usize) } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mem::Pointer; - - #[test] - fn test_set_get() { - let mut register = Registers::new(6); - let pointer = Pointer::new(0x4 as *mut u8); - - register.set(0, pointer); - assert!(register.get(0) == pointer); - - register.set(5, pointer); - assert!(register.get(5) == pointer); - } -} diff --git a/vm/src/runtime_error.rs b/vm/src/runtime_error.rs deleted file mode 100644 index 992c1b391..000000000 --- a/vm/src/runtime_error.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Errors that can be produced at VM runtime. -use crate::mem::Pointer; -use std::convert::From; -use std::io; -use std::net::AddrParseError; - -const INVALID_INPUT: i64 = 11; -const TIMED_OUT: i64 = 13; - -/// An error that can be raised in the VM at runtime. -#[derive(Debug)] -pub(crate) enum RuntimeError { - /// An error to throw as-is. - Error(Pointer), - - /// A fatal error that should result in the VM terminating. - Panic(String), - - /// A non-blocking operation would block, and should be retried at a later - /// point in time. - WouldBlock, -} - -impl RuntimeError { - pub(crate) fn timed_out() -> Self { - RuntimeError::Error(Pointer::int(TIMED_OUT)) - } - - pub(crate) fn should_poll(&self) -> bool { - matches!(self, RuntimeError::WouldBlock) - } -} - -impl From for RuntimeError { - fn from(error: io::Error) -> Self { - if error.kind() == io::ErrorKind::WouldBlock { - RuntimeError::WouldBlock - } else { - let code = match error.kind() { - io::ErrorKind::NotFound => 1, - io::ErrorKind::PermissionDenied => 2, - io::ErrorKind::ConnectionRefused => 3, - io::ErrorKind::ConnectionReset => 4, - io::ErrorKind::ConnectionAborted => 5, - io::ErrorKind::NotConnected => 6, - io::ErrorKind::AddrInUse => 7, - io::ErrorKind::AddrNotAvailable => 8, - io::ErrorKind::BrokenPipe => 9, - io::ErrorKind::AlreadyExists => 10, - io::ErrorKind::InvalidInput => INVALID_INPUT, - io::ErrorKind::InvalidData => 12, - io::ErrorKind::TimedOut => TIMED_OUT, - io::ErrorKind::WriteZero => 14, - io::ErrorKind::Interrupted => 15, - io::ErrorKind::UnexpectedEof => 16, - _ => 0, - }; - - RuntimeError::Error(Pointer::int(code)) - } - } -} - -impl From for RuntimeError { - fn from(result: String) -> Self { - RuntimeError::Panic(result) - } -} - -impl From<&str> for RuntimeError { - fn from(result: &str) -> Self { - RuntimeError::Panic(result.to_string()) - } -} - -impl From for RuntimeError { - fn from(_: AddrParseError) -> Self { - RuntimeError::Error(Pointer::int(INVALID_INPUT)) - } -} diff --git a/vm/src/scheduler/mod.rs b/vm/src/scheduler/mod.rs deleted file mode 100644 index b89111e66..000000000 --- a/vm/src/scheduler/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod process; -pub mod timeout_worker; -pub mod timeouts; diff --git a/vm/src/state.rs b/vm/src/state.rs deleted file mode 100644 index 34757903d..000000000 --- a/vm/src/state.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::arc_without_weak::ArcWithoutWeak; -use crate::builtin_functions::BuiltinFunctions; -use crate::config::Config; -use crate::mem::Pointer; -use crate::network_poller::NetworkPoller; -use crate::permanent_space::PermanentSpace; -use crate::scheduler::process::Scheduler; -use crate::scheduler::timeout_worker::TimeoutWorker; -use ahash::RandomState; -use rand::{thread_rng, Rng}; -use std::collections::HashMap; -use std::env; -use std::panic::RefUnwindSafe; -use std::sync::Mutex; -use std::time; - -/// A reference counted State. -pub(crate) type RcState = ArcWithoutWeak; - -/// The state of a virtual machine. -pub(crate) struct State { - /// The virtual machine's configuration. - pub(crate) config: Config, - - /// The start time of the VM (more or less). - pub(crate) start_time: time::Instant, - - /// The commandline arguments passed to an Inko program. - pub(crate) arguments: Vec, - - /// The environment variables defined when the VM started. - /// - /// We cache environment variables because C functions used through the FFI - /// (or through libraries) may call `setenv()` concurrently with `getenv()` - /// calls, which is unsound. Caching the variables also means we can safely - /// use `localtime_r()` (which internally may call `setenv()`). - pub(crate) environment: HashMap, - - /// The exit status to use when the VM terminates. - pub(crate) exit_status: Mutex, - - /// The scheduler to use for executing Inko processes. - pub(crate) scheduler: Scheduler, - - /// A task used for handling timeouts, such as message and IO timeouts. - pub(crate) timeout_worker: TimeoutWorker, - - /// The network pollers to use for process threads. - pub(crate) network_pollers: Vec, - - /// All builtin functions that a compiler can use. - pub(crate) builtin_functions: BuiltinFunctions, - - /// A type for allocating and storing blocks and permanent objects. - pub(crate) permanent_space: PermanentSpace, - - /// The random state to use for building hashers. - /// - /// We use the same base state for all hashers, seeded with randomly - /// generated keys. This means all hashers start off with the same base - /// state, and thus produce the same hash codes. - /// - /// The alternative is to generate unique seeds for every hasher, in a way - /// that Rust does (= starting off with a thread-local randomly generated - /// number, then incrementing it). This however requires that we somehow - /// expose the means to generate such keys to the standard library, such - /// that it can reuse these where necessary (e.g. a `Map` needs to produce - /// the same results for the same values every time). This leads to - /// implementation details leaking into the standard library, and we want to - /// avoid that. - pub(crate) hash_state: RandomState, -} - -impl RefUnwindSafe for State {} - -impl State { - pub(crate) fn new( - config: Config, - permanent_space: PermanentSpace, - args: &[String], - ) -> RcState { - let arguments = args - .iter() - .map(|arg| permanent_space.allocate_string(arg.clone())) - .collect(); - - let mut rng = thread_rng(); - let hash_state = - RandomState::with_seeds(rng.gen(), rng.gen(), rng.gen(), rng.gen()); - - let environment = env::vars_os() - .into_iter() - .map(|(k, v)| { - ( - k.to_string_lossy().into_owned(), - permanent_space - .allocate_string(v.to_string_lossy().into_owned()), - ) - }) - .collect::>(); - - let scheduler = Scheduler::new( - config.process_threads as usize, - config.backup_threads as usize, - ); - - let network_pollers = - (0..config.netpoll_threads).map(|_| NetworkPoller::new()).collect(); - - let state = State { - scheduler, - environment, - config, - start_time: time::Instant::now(), - exit_status: Mutex::new(0), - timeout_worker: TimeoutWorker::new(), - arguments, - network_pollers, - builtin_functions: BuiltinFunctions::new(), - permanent_space, - hash_state, - }; - - ArcWithoutWeak::new(state) - } - - pub(crate) fn terminate(&self) { - self.scheduler.terminate(); - } - - pub(crate) fn set_exit_status(&self, new_status: i32) { - *self.exit_status.lock().unwrap() = new_status; - } - - pub(crate) fn current_exit_status(&self) -> i32 { - *self.exit_status.lock().unwrap() - } -}