diff --git a/.cargo/config.toml b/.cargo/config.toml
index 81e24566c4..f57d96eaf7 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -30,7 +30,7 @@ rustflags = ["-Clink-args=/FORCE:UNRESOLVED"]
# https://github.com/rust-lang/cargo/issues/3946#issuecomment-973132993
ROC_WORKSPACE_DIR = { value = "", relative = true }
-# Debug flags. Keep this up-to-date with compiler/debug_flags/src/lib.rs.
+# Debug flags. Explanations for these are in compiler/debug_flags/src/lib.rs.
# Set = "1" to turn a debug flag on.
ROC_PRETTY_PRINT_ALIAS_CONTENTS = "0"
ROC_PRINT_UNIFICATIONS = "0"
@@ -49,6 +49,7 @@ ROC_PRINT_IR_AFTER_TRMC = "0"
ROC_PRINT_IR_AFTER_DROP_SPECIALIZATION = "0"
ROC_DEBUG_ALIAS_ANALYSIS = "0"
ROC_PRINT_RUNTIME_ERROR_GEN = "0"
+ROC_NO_UNBOUND_LAYOUT = "0"
ROC_PRINT_LLVM_FN_VERIFICATION = "0"
ROC_WRITE_FINAL_WASM = "0"
ROC_LOG_WASM_INTERP = "0"
diff --git a/.github/workflows/ci_manager.yml b/.github/workflows/ci_manager.yml
index 8d18f8dc99..f0548533f3 100644
--- a/.github/workflows/ci_manager.yml
+++ b/.github/workflows/ci_manager.yml
@@ -104,10 +104,10 @@ jobs:
if: needs.check-changes.outputs.run_tests == 'full'
uses: ./.github/workflows/ubuntu_x86_64.yml
- start-ubuntu-x86-64-tests-debug:
+ start-ubuntu-x86-64-nix-tests-debug:
needs: check-changes
if: needs.check-changes.outputs.run_tests == 'full'
- uses: ./.github/workflows/ubuntu_x86_64_debug.yml
+ uses: ./.github/workflows/ubuntu_x86_64_nix_debug.yml
start-windows-release-build-test:
needs: check-changes
@@ -133,7 +133,7 @@ jobs:
start-nix-macos-apple-silicon-tests,
start-macos-x86-64-tests,
start-ubuntu-x86-64-tests,
- start-ubuntu-x86-64-tests-debug,
+ start-ubuntu-x86-64-nix-tests-debug,
start-windows-release-build-test,
start-windows-tests,
start-roc-benchmarks
diff --git a/.github/workflows/ubuntu_x86_64_debug.yml b/.github/workflows/ubuntu_x86_64_debug.yml
deleted file mode 100644
index 2ad30f0752..0000000000
--- a/.github/workflows/ubuntu_x86_64_debug.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-on:
- workflow_call:
-
-name: cargo test debug
-
-env:
- RUST_BACKTRACE: 1
-
-jobs:
- cargo-test-debug:
- name: cargo test debug
- runs-on: [ubuntu-22.04]
- timeout-minutes: 90
- steps:
- - uses: actions/checkout@v4
-
- - uses: goto-bus-stop/setup-zig@v2
- with:
- version: 0.11.0
-
- - run: zig version
-
- - name: Install dependencies
- run: |
- sudo apt -y install build-essential libunwind-dev pkg-config zlib1g-dev valgrind
- wget https://apt.llvm.org/llvm.sh
- chmod +x llvm.sh
- sudo ./llvm.sh 16
- sudo rm /usr/bin/clang
- sudo ln -s /usr/bin/clang-16 /usr/bin/clang
- sudo ln -s /usr/bin/lld-16 /usr/bin/ld.lld
- sudo apt -y install libpolly-16-dev
-
- # for skipped tests; see #6946, #6947
- - name: cargo test without --release
- env:
- RUSTFLAGS: -C link-arg=-fuse-ld=lld
- run: cargo test -- --skip tests/exhaustive/match_on_result_with_uninhabited_error_destructuring_in_lambda_syntax.txt --skip tests::identity_lambda --skip tests::issue_2300 --skip tests::issue_2582_specialize_result_value --skip tests::sum_lambda
diff --git a/.github/workflows/ubuntu_x86_64_nix_debug.yml b/.github/workflows/ubuntu_x86_64_nix_debug.yml
new file mode 100644
index 0000000000..05835bb932
--- /dev/null
+++ b/.github/workflows/ubuntu_x86_64_nix_debug.yml
@@ -0,0 +1,27 @@
+on:
+ workflow_call:
+
+name: cargo test debug nix
+
+env:
+ RUST_BACKTRACE: 1
+
+jobs:
+ cargo-test-debug-nix:
+ name: cargo test debug nix
+ runs-on: [ubuntu-22.04]
+ timeout-minutes: 90
+ steps:
+ - uses: actions/checkout@v4
+
+ # install nix
+ - uses: cachix/install-nix-action@v23
+ with:
+ nix_path: nixpkgs=channel:nixos-unstable
+
+ - name: Check if debug flag files are in sync
+ run: ./ci/check_debug_vars.sh
+
+ # for skipped tests; see #6946, #6947
+ - name: cargo test without --release
+ run: nix develop -c sh -c 'export ROC_CHECK_MONO_IR=1 && cargo test -- --skip tests/exhaustive/match_on_result_with_uninhabited_error_destructuring_in_lambda_syntax.txt --skip tests::identity_lambda --skip tests::issue_2300 --skip tests::issue_2582_specialize_result_value --skip tests::sum_lambda'
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7b85d55191..0c6dab5572 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -43,20 +43,9 @@ Execute `cargo fmt --all` to fix the formatting.
- The [compiler's README](https://github.com/roc-lang/roc/tree/main/crates/compiler) contains important info.
- The AI chat in the [cursor editor](https://www.cursor.com/) can also help you find your way in the codebase.
-
-:beetle: Debugging Tips
-- Use a debug build of the compiler. We have many asserts enabled in the debug compiler that can alert you to something going wrong. When building from source, build the debug compiler with `cargo build --bin roc`, the binary is at roc/target/debug/roc. When using roc through a nix flake like in [basic-cli](https://github.com/roc-lang/basic-cli), use `rocPkgs.cli-debug` instead of `rocPkgs.cli`.
-- At the bottom of [.cargo/config.toml](https://github.com/roc-lang/roc/blob/main/.cargo/config.toml) we have useful debug flags that activate certain debug prints.
-- For Roc code; minimize the code that produces the issue.
-- If you plan to look at the data used and produced inside the compiler, try to reproduce your issue with a very simple platform like our [minimal Rust platform](https://github.com/roc-lang/roc/tree/main/examples/platform-switching/rust-platform) instead of for example basic-cli.
-- For segmentation faults:
- + In general we recommend using linux to investigate, it has better tools for this.
- + Use `roc build myApp.roc --linker=legacy` followed by `valgrind ./myApp`.
- + Use gdb to step through the code, [this gdb script](https://roc.zulipchat.com/#narrow/stream/395097-compiler-development/topic/gdb.20script/near/424422545) can be helpful.
- + Inspect the generated LLVM IR (`roc build myApp.roc --emit-llvm-ir`) between Roc code that encounters the segfault and code that doesn't.
-
+### Debugging tips
-
+If you need to do some debugging, check out [our tips](devtools/debug_tips.md).
### Commit signing
diff --git a/ci/check_debug_vars.sh b/ci/check_debug_vars.sh
new file mode 100755
index 0000000000..0d54a3d7ce
--- /dev/null
+++ b/ci/check_debug_vars.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
+set -euo pipefail
+
+# Extract vars from .cargo/config.toml
+config_vars=$(grep -E "^ROC_.*= \"[01]\"" .cargo/config.toml | cut -d'=' -f1 | tr -d ' ')
+
+# Extract vars from crates/compiler/debug_flags/src/lib.rs
+lib_vars=$(grep -E "^ ROC_.*" crates/compiler/debug_flags/src/lib.rs | tr -d ' ')
+
+# Sort both lists
+sorted_config_vars=$(echo "$config_vars" | sort)
+sorted_lib_vars=$(echo "$lib_vars" | sort)
+
+# Compare the sorted lists
+if diff <(echo "$sorted_config_vars") <(echo "$sorted_lib_vars") > /dev/null; then
+ echo "The flags in both files are identical."
+else
+ echo "Looks like some flags are out of sync between .cargo/config.toml and crates/compiler/debug_flags/src/lib.rs:"
+ diff <(echo "$sorted_config_vars") <(echo "$sorted_lib_vars")
+fi
\ No newline at end of file
diff --git a/crates/cli/src/format.rs b/crates/cli/src/format.rs
index 7d33833fb4..6cd649a5ee 100644
--- a/crates/cli/src/format.rs
+++ b/crates/cli/src/format.rs
@@ -5,11 +5,12 @@ use std::path::{Path, PathBuf};
use bumpalo::Bump;
use roc_error_macros::{internal_error, user_error};
use roc_fmt::def::fmt_defs;
-use roc_fmt::module::fmt_module;
-use roc_fmt::{Ast, Buf};
-use roc_parse::module::parse_module_defs;
-use roc_parse::remove_spaces::RemoveSpaces;
-use roc_parse::{module, parser::SyntaxError, state::State};
+use roc_fmt::header::fmt_header;
+use roc_fmt::Buf;
+use roc_parse::ast::{FullAst, SpacesBefore};
+use roc_parse::header::parse_module_defs;
+use roc_parse::normalize::Normalize;
+use roc_parse::{header, parser::SyntaxError, state::State};
#[derive(Copy, Clone, Debug)]
pub enum FormatMode {
@@ -199,8 +200,8 @@ pub fn format_src(arena: &Bump, src: &str) -> Result {
}
};
- let ast_normalized = ast.remove_spaces(arena);
- let reparsed_ast_normalized = reparsed_ast.remove_spaces(arena);
+ let ast_normalized = ast.normalize(arena);
+ let reparsed_ast_normalized = reparsed_ast.normalize(arena);
// HACK!
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
@@ -230,19 +231,25 @@ pub fn format_src(arena: &Bump, src: &str) -> Result {
Ok(buf.as_str().to_string())
}
-fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result, SyntaxError<'a>> {
- let (module, state) = module::parse_header(arena, State::new(src.as_bytes()))
+fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result, SyntaxError<'a>> {
+ let (header, state) = header::parse_header(arena, State::new(src.as_bytes()))
.map_err(|e| SyntaxError::Header(e.problem))?;
- let (module, defs) = module.upgrade_header_imports(arena);
+ let (h, defs) = header.item.upgrade_header_imports(arena);
let defs = parse_module_defs(arena, state, defs)?;
- Ok(Ast { module, defs })
+ Ok(FullAst {
+ header: SpacesBefore {
+ before: header.before,
+ item: h,
+ },
+ defs,
+ })
}
-fn fmt_all<'a>(buf: &mut Buf<'a>, ast: &'a Ast) {
- fmt_module(buf, &ast.module);
+fn fmt_all<'a>(buf: &mut Buf<'a>, ast: &'a FullAst) {
+ fmt_header(buf, &ast.header);
fmt_defs(buf, &ast.defs, 0);
diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs
index 982358d03a..b0b2e08e1d 100644
--- a/crates/cli/src/lib.rs
+++ b/crates/cli/src/lib.rs
@@ -432,6 +432,14 @@ pub fn build_app() -> Command {
.action(ArgAction::SetTrue)
.required(false)
)
+ .arg(
+ Arg::new(FLAG_TARGET)
+ .long(FLAG_TARGET)
+ .help("Choose a different target")
+ .default_value(Into::<&'static str>::into(Target::default()))
+ .value_parser(build_target_values_parser.clone())
+ .required(false),
+ )
)
.arg(flag_optimize)
.arg(flag_max_threads)
diff --git a/crates/cli/tests/cli/combine-tasks.roc b/crates/cli/tests/cli/combine-tasks.roc
index 70778a21d4..0e081c4644 100644
--- a/crates/cli/tests/cli/combine-tasks.roc
+++ b/crates/cli/tests/cli/combine-tasks.roc
@@ -9,8 +9,8 @@ main =
a: Task.ok 123,
b: Task.ok "abc",
c: Task.ok [123],
- d: Task.ok ["abc"],
- e: Task.ok (Dict.single "a" "b"),
+ _d: Task.ok ["abc"],
+ _: Task.ok (Dict.single "a" "b"),
}!
Stdout.line! "For multiple tasks: $(Inspect.toStr multipleIn)"
diff --git a/crates/cli/tests/cli_run.rs b/crates/cli/tests/cli_run.rs
index dfe61bd5ad..25237266f2 100644
--- a/crates/cli/tests/cli_run.rs
+++ b/crates/cli/tests/cli_run.rs
@@ -711,6 +711,17 @@ mod cli_run {
)
}
+ #[test]
+ #[cfg_attr(windows, ignore)]
+ fn platform_requires_pkg() {
+ test_roc_app_slim(
+ "crates/cli/tests/platform_requires_pkg",
+ "app.roc",
+ "from app from package",
+ UseValgrind::No,
+ )
+ }
+
#[test]
#[cfg_attr(windows, ignore)]
fn transitive_expects() {
@@ -947,7 +958,7 @@ mod cli_run {
&[],
&[],
&[],
- "For multiple tasks: {a: 123, b: \"abc\", c: [123], d: [\"abc\"], e: {\"a\": \"b\"}}\n",
+ "For multiple tasks: {a: 123, b: \"abc\", c: [123]}\n",
UseValgrind::No,
TestCliCommands::Run,
)
diff --git a/crates/cli/tests/platform_requires_pkg/app.roc b/crates/cli/tests/platform_requires_pkg/app.roc
new file mode 100644
index 0000000000..b90d12b1fa
--- /dev/null
+++ b/crates/cli/tests/platform_requires_pkg/app.roc
@@ -0,0 +1,6 @@
+app [main] {
+ pf: platform "./platform/main.roc"
+}
+
+main =
+ "from app"
diff --git a/crates/cli/tests/platform_requires_pkg/foo/Foo.roc b/crates/cli/tests/platform_requires_pkg/foo/Foo.roc
new file mode 100644
index 0000000000..d0591d9f27
--- /dev/null
+++ b/crates/cli/tests/platform_requires_pkg/foo/Foo.roc
@@ -0,0 +1,3 @@
+module [foo]
+
+foo = "from package"
diff --git a/crates/cli/tests/platform_requires_pkg/foo/main.roc b/crates/cli/tests/platform_requires_pkg/foo/main.roc
new file mode 100644
index 0000000000..ac9841cb5d
--- /dev/null
+++ b/crates/cli/tests/platform_requires_pkg/foo/main.roc
@@ -0,0 +1 @@
+package [Foo] {}
diff --git a/crates/cli/tests/platform_requires_pkg/platform/host.zig b/crates/cli/tests/platform_requires_pkg/platform/host.zig
new file mode 100644
index 0000000000..1ac9ce6dbe
--- /dev/null
+++ b/crates/cli/tests/platform_requires_pkg/platform/host.zig
@@ -0,0 +1,129 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const str = @import("glue").str;
+const RocStr = str.RocStr;
+const testing = std.testing;
+const expectEqual = testing.expectEqual;
+const expect = testing.expect;
+
+const Align = 2 * @alignOf(usize);
+extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
+extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*anyopaque;
+extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void;
+extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
+extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
+
+const DEBUG: bool = false;
+
+export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
+ if (DEBUG) {
+ var ptr = malloc(size);
+ const stdout = std.io.getStdOut().writer();
+ stdout.print("alloc: {d} (alignment {d}, size {d})\n", .{ ptr, alignment, size }) catch unreachable;
+ return ptr;
+ } else {
+ return malloc(size);
+ }
+}
+
+export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
+ if (DEBUG) {
+ const stdout = std.io.getStdOut().writer();
+ stdout.print("realloc: {d} (alignment {d}, old_size {d})\n", .{ c_ptr, alignment, old_size }) catch unreachable;
+ }
+
+ return realloc(@as([*]align(Align) u8, @alignCast(@ptrCast(c_ptr))), new_size);
+}
+
+export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
+ if (DEBUG) {
+ const stdout = std.io.getStdOut().writer();
+ stdout.print("dealloc: {d} (alignment {d})\n", .{ c_ptr, alignment }) catch unreachable;
+ }
+
+ free(@as([*]align(Align) u8, @alignCast(@ptrCast(c_ptr))));
+}
+
+export fn roc_panic(msg: *RocStr, tag_id: u32) callconv(.C) void {
+ _ = tag_id;
+
+ const stderr = std.io.getStdErr().writer();
+ stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg.asSlice()}) catch unreachable;
+ std.process.exit(1);
+}
+
+export fn roc_dbg(loc: *RocStr, msg: *RocStr, src: *RocStr) callconv(.C) void {
+ // This platform uses stdout for testing purposes instead of the normal stderr.
+ const stdout = std.io.getStdOut().writer();
+ stdout.print("[{s}] {s} = {s}\n", .{ loc.asSlice(), src.asSlice(), msg.asSlice() }) catch unreachable;
+}
+
+export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
+ return memset(dst, value, size);
+}
+
+extern fn kill(pid: c_int, sig: c_int) c_int;
+extern fn shm_open(name: *const i8, oflag: c_int, mode: c_uint) c_int;
+extern fn mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) *anyopaque;
+extern fn getppid() c_int;
+
+fn roc_getppid() callconv(.C) c_int {
+ return getppid();
+}
+
+fn roc_getppid_windows_stub() callconv(.C) c_int {
+ return 0;
+}
+
+fn roc_send_signal(pid: c_int, sig: c_int) callconv(.C) c_int {
+ return kill(pid, sig);
+}
+fn roc_shm_open(name: *const i8, oflag: c_int, mode: c_uint) callconv(.C) c_int {
+ return shm_open(name, oflag, mode);
+}
+fn roc_mmap(addr: ?*anyopaque, length: c_uint, prot: c_int, flags: c_int, fd: c_int, offset: c_uint) callconv(.C) *anyopaque {
+ return mmap(addr, length, prot, flags, fd, offset);
+}
+
+comptime {
+ if (builtin.os.tag == .macos or builtin.os.tag == .linux) {
+ @export(roc_getppid, .{ .name = "roc_getppid", .linkage = .Strong });
+ @export(roc_mmap, .{ .name = "roc_mmap", .linkage = .Strong });
+ @export(roc_send_signal, .{ .name = "roc_send_signal", .linkage = .Strong });
+ @export(roc_shm_open, .{ .name = "roc_shm_open", .linkage = .Strong });
+ }
+
+ if (builtin.os.tag == .windows) {
+ @export(roc_getppid_windows_stub, .{ .name = "roc_getppid", .linkage = .Strong });
+ }
+}
+
+const mem = std.mem;
+const Allocator = mem.Allocator;
+
+extern fn roc__mainForHost_1_exposed_generic(*RocStr) void;
+
+const Unit = extern struct {};
+
+pub fn main() u8 {
+ const stdout = std.io.getStdOut().writer();
+ const stderr = std.io.getStdErr().writer();
+
+ var timer = std.time.Timer.start() catch unreachable;
+
+ // actually call roc to populate the callresult
+ var callresult = RocStr.empty();
+ roc__mainForHost_1_exposed_generic(&callresult);
+
+ const nanos = timer.read();
+ const seconds = (@as(f64, @floatFromInt(nanos)) / 1_000_000_000.0);
+
+ // stdout the result
+ stdout.print("{s}", .{callresult.asSlice()}) catch unreachable;
+
+ callresult.decref();
+
+ stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
+
+ return 0;
+}
diff --git a/crates/cli/tests/platform_requires_pkg/platform/main.roc b/crates/cli/tests/platform_requires_pkg/platform/main.roc
new file mode 100644
index 0000000000..b8c40ae927
--- /dev/null
+++ b/crates/cli/tests/platform_requires_pkg/platform/main.roc
@@ -0,0 +1,14 @@
+platform "test"
+ requires {} { main : _ }
+ exposes []
+ packages {
+ foo: "../foo/main.roc",
+ }
+ imports []
+ provides [mainForHost]
+
+import foo.Foo
+
+mainForHost : Str
+mainForHost =
+ "$(main) $(Foo.foo)"
diff --git a/crates/compiler/builtins/roc/Result.roc b/crates/compiler/builtins/roc/Result.roc
index 58aecb1d76..9e7a2b9104 100644
--- a/crates/compiler/builtins/roc/Result.roc
+++ b/crates/compiler/builtins/roc/Result.roc
@@ -1,4 +1,15 @@
-module [Result, isOk, isErr, map, mapErr, try, onErr, withDefault]
+module [
+ Result,
+ isOk,
+ isErr,
+ map,
+ mapErr,
+ mapBoth,
+ map2,
+ try,
+ onErr,
+ withDefault,
+]
import Bool exposing [Bool]
@@ -67,6 +78,22 @@ mapErr = \result, transform ->
Ok v -> Ok v
Err e -> Err (transform e)
+## Maps both the `Ok` and `Err` values of a `Result` to new values.
+mapBoth : Result ok1 err1, (ok1 -> ok2), (err1 -> err2) -> Result ok2 err2
+mapBoth = \result, okTransform, errTransform ->
+ when result is
+ Ok val -> Ok (okTransform val)
+ Err err -> Err (errTransform err)
+
+## Maps the `Ok` values of two `Result`s to a new value using a given transformation,
+## or returns the first `Err` value encountered.
+map2 : Result a err, Result b err, (a, b -> c) -> Result c err
+map2 = \firstResult, secondResult, transform ->
+ when (firstResult, secondResult) is
+ (Ok first, Ok second) -> Ok (transform first second)
+ (Err err, _) -> Err err
+ (_, Err err) -> Err err
+
## If the result is `Ok`, transforms the entire result by running a conversion
## function on the value the `Ok` holds. Then returns that new result. If the
## result is `Err`, this has no effect. Use `onErr` to transform an `Err`.
diff --git a/crates/compiler/can/src/annotation.rs b/crates/compiler/can/src/annotation.rs
index 2118faf72f..5279f9f7a9 100644
--- a/crates/compiler/can/src/annotation.rs
+++ b/crates/compiler/can/src/annotation.rs
@@ -1,6 +1,6 @@
use crate::env::Env;
use crate::procedure::{QualifiedReference, References};
-use crate::scope::{PendingAbilitiesInScope, Scope};
+use crate::scope::{PendingAbilitiesInScope, Scope, SymbolLookup};
use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
@@ -386,7 +386,10 @@ pub(crate) fn make_apply_symbol(
// Look it up in scope!
match scope.lookup_str(ident, region) {
- Ok(symbol) => {
+ Ok(SymbolLookup {
+ symbol,
+ module_params: _,
+ }) => {
references.insert_type_lookup(symbol, QualifiedReference::Unqualified);
Ok(symbol)
}
@@ -398,7 +401,10 @@ pub(crate) fn make_apply_symbol(
}
} else {
match env.qualified_lookup(scope, module_name, ident, region) {
- Ok(symbol) => {
+ Ok(SymbolLookup {
+ symbol,
+ module_params: _,
+ }) => {
references.insert_type_lookup(symbol, QualifiedReference::Qualified);
Ok(symbol)
}
@@ -467,7 +473,8 @@ pub fn find_type_def_symbols(
while let Some(assigned_field) = inner_stack.pop() {
match assigned_field {
AssignedField::RequiredValue(_, _, t)
- | AssignedField::OptionalValue(_, _, t) => {
+ | AssignedField::OptionalValue(_, _, t)
+ | AssignedField::IgnoredValue(_, _, t) => {
stack.push(&t.value);
}
AssignedField::LabelOnly(_) => {}
@@ -1386,6 +1393,7 @@ fn can_assigned_fields<'a>(
break 'inner label;
}
+ IgnoredValue(_, _, _) => unreachable!(),
LabelOnly(loc_field_name) => {
// Interpret { a, b } as { a : a, b : b }
let field_name = Lowercase::from(loc_field_name.value);
diff --git a/crates/compiler/can/src/constraint.rs b/crates/compiler/can/src/constraint.rs
index 0f594d31a8..e10815bc87 100644
--- a/crates/compiler/can/src/constraint.rs
+++ b/crates/compiler/can/src/constraint.rs
@@ -602,7 +602,8 @@ impl Constraints {
| Constraint::Exhaustive { .. }
| Constraint::Resolve(..)
| Constraint::IngestedFile(..)
- | Constraint::CheckCycle(..) => false,
+ | Constraint::CheckCycle(..)
+ | Constraint::ImportParams(..) => false,
}
}
@@ -685,6 +686,15 @@ impl Constraints {
) -> Constraint {
Constraint::IngestedFile(type_index, file_path, bytes)
}
+
+ pub fn import_params(
+ &mut self,
+ opt_type_index: Option,
+ module_id: ModuleId,
+ region: Region,
+ ) -> Constraint {
+ Constraint::ImportParams(opt_type_index, module_id, region)
+ }
}
roc_error_macros::assert_sizeof_default!(Constraint, 3 * 8);
@@ -787,6 +797,7 @@ pub enum Constraint {
CheckCycle(Index, IllegalCycleMark),
IngestedFile(TypeOrVar, Box, Arc>),
+ ImportParams(Option, ModuleId, Region),
}
#[derive(Debug, Clone, Copy, Default)]
@@ -865,6 +876,9 @@ impl std::fmt::Debug for Constraint {
Self::IngestedFile(arg0, arg1, arg2) => {
write!(f, "IngestedFile({arg0:?}, {arg1:?}, {arg2:?})")
}
+ Self::ImportParams(arg0, arg1, arg2) => {
+ write!(f, "ImportParams({arg0:?}, {arg1:?}, {arg2:?})")
+ }
}
}
}
diff --git a/crates/compiler/can/src/copy.rs b/crates/compiler/can/src/copy.rs
index 5be29af2cd..c8d3b63267 100644
--- a/crates/compiler/can/src/copy.rs
+++ b/crates/compiler/can/src/copy.rs
@@ -288,6 +288,22 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr
loc_elems: loc_elems.iter().map(|le| le.map(|e| go_help!(e))).collect(),
},
Var(sym, var) => Var(*sym, sub!(*var)),
+ ParamsVar {
+ symbol,
+ params,
+ var,
+ } => ParamsVar {
+ symbol: *symbol,
+ params: *params,
+ var: sub!(*var),
+ },
+ ImportParams(module_id, region, opt_provided) => ImportParams(
+ *module_id,
+ *region,
+ opt_provided
+ .as_ref()
+ .map(|(var, expr)| (sub!(*var), Box::new(go_help!(&expr)))),
+ ),
&AbilityMember(sym, specialization, specialization_var) => {
AbilityMember(sym, specialization, sub!(specialization_var))
}
diff --git a/crates/compiler/can/src/debug/pretty_print.rs b/crates/compiler/can/src/debug/pretty_print.rs
index 03467c9bde..ad1f01f0ee 100644
--- a/crates/compiler/can/src/debug/pretty_print.rs
+++ b/crates/compiler/can/src/debug/pretty_print.rs
@@ -206,7 +206,13 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
.append("]")
.group(),
),
- Var(sym, _) | AbilityMember(sym, _, _) => pp_sym(c, f, *sym),
+ Var(sym, _) | ParamsVar { symbol: sym, .. } | AbilityMember(sym, _, _) => {
+ pp_sym(c, f, *sym)
+ }
+ ImportParams(_, _, Some((_, params_expr))) => expr(c, p, f, params_expr),
+ ImportParams(module_id, _, None) => {
+ text!(f, "", module_id)
+ }
When {
loc_cond, branches, ..
} => maybe_paren!(
diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs
index 70d95a72ec..0779a9e82d 100644
--- a/crates/compiler/can/src/def.rs
+++ b/crates/compiler/can/src/def.rs
@@ -10,6 +10,7 @@ use crate::annotation::IntroducedVariables;
use crate::annotation::OwnedNamedOrAble;
use crate::derive;
use crate::env::Env;
+use crate::expr::canonicalize_record;
use crate::expr::get_lookup_symbols;
use crate::expr::AnnotatedMark;
use crate::expr::ClosureData;
@@ -18,6 +19,7 @@ use crate::expr::Expr::{self, *};
use crate::expr::StructAccessorData;
use crate::expr::{canonicalize_expr, Output, Recursive};
use crate::pattern::{canonicalize_def_header_pattern, BindingsFromPattern, Pattern};
+use crate::procedure::QualifiedReference;
use crate::procedure::References;
use crate::scope::create_alias;
use crate::scope::{PendingAbilitiesInScope, Scope};
@@ -160,6 +162,13 @@ enum PendingValueDef<'a> {
&'a Loc>,
&'a Loc>,
),
+ /// Module params from an import
+ ImportParams {
+ symbol: Symbol,
+ loc_pattern: Loc,
+ module_id: ModuleId,
+ opt_provided: Option>>>>,
+ },
/// Ingested file
IngestedFile(
Loc,
@@ -174,6 +183,12 @@ impl PendingValueDef<'_> {
PendingValueDef::AnnotationOnly(loc_pattern, _) => loc_pattern,
PendingValueDef::Body(loc_pattern, _) => loc_pattern,
PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern,
+ PendingValueDef::ImportParams {
+ loc_pattern,
+ symbol: _,
+ module_id: _,
+ opt_provided: _,
+ } => loc_pattern,
PendingValueDef::IngestedFile(loc_pattern, _, _) => loc_pattern,
}
}
@@ -485,6 +500,16 @@ fn canonicalize_alias<'a>(
}
}
+#[macro_export]
+macro_rules! params_in_abilities_unimplemented {
+ ($lookup:expr) => {
+ match $lookup.module_params {
+ None => $lookup.symbol,
+ Some(_) => unimplemented!("params in abilities"),
+ }
+ };
+}
+
/// Canonicalizes a claimed ability implementation like `{ eq }` or `{ eq: myEq }`.
/// Returns a mapping of the ability member to the implementation symbol.
/// If there was an error, a problem will be recorded and nothing is returned.
@@ -503,7 +528,7 @@ fn canonicalize_claimed_ability_impl<'a>(
let member_symbol =
match env.qualified_lookup_with_module_id(scope, ability_home, label_str, region) {
- Ok(symbol) => symbol,
+ Ok(lookup) => params_in_abilities_unimplemented!(lookup),
Err(_) => {
env.problem(Problem::NotAnAbilityMember {
ability,
@@ -546,8 +571,13 @@ fn canonicalize_claimed_ability_impl<'a>(
// To handle both cases, try checking for a shadow first, then check for a direct
// reference. We want to check for a direct reference second so that if there is a
// shadow, we won't accidentally grab the imported symbol.
- let opt_impl_symbol = (scope.lookup_ability_member_shadow(member_symbol))
- .or_else(|| scope.lookup_str(label_str, region).ok());
+ let opt_impl_symbol =
+ (scope.lookup_ability_member_shadow(member_symbol)).or_else(|| {
+ scope
+ .lookup_str(label_str, region)
+ .map(|s| params_in_abilities_unimplemented!(s))
+ .ok()
+ });
match opt_impl_symbol {
// It's possible that even if we find a symbol it is still only the member
@@ -599,7 +629,7 @@ fn canonicalize_claimed_ability_impl<'a>(
label.value,
label.region,
) {
- Ok(symbol) => symbol,
+ Ok(lookup) => params_in_abilities_unimplemented!(lookup),
Err(_) => {
env.problem(Problem::NotAnAbilityMember {
ability,
@@ -611,7 +641,7 @@ fn canonicalize_claimed_ability_impl<'a>(
};
let impl_symbol = match scope.lookup(&impl_ident.into(), impl_region) {
- Ok(symbol) => symbol,
+ Ok(symbol) => params_in_abilities_unimplemented!(symbol),
Err(err) => {
env.problem(Problem::RuntimeError(err));
return Err(());
@@ -631,7 +661,9 @@ fn canonicalize_claimed_ability_impl<'a>(
// An error will already have been reported
Err(())
}
- AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => {
+ AssignedField::SpaceBefore(_, _)
+ | AssignedField::SpaceAfter(_, _)
+ | AssignedField::IgnoredValue(_, _, _) => {
internal_error!("unreachable")
}
}
@@ -1107,8 +1139,24 @@ fn canonicalize_value_defs<'a>(
PendingValue::ExpectFx(pending_expect) => {
pending_expect_fx.push(pending_expect);
}
- PendingValue::ModuleImport(introduced_import) => {
- imports_introduced.push(introduced_import);
+ PendingValue::ModuleImport(PendingModuleImport {
+ module_id,
+ region,
+ exposed_symbols,
+ params,
+ }) => {
+ imports_introduced.push(IntroducedImport {
+ module_id,
+ region,
+ exposed_symbols,
+ });
+
+ pending_value_defs.push(PendingValueDef::ImportParams {
+ symbol: params.symbol,
+ loc_pattern: params.loc_pattern,
+ opt_provided: params.opt_provided,
+ module_id,
+ });
}
PendingValue::InvalidIngestedFile => { /* skip */ }
PendingValue::ImportNameConflict => { /* skip */ }
@@ -1556,7 +1604,7 @@ impl DefOrdering {
fn insert_symbol_references(&mut self, def_id: u32, def_references: &DefReferences) {
match def_references {
DefReferences::Value(references) => {
- let it = references.value_lookups().chain(references.calls());
+ let it = references.value_lookups();
for referenced in it {
if let Some(ref_id) = self.get_id(*referenced) {
@@ -2350,6 +2398,50 @@ fn canonicalize_pending_value_def<'a>(
None,
)
}
+ ImportParams {
+ symbol,
+ loc_pattern,
+ module_id,
+ opt_provided,
+ } => {
+ // Insert a reference to the record so that we don't report it as unused
+ // If the whole module is unused, we'll report that separately
+ output
+ .references
+ .insert_value_lookup(symbol, QualifiedReference::Unqualified);
+
+ let (opt_var_record, references) = match opt_provided {
+ Some(params) => {
+ let (record, can_output) =
+ canonicalize_record(env, var_store, scope, loc_pattern.region, params);
+
+ let references = can_output.references.clone();
+ output.union(can_output);
+
+ (Some((var_store.fresh(), Box::new(record))), references)
+ }
+ None => (None, References::new()),
+ };
+
+ let loc_expr = Loc::at(
+ loc_pattern.region,
+ Expr::ImportParams(module_id, loc_pattern.region, opt_var_record),
+ );
+
+ let def = single_can_def(
+ loc_pattern,
+ loc_expr,
+ var_store.fresh(),
+ None,
+ SendMap::default(),
+ );
+
+ DefOutput {
+ output,
+ references: DefReferences::Value(references),
+ def,
+ }
+ }
IngestedFile(loc_pattern, opt_loc_ann, path_literal) => {
let relative_path =
if let ast::StrLiteral::PlainLine(ingested_path) = path_literal.value {
@@ -2891,7 +2983,7 @@ enum PendingValue<'a> {
Dbg(PendingExpectOrDbg<'a>),
Expect(PendingExpectOrDbg<'a>),
ExpectFx(PendingExpectOrDbg<'a>),
- ModuleImport(IntroducedImport),
+ ModuleImport(PendingModuleImport<'a>),
SignatureDefMismatch,
InvalidIngestedFile,
ImportNameConflict,
@@ -2902,6 +2994,19 @@ struct PendingExpectOrDbg<'a> {
preceding_comment: Region,
}
+struct PendingModuleImport<'a> {
+ module_id: ModuleId,
+ region: Region,
+ exposed_symbols: Vec<(Symbol, Region)>,
+ params: PendingModuleImportParams<'a>,
+}
+
+struct PendingModuleImportParams<'a> {
+ symbol: Symbol,
+ loc_pattern: Loc,
+ opt_provided: Option>>>>,
+}
+
pub struct IntroducedImport {
module_id: ModuleId,
region: Region,
@@ -3051,10 +3156,27 @@ fn to_pending_value_def<'a>(
None => module_name.clone(),
};
+ // Generate a symbol for the module params def
+ // We do this even if params weren't provided so that solve can report if they are missing
+ let params_sym = scope.gen_unique_symbol();
+ let params_region = module_import.params.map(|p| p.params.region).unwrap_or(region);
+ let params =
+ PendingModuleImportParams {
+ symbol: params_sym,
+ loc_pattern: Loc::at(params_region, Pattern::Identifier(params_sym)),
+ opt_provided: module_import.params.map(|p| p.params.value),
+ };
+ let provided_params_sym = if module_import.params.is_some() {
+ // Only add params to scope if they are provided
+ Some(params_sym)
+ } else {
+ None
+ };
+
if let Err(existing_import) =
scope
.modules
- .insert(name_with_alias.clone(), module_id, region)
+ .insert(name_with_alias.clone(), module_id, provided_params_sym, region)
{
env.problems.push(Problem::ImportNameConflict {
name: name_with_alias,
@@ -3065,7 +3187,7 @@ fn to_pending_value_def<'a>(
});
return PendingValue::ImportNameConflict;
- }
+ };
let exposed_names = module_import
.exposed
@@ -3117,13 +3239,13 @@ fn to_pending_value_def<'a>(
}))
}
}
-
}
- PendingValue::ModuleImport(IntroducedImport {
+ PendingValue::ModuleImport(PendingModuleImport {
module_id,
region,
exposed_symbols,
+ params,
})
}
IngestedFileImport(ingested_file) => {
diff --git a/crates/compiler/can/src/desugar.rs b/crates/compiler/can/src/desugar.rs
index cba1b7df4d..bb4affaf72 100644
--- a/crates/compiler/can/src/desugar.rs
+++ b/crates/compiler/can/src/desugar.rs
@@ -140,7 +140,9 @@ fn desugar_value_def<'a>(
let desugared_params =
params.map(|ModuleImportParams { before, params }| ModuleImportParams {
before,
- params: desugar_field_collection(arena, params, src, line_info, module_path),
+ params: params.map(|params| {
+ desugar_field_collection(arena, *params, src, line_info, module_path)
+ }),
});
ModuleImport(roc_parse::ast::ModuleImport {
@@ -521,44 +523,68 @@ pub fn desugar_expr<'a>(
});
}
- let mut field_names = Vec::with_capacity_in(fields.len(), arena);
- let mut field_vals = Vec::with_capacity_in(fields.len(), arena);
-
- for field in fields.items {
- match desugar_field(arena, &field.value, src, line_info, module_path) {
- AssignedField::RequiredValue(loc_name, _, loc_val) => {
- field_names.push(loc_name);
- field_vals.push(loc_val);
- }
- AssignedField::LabelOnly(loc_name) => {
- field_names.push(loc_name);
- field_vals.push(arena.alloc(Loc {
- region: loc_name.region,
- value: Expr::Var {
- module_name: "",
- ident: loc_name.value,
- },
- }));
- }
- AssignedField::OptionalValue(loc_name, _, loc_val) => {
- return arena.alloc(Loc {
- region: loc_expr.region,
- value: OptionalFieldInRecordBuilder(arena.alloc(loc_name), loc_val),
- });
- }
- AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => {
- unreachable!("Should have been desugared in `desugar_field`")
- }
- AssignedField::Malformed(_name) => {}
- }
+ struct FieldData<'d> {
+ name: Loc<&'d str>,
+ value: &'d Loc>,
+ ignored: bool,
}
- let closure_arg_from_field = |field: Loc<&'a str>| Loc {
- region: field.region,
- value: Pattern::Identifier {
- ident: arena.alloc_str(&format!("#{}", field.value)),
- },
- };
+ let mut field_data = Vec::with_capacity_in(fields.len(), arena);
+
+ for field in fields.items {
+ let (name, value, ignored) =
+ match desugar_field(arena, &field.value, src, line_info, module_path) {
+ AssignedField::RequiredValue(loc_name, _, loc_val) => {
+ (loc_name, loc_val, false)
+ }
+ AssignedField::IgnoredValue(loc_name, _, loc_val) => {
+ (loc_name, loc_val, true)
+ }
+ AssignedField::LabelOnly(loc_name) => (
+ loc_name,
+ &*arena.alloc(Loc {
+ region: loc_name.region,
+ value: Expr::Var {
+ module_name: "",
+ ident: loc_name.value,
+ },
+ }),
+ false,
+ ),
+ AssignedField::OptionalValue(loc_name, _, loc_val) => {
+ return arena.alloc(Loc {
+ region: loc_expr.region,
+ value: OptionalFieldInRecordBuilder(arena.alloc(loc_name), loc_val),
+ });
+ }
+ AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => {
+ unreachable!("Should have been desugared in `desugar_field`")
+ }
+ AssignedField::Malformed(_name) => continue,
+ };
+
+ field_data.push(FieldData {
+ name,
+ value,
+ ignored,
+ });
+ }
+
+ let closure_arg_from_field =
+ |FieldData {
+ name,
+ value: _,
+ ignored,
+ }: &FieldData<'a>| Loc {
+ region: name.region,
+ value: if *ignored {
+ Pattern::Underscore(name.value)
+ } else {
+ Pattern::Identifier {
+ ident: arena.alloc_str(&format!("#{}", name.value)),
+ }
+ },
+ };
let combiner_closure_in_region = |region| {
let closure_body = Tuple(Collection::with_items(
@@ -607,15 +633,15 @@ pub fn desugar_expr<'a>(
};
let closure_args = {
- if field_names.len() == 2 {
+ if field_data.len() == 2 {
arena.alloc_slice_copy(&[
- closure_arg_from_field(field_names[0]),
- closure_arg_from_field(field_names[1]),
+ closure_arg_from_field(&field_data[0]),
+ closure_arg_from_field(&field_data[1]),
])
} else {
let second_to_last_arg =
- closure_arg_from_field(field_names[field_names.len() - 2]);
- let last_arg = closure_arg_from_field(field_names[field_names.len() - 1]);
+ closure_arg_from_field(&field_data[field_data.len() - 2]);
+ let last_arg = closure_arg_from_field(&field_data[field_data.len() - 1]);
let mut second_arg = Pattern::Tuple(Collection::with_items(
arena.alloc_slice_copy(&[second_to_last_arg, last_arg]),
@@ -623,18 +649,18 @@ pub fn desugar_expr<'a>(
let mut second_arg_region =
Region::span_across(&second_to_last_arg.region, &last_arg.region);
- for index in (1..(field_names.len() - 2)).rev() {
+ for index in (1..(field_data.len() - 2)).rev() {
second_arg =
Pattern::Tuple(Collection::with_items(arena.alloc_slice_copy(&[
- closure_arg_from_field(field_names[index]),
+ closure_arg_from_field(&field_data[index]),
Loc::at(second_arg_region, second_arg),
])));
second_arg_region =
- Region::span_across(&field_names[index].region, &second_arg_region);
+ Region::span_across(&field_data[index].name.region, &second_arg_region);
}
arena.alloc_slice_copy(&[
- closure_arg_from_field(field_names[0]),
+ closure_arg_from_field(&field_data[0]),
Loc::at(second_arg_region, second_arg),
])
}
@@ -642,22 +668,26 @@ pub fn desugar_expr<'a>(
let record_val = Record(Collection::with_items(
Vec::from_iter_in(
- field_names.iter().map(|field_name| {
- Loc::at(
- field_name.region,
- AssignedField::RequiredValue(
- Loc::at(field_name.region, field_name.value),
- &[],
- arena.alloc(Loc::at(
- field_name.region,
- Expr::Var {
- module_name: "",
- ident: arena.alloc_str(&format!("#{}", field_name.value)),
- },
- )),
- ),
- )
- }),
+ field_data
+ .iter()
+ .filter(|field| !field.ignored)
+ .map(|field| {
+ Loc::at(
+ field.name.region,
+ AssignedField::RequiredValue(
+ field.name,
+ &[],
+ arena.alloc(Loc::at(
+ field.name.region,
+ Expr::Var {
+ module_name: "",
+ ident: arena
+ .alloc_str(&format!("#{}", field.name.value)),
+ },
+ )),
+ ),
+ )
+ }),
arena,
)
.into_bump_slice(),
@@ -671,14 +701,14 @@ pub fn desugar_expr<'a>(
),
});
- if field_names.len() == 2 {
+ if field_data.len() == 2 {
return arena.alloc(Loc {
region: loc_expr.region,
value: Apply(
new_mapper,
arena.alloc_slice_copy(&[
- field_vals[0],
- field_vals[1],
+ field_data[0].value,
+ field_data[1].value,
record_combiner_closure,
]),
CalledVia::RecordBuilder,
@@ -688,27 +718,30 @@ pub fn desugar_expr<'a>(
let mut inner_combined = arena.alloc(Loc {
region: Region::span_across(
- &field_vals[field_names.len() - 2].region,
- &field_vals[field_names.len() - 1].region,
+ &field_data[field_data.len() - 2].value.region,
+ &field_data[field_data.len() - 1].value.region,
),
value: Apply(
new_mapper,
arena.alloc_slice_copy(&[
- field_vals[field_names.len() - 2],
- field_vals[field_names.len() - 1],
+ field_data[field_data.len() - 2].value,
+ field_data[field_data.len() - 1].value,
combiner_closure_in_region(loc_expr.region),
]),
CalledVia::RecordBuilder,
),
});
- for index in (1..(field_names.len() - 2)).rev() {
+ for index in (1..(field_data.len() - 2)).rev() {
inner_combined = arena.alloc(Loc {
- region: Region::span_across(&field_vals[index].region, &inner_combined.region),
+ region: Region::span_across(
+ &field_data[index].value.region,
+ &inner_combined.region,
+ ),
value: Apply(
new_mapper,
arena.alloc_slice_copy(&[
- field_vals[index],
+ field_data[index].value,
inner_combined,
combiner_closure_in_region(loc_expr.region),
]),
@@ -722,7 +755,7 @@ pub fn desugar_expr<'a>(
value: Apply(
new_mapper,
arena.alloc_slice_copy(&[
- field_vals[0],
+ field_data[0].value,
inner_combined,
record_combiner_closure,
]),
@@ -1095,6 +1128,14 @@ fn desugar_field<'a>(
spaces,
desugar_expr(arena, loc_expr, src, line_info, module_path),
),
+ IgnoredValue(loc_str, spaces, loc_expr) => IgnoredValue(
+ Loc {
+ value: loc_str.value,
+ region: loc_str.region,
+ },
+ spaces,
+ desugar_expr(arena, loc_expr, src, line_info, module_path),
+ ),
LabelOnly(loc_str) => {
// Desugar { x } into { x: x }
let loc_expr = Loc {
diff --git a/crates/compiler/can/src/env.rs b/crates/compiler/can/src/env.rs
index e2f6d2ee23..a5f72fd906 100644
--- a/crates/compiler/can/src/env.rs
+++ b/crates/compiler/can/src/env.rs
@@ -1,7 +1,7 @@
use std::path::Path;
use crate::procedure::References;
-use crate::scope::Scope;
+use crate::scope::{ModuleLookup, Scope, SymbolLookup};
use bumpalo::Bump;
use roc_collections::{MutMap, VecSet};
use roc_module::ident::{Ident, ModuleName};
@@ -74,7 +74,7 @@ impl<'a> Env<'a> {
module_name_str: &str,
ident: &str,
region: Region,
- ) -> Result {
+ ) -> Result {
debug_assert!(
!module_name_str.is_empty(),
"Called env.qualified_lookup with an unqualified ident: {ident:?}"
@@ -82,8 +82,10 @@ impl<'a> Env<'a> {
let module_name = ModuleName::from(module_name_str);
- match scope.modules.get_id(&module_name) {
- Some(module_id) => self.qualified_lookup_help(scope, module_id, ident, region),
+ match scope.modules.lookup(&module_name) {
+ Some(lookedup_module) => {
+ self.qualified_lookup_help(scope, lookedup_module, ident, region)
+ }
None => Err(RuntimeError::ModuleNotImported {
module_name: module_name.clone(),
imported_modules: scope
@@ -106,11 +108,11 @@ impl<'a> Env<'a> {
module_id: ModuleId,
ident: &str,
region: Region,
- ) -> Result {
- if !scope.modules.has_id(module_id) {
- Err(self.module_exists_but_not_imported(scope, module_id, region))
+ ) -> Result {
+ if let Some(module) = scope.modules.lookup_by_id(&module_id) {
+ self.qualified_lookup_help(scope, module, ident, region)
} else {
- self.qualified_lookup_help(scope, module_id, ident, region)
+ Err(self.module_exists_but_not_imported(scope, module_id, region))
}
}
@@ -118,18 +120,18 @@ impl<'a> Env<'a> {
fn qualified_lookup_help(
&mut self,
scope: &Scope,
- module_id: ModuleId,
+ module: ModuleLookup,
ident: &str,
region: Region,
- ) -> Result {
+ ) -> Result {
let is_type_name = ident.starts_with(|c: char| c.is_uppercase());
// You can do qualified lookups on your own module, e.g.
// if I'm in the Foo module, I can do a `Foo.bar` lookup.
- if module_id == self.home {
+ if module.id == self.home {
match scope.locals.ident_ids.get_id(ident) {
Some(ident_id) => {
- let symbol = Symbol::new(module_id, ident_id);
+ let symbol = Symbol::new(module.id, ident_id);
if is_type_name {
self.qualified_type_lookups.insert(symbol);
@@ -137,7 +139,7 @@ impl<'a> Env<'a> {
self.qualified_value_lookups.insert(symbol);
}
- Ok(symbol)
+ Ok(SymbolLookup::no_params(symbol))
}
None => {
let error = RuntimeError::LookupNotInScope {
@@ -157,10 +159,10 @@ impl<'a> Env<'a> {
}
}
} else {
- match self.dep_idents.get(&module_id) {
+ match self.dep_idents.get(&module.id) {
Some(exposed_ids) => match exposed_ids.get_id(ident) {
Some(ident_id) => {
- let symbol = Symbol::new(module_id, ident_id);
+ let symbol = Symbol::new(module.id, ident_id);
if is_type_name {
self.qualified_type_lookups.insert(symbol);
@@ -168,12 +170,12 @@ impl<'a> Env<'a> {
self.qualified_value_lookups.insert(symbol);
}
- Ok(symbol)
+ Ok(module.into_symbol(symbol))
}
None => Err(RuntimeError::ValueNotExposed {
module_name: self
.qualified_module_ids
- .get_name(module_id)
+ .get_name(module.id)
.expect("Module ID known, but not in the module IDs somehow")
.as_inner()
.clone(),
@@ -182,7 +184,7 @@ impl<'a> Env<'a> {
exposed_values: exposed_ids.exposed_values(),
}),
},
- _ => Err(self.module_exists_but_not_imported(scope, module_id, region)),
+ _ => Err(self.module_exists_but_not_imported(scope, module.id, region)),
}
}
}
diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs
index 5d3f3e18eb..86c372d700 100644
--- a/crates/compiler/can/src/expr.rs
+++ b/crates/compiler/can/src/expr.rs
@@ -7,9 +7,10 @@ use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumBound,
};
+use crate::params_in_abilities_unimplemented;
use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern, PermitShadows};
use crate::procedure::{QualifiedReference, References};
-use crate::scope::Scope;
+use crate::scope::{Scope, SymbolLookup};
use crate::traverse::{walk_expr, Visitor};
use roc_collections::soa::Index;
use roc_collections::{SendMap, VecMap, VecSet};
@@ -17,7 +18,7 @@ use roc_error_macros::internal_error;
use roc_module::called_via::CalledVia;
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
-use roc_module::symbol::Symbol;
+use roc_module::symbol::{ModuleId, Symbol};
use roc_parse::ast::{self, Defs, PrecedenceConflict, StrLiteral};
use roc_parse::ident::Accessor;
use roc_parse::pattern::PatternType::*;
@@ -107,6 +108,12 @@ pub enum Expr {
// Lookups
Var(Symbol, Variable),
+ /// Like Var, but from a module with params
+ ParamsVar {
+ symbol: Symbol,
+ params: Symbol,
+ var: Variable,
+ },
AbilityMember(
/// Actual member name
Symbol,
@@ -177,6 +184,9 @@ pub enum Expr {
elems: Vec<(Variable, Box>)>,
},
+ /// Module params expression in import
+ ImportParams(ModuleId, Region, Option<(Variable, Box)>),
+
/// The "crash" keyword
Crash {
msg: Box>,
@@ -308,6 +318,11 @@ impl Expr {
Self::SingleQuote(..) => Category::Character,
Self::List { .. } => Category::List,
&Self::Var(sym, _) => Category::Lookup(sym),
+ &Self::ParamsVar {
+ symbol,
+ params: _,
+ var: _,
+ } => Category::Lookup(symbol),
&Self::AbilityMember(sym, _, _) => Category::Lookup(sym),
Self::When { .. } => Category::When,
Self::If { .. } => Category::If,
@@ -324,6 +339,8 @@ impl Expr {
Self::RecordAccessor(data) => Category::Accessor(data.field.clone()),
Self::TupleAccess { index, .. } => Category::TupleAccess(*index),
Self::RecordUpdate { .. } => Category::Record,
+ Self::ImportParams(_, _, Some((_, expr))) => expr.category(),
+ Self::ImportParams(_, _, None) => Category::Unknown,
Self::Tag {
name, arguments, ..
} => Category::TagApply {
@@ -632,33 +649,8 @@ pub fn canonicalize_expr<'a>(
(answer, Output::default())
}
ast::Expr::Record(fields) => {
- if fields.is_empty() {
- (EmptyRecord, Output::default())
- } else {
- match canonicalize_fields(env, var_store, scope, region, fields.items) {
- Ok((can_fields, output)) => (
- Record {
- record_var: var_store.fresh(),
- fields: can_fields,
- },
- output,
- ),
- Err(CanonicalizeRecordProblem::InvalidOptionalValue {
- field_name,
- field_region,
- record_region,
- }) => (
- Expr::RuntimeError(roc_problem::can::RuntimeError::InvalidOptionalValue {
- field_name,
- field_region,
- record_region,
- }),
- Output::default(),
- ),
- }
- }
+ canonicalize_record(env, var_store, scope, region, *fields)
}
-
ast::Expr::RecordUpdate {
fields,
update: loc_update,
@@ -1452,6 +1444,42 @@ pub fn canonicalize_expr<'a>(
)
}
+pub fn canonicalize_record<'a>(
+ env: &mut Env<'a>,
+ var_store: &mut VarStore,
+ scope: &mut Scope,
+ region: Region,
+ fields: ast::Collection<'a, Loc>>>,
+) -> (Expr, Output) {
+ use Expr::*;
+
+ if fields.is_empty() {
+ (EmptyRecord, Output::default())
+ } else {
+ match canonicalize_fields(env, var_store, scope, region, fields.items) {
+ Ok((can_fields, output)) => (
+ Record {
+ record_var: var_store.fresh(),
+ fields: can_fields,
+ },
+ output,
+ ),
+ Err(CanonicalizeRecordProblem::InvalidOptionalValue {
+ field_name,
+ field_region,
+ record_region,
+ }) => (
+ Expr::RuntimeError(roc_problem::can::RuntimeError::InvalidOptionalValue {
+ field_name,
+ field_region,
+ record_region,
+ }),
+ Output::default(),
+ ),
+ }
+ }
+}
+
pub fn canonicalize_closure<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
@@ -1846,6 +1874,11 @@ fn canonicalize_field<'a>(
field_region: Region::span_across(&label.region, &loc_expr.region),
}),
+ // An ignored value, e.g. `{ _name: 123 }`
+ IgnoredValue(_, _, _) => {
+ internal_error!("Somehow an IgnoredValue record field was not desugared!");
+ }
+
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
LabelOnly(_) => {
internal_error!("Somehow a LabelOnly record field was not desugared!");
@@ -1876,19 +1909,19 @@ fn canonicalize_var_lookup(
// Since module_name was empty, this is an unqualified var.
// Look it up in scope!
match scope.lookup_str(ident, region) {
- Ok(symbol) => {
+ Ok(lookup) => {
output
.references
- .insert_value_lookup(symbol, QualifiedReference::Unqualified);
+ .insert_value_lookup(lookup.symbol, QualifiedReference::Unqualified);
- if scope.abilities_store.is_ability_member_name(symbol) {
+ if scope.abilities_store.is_ability_member_name(lookup.symbol) {
AbilityMember(
- symbol,
+ params_in_abilities_unimplemented!(lookup),
Some(scope.abilities_store.fresh_specialization_id()),
var_store.fresh(),
)
} else {
- Var(symbol, var_store.fresh())
+ lookup_to_expr(lookup, var_store.fresh())
}
}
Err(problem) => {
@@ -1901,19 +1934,19 @@ fn canonicalize_var_lookup(
// Since module_name was nonempty, this is a qualified var.
// Look it up in the env!
match env.qualified_lookup(scope, module_name, ident, region) {
- Ok(symbol) => {
+ Ok(lookup) => {
output
.references
- .insert_value_lookup(symbol, QualifiedReference::Qualified);
+ .insert_value_lookup(lookup.symbol, QualifiedReference::Qualified);
- if scope.abilities_store.is_ability_member_name(symbol) {
+ if scope.abilities_store.is_ability_member_name(lookup.symbol) {
AbilityMember(
- symbol,
+ params_in_abilities_unimplemented!(lookup),
Some(scope.abilities_store.fresh_specialization_id()),
var_store.fresh(),
)
} else {
- Var(symbol, var_store.fresh())
+ lookup_to_expr(lookup, var_store.fresh())
}
}
Err(problem) => {
@@ -1931,6 +1964,24 @@ fn canonicalize_var_lookup(
(can_expr, output)
}
+fn lookup_to_expr(
+ SymbolLookup {
+ symbol,
+ module_params: params,
+ }: SymbolLookup,
+ var: Variable,
+) -> Expr {
+ if let Some(params) = params {
+ Expr::ParamsVar {
+ symbol,
+ params,
+ var,
+ }
+ } else {
+ Expr::Var(symbol, var)
+ }
+}
+
/// Currently uses the heuristic of "only inline if it's a builtin"
pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
use Expr::*;
@@ -1949,6 +2000,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
| other @ RecordAccessor { .. }
| other @ RecordUpdate { .. }
| other @ Var(..)
+ | other @ ParamsVar { .. }
| other @ AbilityMember(..)
| other @ RunLowLevel { .. }
| other @ TypedHole { .. }
@@ -2212,6 +2264,14 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
);
}
+ ImportParams(module_id, region, Some((var, expr))) => ImportParams(
+ module_id,
+ region,
+ Some((var, Box::new(inline_calls(var_store, *expr)))),
+ ),
+
+ ImportParams(module_id, region, None) => ImportParams(module_id, region, None),
+
RecordAccess {
record_var,
ext_var,
@@ -2433,7 +2493,8 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
}
ast::Expr::Record(fields) => fields.iter().all(|loc_field| match loc_field.value {
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
- | ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
+ | ast::AssignedField::OptionalValue(_label, loc_comments, loc_val)
+ | ast::AssignedField::IgnoredValue(_label, loc_comments, loc_val) => {
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
}
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
@@ -2481,7 +2542,8 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
is_valid_interpolation(&update.value)
&& fields.iter().all(|loc_field| match loc_field.value {
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
- | ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
+ | ast::AssignedField::OptionalValue(_label, loc_comments, loc_val)
+ | ast::AssignedField::IgnoredValue(_label, loc_comments, loc_val) => {
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
}
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
@@ -2514,7 +2576,8 @@ pub fn is_valid_interpolation(expr: &ast::Expr<'_>) -> bool {
is_valid_interpolation(&mapper.value)
&& fields.iter().all(|loc_field| match loc_field.value {
ast::AssignedField::RequiredValue(_label, loc_comments, loc_val)
- | ast::AssignedField::OptionalValue(_label, loc_comments, loc_val) => {
+ | ast::AssignedField::OptionalValue(_label, loc_comments, loc_val)
+ | ast::AssignedField::IgnoredValue(_label, loc_comments, loc_val) => {
loc_comments.is_empty() && is_valid_interpolation(&loc_val.value)
}
ast::AssignedField::Malformed(_) | ast::AssignedField::LabelOnly(_) => true,
@@ -3133,6 +3196,11 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec {
while let Some(expr) = stack.pop() {
match expr {
Expr::Var(symbol, var)
+ | Expr::ParamsVar {
+ symbol,
+ params: _,
+ var,
+ }
| Expr::RecordUpdate {
symbol,
record_var: var,
@@ -3237,6 +3305,9 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec {
Expr::Tuple { elems, .. } => {
stack.extend(elems.iter().map(|(_, elem)| &elem.value));
}
+ Expr::ImportParams(_, _, Some((_, expr))) => {
+ stack.push(expr);
+ }
Expr::Expect {
loc_continuation, ..
}
@@ -3263,6 +3334,7 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec {
| Expr::EmptyRecord
| Expr::TypedHole(_)
| Expr::RuntimeError(_)
+ | Expr::ImportParams(_, _, None)
| Expr::OpaqueWrapFunction(_) => {}
}
}
diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs
index d866a04386..ddcbcf4361 100644
--- a/crates/compiler/can/src/module.rs
+++ b/crates/compiler/can/src/module.rs
@@ -6,9 +6,11 @@ use crate::def::{canonicalize_defs, report_unused_imports, Def};
use crate::effect_module::HostedGeneratedFunctions;
use crate::env::Env;
use crate::expr::{
- ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives,
+ AnnotatedMark, ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives,
+};
+use crate::pattern::{
+ canonicalize_record_destructure, BindingsFromPattern, Pattern, PermitShadows,
};
-use crate::pattern::{BindingsFromPattern, Pattern};
use crate::procedure::References;
use crate::scope::Scope;
use bumpalo::Bump;
@@ -18,7 +20,7 @@ use roc_module::ident::Ident;
use roc_module::ident::Lowercase;
use roc_module::symbol::{IdentIds, IdentIdsByModule, ModuleId, PackageModuleIds, Symbol};
use roc_parse::ast::{Defs, TypeAnnotation};
-use roc_parse::header::HeaderType;
+use roc_parse::header::{HeaderType, ModuleParams};
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region};
@@ -136,6 +138,7 @@ pub struct Module {
pub abilities_store: PendingAbilitiesStore,
pub loc_expects: VecMap>,
pub loc_dbgs: VecMap,
+ pub params_pattern: Option<(Variable, AnnotatedMark, Loc)>,
}
#[derive(Debug, Default)]
@@ -149,6 +152,7 @@ pub struct RigidVariables {
pub struct ModuleOutput {
pub aliases: MutMap,
pub rigid_variables: RigidVariables,
+ pub params_pattern: Option<(Variable, AnnotatedMark, Loc)>,
pub declarations: Declarations,
pub exposed_imports: MutMap,
pub exposed_symbols: VecSet,
@@ -244,6 +248,7 @@ impl GeneratedInfo {
generates_with,
name: _,
exposes: _,
+ opt_params: _,
} => {
debug_assert!(generates_with.is_empty());
GeneratedInfo::Builtin
@@ -274,7 +279,7 @@ fn has_no_implementation(expr: &Expr) -> bool {
pub fn canonicalize_module_defs<'a>(
arena: &'a Bump,
loc_defs: &'a mut Defs<'a>,
- header_type: &roc_parse::header::HeaderType,
+ header_type: &'a roc_parse::header::HeaderType,
home: ModuleId,
module_path: &'a str,
src: &'a str,
@@ -290,6 +295,7 @@ pub fn canonicalize_module_defs<'a>(
opt_shorthand: Option<&'a str>,
) -> ModuleOutput {
let mut can_exposed_imports = MutMap::default();
+
let mut scope = Scope::new(
home,
qualified_module_ids
@@ -382,9 +388,42 @@ pub fn canonicalize_module_defs<'a>(
}
}
+ let mut output = Output::default();
+
+ let params_pattern = header_type.get_params().as_ref().map(
+ |ModuleParams {
+ pattern,
+ before_arrow: _,
+ after_arrow: _,
+ }| {
+ let can_pattern = canonicalize_record_destructure(
+ &mut env,
+ var_store,
+ &mut scope,
+ &mut output,
+ PatternType::ModuleParams,
+ &pattern.value,
+ pattern.region,
+ PermitShadows(false),
+ );
+
+ let loc_pattern = Loc::at(pattern.region, can_pattern);
+
+ for (symbol, _) in BindingsFromPattern::new(&loc_pattern) {
+ env.top_level_symbols.insert(symbol);
+ }
+
+ (
+ var_store.fresh(),
+ AnnotatedMark::new(var_store),
+ loc_pattern,
+ )
+ },
+ );
+
let (defs, output, symbols_introduced, imports_introduced) = canonicalize_defs(
&mut env,
- Output::default(),
+ output,
var_store,
&mut scope,
loc_defs,
@@ -812,6 +851,7 @@ pub fn canonicalize_module_defs<'a>(
scope,
aliases,
rigid_variables,
+ params_pattern,
declarations,
referenced_values,
exposed_imports: can_exposed_imports,
@@ -1105,6 +1145,7 @@ fn fix_values_captured_in_closure_expr(
| SingleQuote(..)
| IngestedFile(..)
| Var(..)
+ | ParamsVar { .. }
| AbilityMember(..)
| EmptyRecord
| TypedHole { .. }
@@ -1216,6 +1257,12 @@ fn fix_values_captured_in_closure_expr(
}
}
+ ImportParams(_, _, Some((_, expr))) => {
+ fix_values_captured_in_closure_expr(expr, no_capture_symbols, closure_captures);
+ }
+
+ ImportParams(_, _, None) => {}
+
Tuple { elems, .. } => {
for (_var, expr) in elems.iter_mut() {
fix_values_captured_in_closure_expr(
diff --git a/crates/compiler/can/src/pattern.rs b/crates/compiler/can/src/pattern.rs
index dc3a7d7dfe..129236bb46 100644
--- a/crates/compiler/can/src/pattern.rs
+++ b/crates/compiler/can/src/pattern.rs
@@ -623,132 +623,16 @@ pub fn canonicalize_pattern<'a>(
}
}
- RecordDestructure(patterns) => {
- let ext_var = var_store.fresh();
- let whole_var = var_store.fresh();
- let mut destructs = Vec::with_capacity(patterns.len());
- let mut opt_erroneous = None;
-
- for loc_pattern in patterns.iter() {
- match loc_pattern.value {
- Identifier { ident: label } => {
- match scope.introduce(label.into(), region) {
- Ok(symbol) => {
- output.references.insert_bound(symbol);
-
- destructs.push(Loc {
- region: loc_pattern.region,
- value: RecordDestruct {
- var: var_store.fresh(),
- label: Lowercase::from(label),
- symbol,
- typ: DestructType::Required,
- },
- });
- }
- Err((shadowed_symbol, shadow, new_symbol)) => {
- env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
- original_region: shadowed_symbol.region,
- shadow: shadow.clone(),
- kind: ShadowKind::Variable,
- }));
-
- // No matter what the other patterns
- // are, we're definitely shadowed and will
- // get a runtime exception as soon as we
- // encounter the first bad pattern.
- opt_erroneous = Some(Pattern::Shadowed(
- shadowed_symbol.region,
- shadow,
- new_symbol,
- ));
- }
- };
- }
-
- RequiredField(label, loc_guard) => {
- // a guard does not introduce the label into scope!
- let symbol =
- scope.scopeless_symbol(&Ident::from(label), loc_pattern.region);
- let can_guard = canonicalize_pattern(
- env,
- var_store,
- scope,
- output,
- pattern_type,
- &loc_guard.value,
- loc_guard.region,
- permit_shadows,
- );
-
- destructs.push(Loc {
- region: loc_pattern.region,
- value: RecordDestruct {
- var: var_store.fresh(),
- label: Lowercase::from(label),
- symbol,
- typ: DestructType::Guard(var_store.fresh(), can_guard),
- },
- });
- }
- OptionalField(label, loc_default) => {
- // an optional DOES introduce the label into scope!
- match scope.introduce(label.into(), region) {
- Ok(symbol) => {
- let (can_default, expr_output) = canonicalize_expr(
- env,
- var_store,
- scope,
- loc_default.region,
- &loc_default.value,
- );
-
- // an optional field binds the symbol!
- output.references.insert_bound(symbol);
-
- output.union(expr_output);
-
- destructs.push(Loc {
- region: loc_pattern.region,
- value: RecordDestruct {
- var: var_store.fresh(),
- label: Lowercase::from(label),
- symbol,
- typ: DestructType::Optional(var_store.fresh(), can_default),
- },
- });
- }
- Err((shadowed_symbol, shadow, new_symbol)) => {
- env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
- original_region: shadowed_symbol.region,
- shadow: shadow.clone(),
- kind: ShadowKind::Variable,
- }));
-
- // No matter what the other patterns
- // are, we're definitely shadowed and will
- // get a runtime exception as soon as we
- // encounter the first bad pattern.
- opt_erroneous = Some(Pattern::Shadowed(
- shadowed_symbol.region,
- shadow,
- new_symbol,
- ));
- }
- };
- }
- _ => unreachable!("Any other pattern should have given a parse error"),
- }
- }
-
- // If we encountered an erroneous pattern (e.g. one with shadowing),
- // use the resulting RuntimeError. Otherwise, return a successful record destructure.
- opt_erroneous.unwrap_or(Pattern::RecordDestructure {
- whole_var,
- ext_var,
- destructs,
- })
- }
+ RecordDestructure(patterns) => canonicalize_record_destructure(
+ env,
+ var_store,
+ scope,
+ output,
+ pattern_type,
+ patterns,
+ region,
+ permit_shadows,
+ ),
RequiredField(_name, _loc_pattern) => {
unreachable!("should have been handled in RecordDestructure");
@@ -894,6 +778,144 @@ pub fn canonicalize_pattern<'a>(
}
}
+#[allow(clippy::too_many_arguments)]
+pub fn canonicalize_record_destructure<'a>(
+ env: &mut Env<'a>,
+ var_store: &mut VarStore,
+ scope: &mut Scope,
+ output: &mut Output,
+ pattern_type: PatternType,
+ patterns: &ast::Collection>>,
+ region: Region,
+ permit_shadows: PermitShadows,
+) -> Pattern {
+ use ast::Pattern::*;
+
+ let ext_var = var_store.fresh();
+ let whole_var = var_store.fresh();
+ let mut destructs = Vec::with_capacity(patterns.len());
+ let mut opt_erroneous = None;
+
+ for loc_pattern in patterns.iter() {
+ match loc_pattern.value {
+ Identifier { ident: label } => {
+ match scope.introduce(label.into(), region) {
+ Ok(symbol) => {
+ output.references.insert_bound(symbol);
+
+ destructs.push(Loc {
+ region: loc_pattern.region,
+ value: RecordDestruct {
+ var: var_store.fresh(),
+ label: Lowercase::from(label),
+ symbol,
+ typ: DestructType::Required,
+ },
+ });
+ }
+ Err((shadowed_symbol, shadow, new_symbol)) => {
+ env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
+ original_region: shadowed_symbol.region,
+ shadow: shadow.clone(),
+ kind: ShadowKind::Variable,
+ }));
+
+ // No matter what the other patterns
+ // are, we're definitely shadowed and will
+ // get a runtime exception as soon as we
+ // encounter the first bad pattern.
+ opt_erroneous = Some(Pattern::Shadowed(
+ shadowed_symbol.region,
+ shadow,
+ new_symbol,
+ ));
+ }
+ };
+ }
+
+ RequiredField(label, loc_guard) => {
+ // a guard does not introduce the label into scope!
+ let symbol = scope.scopeless_symbol(&Ident::from(label), loc_pattern.region);
+ let can_guard = canonicalize_pattern(
+ env,
+ var_store,
+ scope,
+ output,
+ pattern_type,
+ &loc_guard.value,
+ loc_guard.region,
+ permit_shadows,
+ );
+
+ destructs.push(Loc {
+ region: loc_pattern.region,
+ value: RecordDestruct {
+ var: var_store.fresh(),
+ label: Lowercase::from(label),
+ symbol,
+ typ: DestructType::Guard(var_store.fresh(), can_guard),
+ },
+ });
+ }
+ OptionalField(label, loc_default) => {
+ // an optional DOES introduce the label into scope!
+ match scope.introduce(label.into(), region) {
+ Ok(symbol) => {
+ let (can_default, expr_output) = canonicalize_expr(
+ env,
+ var_store,
+ scope,
+ loc_default.region,
+ &loc_default.value,
+ );
+
+ // an optional field binds the symbol!
+ output.references.insert_bound(symbol);
+
+ output.union(expr_output);
+
+ destructs.push(Loc {
+ region: loc_pattern.region,
+ value: RecordDestruct {
+ var: var_store.fresh(),
+ label: Lowercase::from(label),
+ symbol,
+ typ: DestructType::Optional(var_store.fresh(), can_default),
+ },
+ });
+ }
+ Err((shadowed_symbol, shadow, new_symbol)) => {
+ env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
+ original_region: shadowed_symbol.region,
+ shadow: shadow.clone(),
+ kind: ShadowKind::Variable,
+ }));
+
+ // No matter what the other patterns
+ // are, we're definitely shadowed and will
+ // get a runtime exception as soon as we
+ // encounter the first bad pattern.
+ opt_erroneous = Some(Pattern::Shadowed(
+ shadowed_symbol.region,
+ shadow,
+ new_symbol,
+ ));
+ }
+ };
+ }
+ _ => unreachable!("Any other pattern should have given a parse error"),
+ }
+ }
+
+ // If we encountered an erroneous pattern (e.g. one with shadowing),
+ // use the resulting RuntimeError. Otherwise, return a successful record destructure.
+ opt_erroneous.unwrap_or(Pattern::RecordDestructure {
+ whole_var,
+ ext_var,
+ destructs,
+ })
+}
+
/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't
/// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern.
fn unsupported_pattern(env: &mut Env, pattern_type: PatternType, region: Region) -> Pattern {
diff --git a/crates/compiler/can/src/scope.rs b/crates/compiler/can/src/scope.rs
index c157659c37..6de854fa80 100644
--- a/crates/compiler/can/src/scope.rs
+++ b/crates/compiler/can/src/scope.rs
@@ -1,8 +1,8 @@
use roc_collections::{VecMap, VecSet};
use roc_error_macros::internal_error;
use roc_module::ident::{Ident, ModuleName};
-use roc_module::symbol::{IdentId, IdentIds, ModuleId, ScopeModules, Symbol};
-use roc_problem::can::RuntimeError;
+use roc_module::symbol::{IdentId, IdentIds, ModuleId, ModuleIds, Symbol};
+use roc_problem::can::{RuntimeError, ScopeModuleSource};
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use roc_types::types::{Alias, AliasKind, AliasVar, Type};
@@ -76,7 +76,7 @@ impl Scope {
}
}
- pub fn lookup(&self, ident: &Ident, region: Region) -> Result {
+ pub fn lookup(&self, ident: &Ident, region: Region) -> Result {
self.lookup_str(ident.as_str(), region)
}
@@ -91,7 +91,7 @@ impl Scope {
.push(("Set".into(), Symbol::SET_SET, Region::zero()));
}
- pub fn lookup_str(&self, ident: &str, region: Region) -> Result {
+ pub fn lookup_str(&self, ident: &str, region: Region) -> Result {
use ContainsIdent::*;
match self.scope_contains_ident(ident) {
@@ -205,14 +205,19 @@ impl Scope {
}
}
- fn has_imported_symbol(&self, ident: &str) -> Option<(Symbol, Region)> {
- for (import, shadow, original_region) in self.imported_symbols.iter() {
- if ident == import.as_str() {
- return Some((*shadow, *original_region));
- }
- }
-
- None
+ fn has_imported_symbol(&self, ident: &str) -> Option<(SymbolLookup, Region)> {
+ self.imported_symbols
+ .iter()
+ .find_map(|(import, symbol, original_region)| {
+ if ident == import.as_str() {
+ match self.modules.lookup_by_id(&symbol.module_id()) {
+ Some(module) => Some((module.into_symbol(*symbol), *original_region)),
+ None => Some((SymbolLookup::no_params(*symbol), *original_region)),
+ }
+ } else {
+ None
+ }
+ })
}
/// Is an identifier in scope, either in the locals or imports
@@ -229,7 +234,7 @@ impl Scope {
ContainsIdent::InScope(original_symbol, original_region) => {
// the ident is already in scope; up to the caller how to handle that
// (usually it's shadowing, but it is valid to shadow ability members)
- Err((original_symbol, original_region))
+ Err((original_symbol.symbol, original_region))
}
ContainsIdent::NotPresent => {
// We know nothing about this ident yet; introduce it to the scope
@@ -389,7 +394,13 @@ impl Scope {
region: Region,
) -> Result<(), (Symbol, Region)> {
match self.scope_contains_ident(ident.as_str()) {
- ContainsIdent::InScope(symbol, region) => Err((symbol, region)),
+ ContainsIdent::InScope(
+ SymbolLookup {
+ symbol,
+ module_params: _,
+ },
+ region,
+ ) => Err((symbol, region)),
ContainsIdent::NotPresent | ContainsIdent::NotInScope(_) => {
self.imported_symbols.push((ident, symbol, region));
Ok(())
@@ -534,7 +545,7 @@ pub fn create_alias(
#[derive(Debug)]
enum ContainsIdent {
- InScope(Symbol, Region),
+ InScope(SymbolLookup, Region),
NotInScope(IdentId),
NotPresent,
}
@@ -561,7 +572,7 @@ impl ScopedIdentIds {
fn has_in_scope(&self, ident: &Ident) -> Option<(Symbol, Region)> {
match self.contains_ident(ident.as_str()) {
- ContainsIdent::InScope(symbol, region) => Some((symbol, region)),
+ ContainsIdent::InScope(symbol, region) => Some((symbol.symbol, region)),
ContainsIdent::NotInScope(_) | ContainsIdent::NotPresent => None,
}
}
@@ -574,7 +585,10 @@ impl ScopedIdentIds {
for ident_id in self.ident_ids.get_id_many(ident) {
let index = ident_id.index();
if self.in_scope[index] {
- return InScope(Symbol::new(self.home, ident_id), self.regions[index]);
+ return InScope(
+ SymbolLookup::no_params(Symbol::new(self.home, ident_id)),
+ self.regions[index],
+ );
} else {
result = NotInScope(ident_id)
}
@@ -646,6 +660,149 @@ impl ScopedIdentIds {
}
}
+#[derive(Debug, Clone)]
+pub struct ScopeModules {
+ /// The ids of all modules in scope
+ ids: Vec,
+ /// The alias or original name of each module in scope
+ names: Vec,
+ /// Why is this module in scope?
+ sources: Vec,
+ /// The params of a module if any
+ params: Vec