This commit is contained in:
Luke Boswell 2024-11-11 10:22:58 +11:00
parent 743030fc99
commit 8a566dc339
No known key found for this signature in database
GPG key ID: F6DB3C9DB47377B0
260 changed files with 6344 additions and 2958 deletions

View file

@ -1,5 +1,5 @@
on:
#pull_request:
# pull_request:
workflow_dispatch:
# this cancels workflows currently in progress if you start a new one
@ -11,7 +11,7 @@ env:
# use .tar.gz for quick testing
ARCHIVE_FORMAT: .tar.br
# Make a new basic-cli git tag and set it here before starting this workflow
RELEASE_TAG: 0.14.0
RELEASE_TAG: 0.16.0
jobs:
prepare:
@ -34,14 +34,14 @@ jobs:
fi
# get latest nightly releases
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-latest.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-TESTING.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-TESTING.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-TESTING.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-TESTING.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-latest.tar.gz
- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-latest.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_x86_64-TESTING.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-linux_arm64-TESTING.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_x86_64-TESTING.tar.gz
#- run: curl -fOL https://github.com/roc-lang/roc/releases/download/nightly/roc_nightly-macos_apple_silicon-TESTING.tar.gz
- name: Save roc_nightly archives
uses: actions/upload-artifact@v4

View file

@ -24,4 +24,4 @@ jobs:
# 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'
run: nix develop -c sh -c 'export ROC_CHECK_MONO_IR=1 && cargo test'

View file

@ -275,10 +275,6 @@ main =
const UNFORMATTED_ROC: &str = r#"app [main] { pf: platform "platform/main.roc" }
import pf.Stdout
import pf.Stdin
main =
Stdout.line! "What's your name?"
name = Stdin.line!

View file

@ -322,6 +322,81 @@ mod cli_tests {
insta::assert_snapshot!(cli_test_out.normalize_stdout_and_stderr());
}
mod no_platform {
use super::*;
#[test]
#[cfg_attr(windows, ignore)]
fn roc_check_markdown_docs() {
let cli_build = ExecCli::new(
CMD_CHECK,
file_from_root("crates/cli/tests/markdown", "form.md"),
);
let expected_out =
"0 error and 0 warning found in <ignored for test> ms\n0 error and 0 warning found in <ignored for test> ms\n";
cli_build.run().assert_clean_stdout(expected_out);
}
#[test]
#[cfg_attr(windows, ignore)]
fn import_in_expect() {
let cli_build = ExecCli::new(
CMD_TEST,
file_from_root(
"crates/cli/tests/test-projects/module_params",
"ImportInExpect.roc",
),
);
let expected_out = r#"
0 failed and 3 passed in <ignored for test> ms.
"#;
cli_build.run().assert_clean_stdout(expected_out);
}
}
mod test_platform_basic_cli {
use super::*;
use roc_cli::CMD_RUN;
#[test]
#[cfg_attr(windows, ignore)]
fn module_params_different_types() {
let cli_build = ExecCli::new(
CMD_RUN,
file_from_root(
"crates/cli/tests/test-projects/module_params",
"different_types.roc",
),
);
let expected_out = "Write something:\n42\n";
cli_build
.run_executable(false, Some("42"), None)
.assert_clean_stdout(expected_out);
}
#[test]
#[cfg_attr(windows, ignore)]
fn module_params_issue_7116() {
let cli_build = ExecCli::new(
CMD_RUN,
file_from_root(
"crates/cli/tests/test-projects/module_params",
"issue_7116.roc",
),
);
cli_build.run().assert_zero_exit();
}
}
mod test_platform_simple_zig {
use super::*;
use roc_cli::{CMD_BUILD, CMD_DEV, CMD_TEST};
@ -655,7 +730,6 @@ mod cli_tests {
mod test_platform_effects_zig {
use super::*;
use cli_test_utils::helpers::file_from_root;
use roc_cli::CMD_BUILD;
static BUILD_PLATFORM_HOST: std::sync::Once = std::sync::Once::new();
@ -665,7 +739,7 @@ mod cli_tests {
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root(
"crates/cli/tests/test-projects/effects/platform/",
"crates/cli/tests/test-projects/test-platform-effects-zig/",
"app-stub.roc",
),
)
@ -692,7 +766,7 @@ mod cli_tests {
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root("crates/cli/tests/test-projects/effects", "print-line.roc"),
file_from_root("crates/cli/tests/test-projects/effectful", "print-line.roc"),
);
let expected_output = "You entered: hi there!\nIt is known\n";
@ -714,7 +788,7 @@ mod cli_tests {
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root(
"crates/cli/tests/test-projects/effects",
"crates/cli/tests/test-projects/effectful",
"combine-tasks.roc",
),
);
@ -738,7 +812,7 @@ mod cli_tests {
let cli_build = ExecCli::new(
CMD_BUILD,
file_from_root(
"crates/cli/tests/test-projects/effects",
"crates/cli/tests/test-projects/effectful",
"inspect-logging.roc",
),
);
@ -780,18 +854,117 @@ mod cli_tests {
#[test]
#[cfg_attr(windows, ignore)]
fn roc_check_markdown_docs() {
fn effectful_form() {
build_platform_host();
let cli_build = ExecCli::new(
CMD_CHECK,
file_from_root("crates/cli/tests/markdown", "form.md"),
roc_cli::CMD_DEV,
file_from_root("crates/cli/tests/test-projects/effectful", "form.roc"),
);
let expected_out =
"0 error and 0 warning found in <ignored for test> ms\n0 error and 0 warning found in <ignored for test> ms\n";
let expected_out = r#"
What's your first name?
What's your last name?
cli_build.run().assert_clean_stdout(expected_out);
Hi, Agus Zubiaga!
How old are you?
Nice! You can vote!
Bye! 👋
"#;
cli_build
.run_executable(false, Some("Agus\nZubiaga\n27\n"), None)
.assert_clean_stdout(expected_out);
}
#[test]
#[cfg_attr(windows, ignore)]
fn effectful_hello() {
todo!();
// test_roc_app(
// "crates/cli/tests/test-projects/effectful",
// "hello.roc",
// &[],
// &[],
// &[],
// indoc!(
// r#"
// I'm an effect 👻
// "#
// ),
// UseValgrind::No,
// TestCliCommands::Dev,
// );
}
#[test]
#[cfg_attr(windows, ignore)]
fn effectful_loops() {
todo!();
// test_roc_app(
// "crates/cli/tests/test-projects/effectful",
// "loops.roc",
// &[],
// &[],
// &[],
// indoc!(
// r#"
// Lu
// Marce
// Joaquin
// Chloé
// Mati
// Pedro
// "#
// ),
// UseValgrind::No,
// TestCliCommands::Dev,
// );
}
#[test]
#[cfg_attr(windows, ignore)]
fn effectful_untyped_passed_fx() {
todo!();
// test_roc_app(
// "crates/cli/tests/test-projects/effectful",
// "untyped_passed_fx.roc",
// &[],
// &[],
// &[],
// indoc!(
// r#"
// Before hello
// Hello, World!
// After hello
// "#
// ),
// UseValgrind::No,
// TestCliCommands::Dev,
// );
}
#[test]
#[cfg_attr(windows, ignore)]
fn effectful_ignore_result() {
todo!();
// test_roc_app(
// "crates/cli/tests/test-projects/effectful",
// "ignore_result.roc",
// &[],
// &[],
// &[],
// indoc!(
// r#"
// I asked for input and I ignored it. Deal with it! 😎
// "#
// ),
// UseValgrind::No,
// TestCliCommands::Dev,
// );
}
}

View file

@ -0,0 +1,21 @@
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
import pf.PlatformTasks
main! = \{} ->
multipleIn =
{ sequential <-
a: Ok 123,
b: Ok "abc",
c: Ok [123],
_d: Ok ["abc"],
_: Ok (Dict.single "a" "b"),
}?
PlatformTasks.putLine! "For multiple tasks: $(Inspect.toStr multipleIn)"
sequential : Result a err, Result b err, (a, b -> c) -> Result c err
sequential = \firstTask, secondTask, mapper ->
first = firstTask
second = secondTask
Ok (mapper first second)

View file

@ -0,0 +1,27 @@
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
import pf.Effect
main! : {} => {}
main! = \{} ->
first = ask! "What's your first name?"
last = ask! "What's your last name?"
Effect.putLine! "\nHi, $(first) $(last)!\n"
when Str.toU8 (ask! "How old are you?") is
Err InvalidNumStr ->
Effect.putLine! "Enter a valid number"
Ok age if age >= 18 ->
Effect.putLine! "\nNice! You can vote!"
Ok age ->
Effect.putLine! "\nYou'll be able to vote in $(Num.toStr (18 - age)) years"
Effect.putLine! "\nBye! 👋"
ask! : Str => Str
ask! = \question ->
Effect.putLine! question
Effect.getLine! {}

View file

@ -0,0 +1,7 @@
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
import pf.Effect
main! : {} => {}
main! = \{} ->
Effect.putLine! "I'm an effect 👻"

View file

@ -0,0 +1,8 @@
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
import pf.Effect
main! : {} => {}
main! = \{} ->
_ = Effect.getLine! {}
Effect.putLine! "I asked for input and I ignored it. Deal with it! 😎"

View file

@ -1,12 +1,12 @@
#
# Shows how Roc values can be logged
#
app [main] { pf: platform "platform/main.roc" }
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
import pf.PlatformTasks
import Community
main =
main! = \{} ->
Community.empty
|> Community.addPerson {
firstName: "John",

View file

@ -0,0 +1,16 @@
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
import pf.Effect
main! : {} => {}
main! = \{} ->
friends = ["Lu", "Marce", "Joaquin", "Chloé", "Mati", "Pedro"]
printAll! friends
printAll! : List Str => {}
printAll! = \friends ->
when friends is
[] -> {}
[first, .. as remaining] ->
Effect.putLine! first
printAll! remaining

View file

@ -0,0 +1,27 @@
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
import pf.Effect
main! : {} => {}
main! = \{} ->
["Welcome!", "What's your name?"]
|> forEach! Effect.putLine!
line = Effect.getLine! {}
if line == "secret" then
Effect.putLine! "You found the secret"
Effect.putLine! "Congratulations!"
else
{}
Effect.putLine! "You entered: $(line)"
Effect.putLine! "It is known"
forEach! : List a, (a => {}) => {}
forEach! = \l, f! ->
when l is
[] -> {}
[x, .. as xs] ->
f! x
forEach! xs f!

View file

@ -0,0 +1,12 @@
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
import pf.Effect
main! : {} => {}
main! = \{} ->
logged! "hello" (\{} -> Effect.putLine! "Hello, World!")
logged! = \name, fx! ->
Effect.putLine! "Before $(name)"
fx! {}
Effect.putLine! "After $(name)"

View file

@ -1,21 +0,0 @@
app [main] { pf: platform "platform/main.roc" }
import pf.PlatformTasks
main =
multipleIn =
{ sequential <-
a: Task.ok 123,
b: Task.ok "abc",
c: Task.ok [123],
_d: Task.ok ["abc"],
_: Task.ok (Dict.single "a" "b"),
}!
PlatformTasks.putLine! "For multiple tasks: $(Inspect.toStr multipleIn)"
sequential : Task a err, Task b err, (a, b -> c) -> Task c err
sequential = \firstTask, secondTask, mapper ->
first = firstTask!
second = secondTask!
Task.ok (mapper first second)

View file

@ -1,7 +0,0 @@
hosted PlatformTasks
exposes [putLine, getLine]
imports []
putLine : Str -> Task {} *
getLine : Task Str *

View file

@ -1,4 +0,0 @@
app [main] { pf: platform "main.roc" }
# just a stubbed app for building the test platform
main = Task.ok {}

View file

@ -1,9 +0,0 @@
platform "effects"
requires {} { main : Task {} [] }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Task {} []
mainForHost = main

View file

@ -1,11 +0,0 @@
app [main] { pf: platform "platform/main.roc" }
import pf.PlatformTasks
main : Task {} []
main =
line = PlatformTasks.getLine!
PlatformTasks.putLine! "You entered: $(line)"
PlatformTasks.putLine! "It is known"
Task.ok {}

View file

@ -0,0 +1,3 @@
module { passed } -> [exposed]
exposed = passed

View file

@ -0,0 +1,15 @@
module []
https = \url -> "https://$(url)"
expect
import Api { appId: "one", protocol: https }
Api.baseUrl == "https://api.example.com/one"
expect
import Api { appId: "two", protocol: https }
Api.getUser 1 == "https://api.example.com/two/users/1"
expect
import NoParams
NoParams.hello == "hello!"

View file

@ -0,0 +1,3 @@
module [hello]
hello = "hello!"

View file

@ -0,0 +1,14 @@
app [main] {
cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.16.0/O00IPk-Krg_diNS2dVWlI0ZQP794Vctxzv0ha96mK0E.tar.br",
}
import cli.Stdout
import cli.Stdin
import Alias { passed: Stdin.line } as In
import Alias { passed: Stdout.line } as Out
main =
Out.exposed! "Write something:"
input = In.exposed!
Out.exposed! input

View file

@ -0,0 +1,11 @@
app [main] {
cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.16.0/O00IPk-Krg_diNS2dVWlI0ZQP794Vctxzv0ha96mK0E.tar.br",
}
import Alias { passed: Task.ok {} }
main =
Task.loop! {} loop
loop = \{} ->
Task.map Alias.exposed \x -> Done x

View file

@ -1,7 +1,7 @@
app [main] { pf: platform "../effects/platform/main.roc" }
app [main!] { pf: platform "../test-platform-effects-zig/main.roc" }
import pf.PlatformTasks
import Menu { echo: PlatformTasks.putLine }
import pf.Effect
import Menu { echo: \str -> Effect.putLine! str }
main =
main! = \{} ->
Menu.menu "Agus"

View file

@ -0,0 +1,7 @@
hosted Effect
exposes [putLine!, getLine!]
imports []
putLine! : Str => {}
getLine! : {} => Str

View file

@ -0,0 +1,4 @@
app [main!] { pf: platform "main.roc" }
# just a stubbed app for building the test platform
main! = Ok {}

View file

@ -12,9 +12,6 @@ const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed_generic([*]u8) void;
extern fn roc__mainForHost_1_exposed_size() i64;
extern fn roc__mainForHost_0_caller(*const u8, [*]u8, [*]u8) void;
extern fn roc__mainForHost_0_size() i64;
extern fn roc__mainForHost_0_result_size() i64;
const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
@ -124,40 +121,9 @@ pub export fn main() u8 {
roc__mainForHost_1_exposed_generic(output);
call_the_closure(output);
return 0;
}
fn call_the_closure(closure_data_pointer: [*]u8) void {
const allocator = std.heap.page_allocator;
const size = roc__mainForHost_0_result_size();
if (size == 0) {
// the function call returns an empty record
// allocating 0 bytes causes issues because the allocator will return a NULL pointer
// So it's special-cased
const flags: u8 = 0;
var result: [1]u8 = .{0};
roc__mainForHost_0_caller(&flags, closure_data_pointer, &result);
return;
}
const raw_output = allocator.alignedAlloc(u8, @alignOf(u64), @as(usize, @intCast(size))) catch unreachable;
var output = @as([*]u8, @ptrCast(raw_output));
defer {
allocator.free(raw_output);
}
const flags: u8 = 0;
roc__mainForHost_0_caller(&flags, closure_data_pointer, output);
return;
}
pub export fn roc_fx_getLine() str.RocStr {
return roc_fx_getLine_help() catch return str.RocStr.empty();
}

View file

@ -0,0 +1,9 @@
platform "effects"
requires {} { main! : {} => {} }
exposes []
packages {}
imports []
provides [mainForHost!]
mainForHost! : {} => {}
mainForHost! = \{} -> main! {}

View file

@ -222,8 +222,8 @@ sequence = \taskList ->
Task.loop (taskList, List.withCapacity (List.len taskList)) \(tasks, values) ->
when tasks is
[task, .. as rest] ->
value = task!
Task.ok (Step (rest, List.append values value))
Task.map task \value ->
Step (rest, List.append values value)
[] ->
Task.ok (Done values)

View file

@ -4,7 +4,9 @@ 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;
use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader};
use roc_parse::ast::{
AssignedField, ExtractSpaces, FunctionArrow, Pattern, Tag, TypeAnnotation, TypeHeader,
};
use roc_problem::can::ShadowKind;
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
@ -448,7 +450,7 @@ pub fn find_type_def_symbols(
stack.push(&t.value);
}
}
Function(arguments, result) => {
Function(arguments, _arrow, result) => {
for t in arguments.iter() {
stack.push(&t.value);
}
@ -554,7 +556,7 @@ fn can_annotation_help(
use roc_parse::ast::TypeAnnotation::*;
match annotation {
Function(argument_types, return_type) => {
Function(argument_types, arrow, return_type) => {
let mut args = Vec::new();
for arg in *argument_types {
@ -589,7 +591,12 @@ fn can_annotation_help(
introduced_variables.insert_lambda_set(lambda_set);
let closure = Type::Variable(lambda_set);
Type::Function(args, Box::new(closure), Box::new(ret))
let fx_type = match arrow {
FunctionArrow::Pure => Type::Pure,
FunctionArrow::Effectful => Type::Effectful,
};
Type::Function(args, Box::new(closure), Box::new(ret), Box::new(fx_type))
}
Apply(module_name, ident, type_arguments) => {
let symbol = match make_apply_symbol(env, region, scope, module_name, ident, references)

View file

@ -418,6 +418,7 @@ fn defn(
expr_var: var_store.fresh(),
pattern_vars: SendMap::default(),
annotation: None,
kind: crate::def::DefKind::Let,
}
}
@ -446,6 +447,7 @@ fn defn_help(
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
return_type: ret_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: Vec::new(),
@ -547,6 +549,7 @@ fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel)
expr_var: record_var,
pattern_vars: SendMap::default(),
annotation: None,
kind: crate::def::DefKind::Let,
};
let body = LetNonRec(Box::new(def), Box::new(no_region(cont)));

View file

@ -6,7 +6,7 @@ use crate::abilities::SpecializationId;
use crate::exhaustive::{ExhaustiveContext, SketchedRows};
use crate::expected::{Expected, PExpected};
use roc_collections::soa::{index_push_new, slice_extend_new};
use roc_module::ident::TagName;
use roc_module::ident::{IdentSuffix, TagName};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Loc, Region};
use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, Variable};
@ -29,6 +29,8 @@ pub struct Constraints {
pub eq: Vec<Eq>,
pub pattern_eq: Vec<PatternEq>,
pub cycles: Vec<Cycle>,
pub fx_call_constraints: Vec<FxCallConstraint>,
pub fx_suffix_constraints: Vec<FxSuffixConstraint>,
}
impl std::fmt::Debug for Constraints {
@ -50,6 +52,8 @@ impl std::fmt::Debug for Constraints {
.field("eq", &self.eq)
.field("pattern_eq", &self.pattern_eq)
.field("cycles", &self.cycles)
.field("fx_call_constraints", &self.fx_call_constraints)
.field("fx_suffix_constraints", &self.fx_suffix_constraints)
.finish()
}
}
@ -81,6 +85,8 @@ impl Constraints {
let eq = Vec::new();
let pattern_eq = Vec::new();
let cycles = Vec::new();
let fx_call_constraints = Vec::with_capacity(16);
let fx_suffix_constraints = Vec::new();
categories.extend([
Category::Record,
@ -130,6 +136,8 @@ impl Constraints {
eq,
pattern_eq,
cycles,
fx_call_constraints,
fx_suffix_constraints,
}
}
@ -574,6 +582,64 @@ impl Constraints {
Constraint::Lookup(symbol, expected_index, region)
}
pub fn fx_call(
&mut self,
call_fx_var: Variable,
call_kind: FxCallKind,
call_region: Region,
expectation: Option<FxExpectation>,
) -> Constraint {
let constraint = FxCallConstraint {
call_fx_var,
call_kind,
call_region,
expectation,
};
let constraint_index = index_push_new(&mut self.fx_call_constraints, constraint);
Constraint::FxCall(constraint_index)
}
pub fn fx_pattern_suffix(
&mut self,
symbol: Symbol,
type_index: TypeOrVar,
region: Region,
) -> Constraint {
let constraint = FxSuffixConstraint {
kind: FxSuffixKind::Pattern(symbol),
type_index,
region,
};
let constraint_index = index_push_new(&mut self.fx_suffix_constraints, constraint);
Constraint::FxSuffix(constraint_index)
}
pub fn fx_record_field_suffix(
&mut self,
suffix: IdentSuffix,
variable: Variable,
region: Region,
) -> Constraint {
let type_index = Self::push_type_variable(variable);
let constraint = FxSuffixConstraint {
kind: FxSuffixKind::RecordField(suffix),
type_index,
region,
};
let constraint_index = index_push_new(&mut self.fx_suffix_constraints, constraint);
Constraint::FxSuffix(constraint_index)
}
pub fn flex_to_pure(&mut self, fx_var: Variable) -> Constraint {
Constraint::FlexToPure(fx_var)
}
pub fn contains_save_the_environment(&self, constraint: &Constraint) -> bool {
match constraint {
Constraint::SaveTheEnvironment => true,
@ -598,6 +664,10 @@ impl Constraints {
| Constraint::Store(..)
| Constraint::Lookup(..)
| Constraint::Pattern(..)
| Constraint::ExpectEffectful(..)
| Constraint::FxCall(_)
| Constraint::FxSuffix(_)
| Constraint::FlexToPure(_)
| Constraint::True
| Constraint::IsOpenType(_)
| Constraint::IncludesTag(_)
@ -770,6 +840,14 @@ pub enum Constraint {
Index<PatternCategory>,
Region,
),
/// Check call fx against enclosing function fx
FxCall(Index<FxCallConstraint>),
/// Require idents to be accurately suffixed
FxSuffix(Index<FxSuffixConstraint>),
/// Set an fx var as pure if flex (no effectful functions were called)
FlexToPure(Variable),
/// Expect statement or ignored def to be effectful
ExpectEffectful(Variable, ExpectEffectfulReason, Region),
/// Used for things that always unify, e.g. blanks and runtime errors
True,
SaveTheEnvironment,
@ -842,6 +920,56 @@ pub struct Cycle {
pub expr_regions: Slice<Region>,
}
#[derive(Debug)]
pub struct FxCallConstraint {
pub call_fx_var: Variable,
pub call_kind: FxCallKind,
pub call_region: Region,
pub expectation: Option<FxExpectation>,
}
#[derive(Debug, Clone, Copy)]
pub struct FxExpectation {
pub fx_var: Variable,
pub ann_region: Option<Region>,
}
#[derive(Debug, Clone, Copy)]
pub enum FxCallKind {
Call(Option<Symbol>),
Stmt,
Ignored,
}
#[derive(Debug, Clone, Copy)]
pub struct FxSuffixConstraint {
pub type_index: TypeOrVar,
pub kind: FxSuffixKind,
pub region: Region,
}
#[derive(Debug, Clone, Copy)]
pub enum FxSuffixKind {
Let(Symbol),
Pattern(Symbol),
RecordField(IdentSuffix),
}
impl FxSuffixKind {
pub fn suffix(&self) -> IdentSuffix {
match self {
Self::Let(symbol) | Self::Pattern(symbol) => symbol.suffix(),
Self::RecordField(suffix) => *suffix,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum ExpectEffectfulReason {
Stmt,
Ignored,
}
/// Custom impl to limit vertical space used by the debug output
impl std::fmt::Debug for Constraint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -858,6 +986,18 @@ impl std::fmt::Debug for Constraint {
Self::Pattern(arg0, arg1, arg2, arg3) => {
write!(f, "Pattern({arg0:?}, {arg1:?}, {arg2:?}, {arg3:?})")
}
Self::FxCall(arg0) => {
write!(f, "FxCall({arg0:?})")
}
Self::FxSuffix(arg0) => {
write!(f, "FxSuffix({arg0:?})")
}
Self::ExpectEffectful(arg0, arg1, arg2) => {
write!(f, "EffectfulStmt({arg0:?}, {arg1:?}, {arg2:?})")
}
Self::FlexToPure(arg0) => {
write!(f, "FlexToPure({arg0:?})")
}
Self::True => write!(f, "True"),
Self::SaveTheEnvironment => write!(f, "SaveTheEnvironment"),
Self::Let(arg0, arg1) => f.debug_tuple("Let").field(arg0).field(arg1).finish(),

View file

@ -1,5 +1,5 @@
use crate::{
def::Def,
def::{Def, DefKind},
expr::{
ClosureData, Expr, Field, OpaqueWrapFunctionData, StructAccessorData, WhenBranchPattern,
},
@ -378,6 +378,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
expr_var,
pattern_vars,
annotation,
kind,
}| Def {
loc_pattern: loc_pattern.map(|p| deep_copy_pattern_help(env, copied, p)),
loc_expr: loc_expr.map(|e| go_help!(e)),
@ -386,6 +387,11 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
// Annotation should only be used in constraining, don't clone before
// constraining :)
annotation: annotation.clone(),
kind: match kind {
DefKind::Let => DefKind::Let,
DefKind::Stmt(v) => DefKind::Stmt(sub!(*v)),
DefKind::Ignored(v) => DefKind::Ignored(sub!(*v)),
},
},
)
.collect(),
@ -399,6 +405,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
expr_var,
pattern_vars,
annotation,
kind,
} = &**def;
let def = Def {
loc_pattern: loc_pattern.map(|p| deep_copy_pattern_help(env, copied, p)),
@ -408,18 +415,20 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
// Annotation should only be used in constraining, don't clone before
// constraining :)
annotation: annotation.clone(),
kind: *kind,
};
LetNonRec(Box::new(def), Box::new(body.map(|e| go_help!(e))))
}
Call(f, args, called_via) => {
let (fn_var, fn_expr, clos_var, ret_var) = &**f;
let (fn_var, fn_expr, clos_var, ret_var, fx_var) = &**f;
Call(
Box::new((
sub!(*fn_var),
fn_expr.map(|e| go_help!(e)),
sub!(*clos_var),
sub!(*ret_var),
sub!(*fx_var),
)),
args.iter()
.map(|(var, expr)| (sub!(*var), expr.map(|e| go_help!(e))))
@ -456,6 +465,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
function_type,
closure_type,
return_type,
fx_type,
early_returns,
name,
captured_symbols,
@ -466,6 +476,7 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
function_type: sub!(*function_type),
closure_type: sub!(*closure_type),
return_type: sub!(*return_type),
fx_type: sub!(*fx_type),
early_returns: early_returns
.iter()
.map(|(var, region)| (sub!(*var), *region))
@ -974,7 +985,7 @@ fn deep_copy_type_vars<C: CopyEnv>(
// Everything else is a mechanical descent.
Structure(flat_type) => match flat_type {
EmptyRecord | EmptyTagUnion => Structure(flat_type),
EmptyRecord | EmptyTagUnion | EffectfulFunc => Structure(flat_type),
Apply(symbol, arguments) => {
descend_slice!(arguments);
@ -983,15 +994,21 @@ fn deep_copy_type_vars<C: CopyEnv>(
Structure(Apply(symbol, new_arguments))
})
}
Func(arguments, closure_var, ret_var) => {
Func(arguments, closure_var, ret_var, fx_var) => {
descend_slice!(arguments);
let new_closure_var = descend_var!(closure_var);
let new_ret_var = descend_var!(ret_var);
let new_fx_var = descend_var!(fx_var);
perform_clone!({
let new_arguments = clone_var_slice!(arguments);
Structure(Func(new_arguments, new_closure_var, new_ret_var))
Structure(Func(
new_arguments,
new_closure_var,
new_ret_var,
new_fx_var,
))
})
}
Record(fields, ext_var) => {
@ -1180,6 +1197,8 @@ fn deep_copy_type_vars<C: CopyEnv>(
})
}
ErasedLambda => ErasedLambda,
Pure => Pure,
Effectful => Effectful,
RangedNumber(range) => {
perform_clone!(RangedNumber(range))

View file

@ -1,6 +1,6 @@
//! Pretty-prints the canonical AST back to check our work - do things look reasonable?
use crate::def::Def;
use crate::def::{Def, DefKind};
use crate::expr::Expr::{self, *};
use crate::expr::{
ClosureData, DeclarationTag, Declarations, FunctionDef, OpaqueWrapFunctionData,
@ -107,9 +107,14 @@ fn def<'a>(c: &Ctx, f: &'a Arena<'a>, d: &'a Def) -> DocBuilder<'a, Arena<'a>> {
expr_var: _,
pattern_vars: _,
annotation: _,
kind,
} = d;
def_help(c, f, &loc_pattern.value, &loc_expr.value)
match kind {
DefKind::Let => def_help(c, f, &loc_pattern.value, &loc_expr.value),
DefKind::Ignored(_) => def_help(c, f, &loc_pattern.value, &loc_expr.value),
DefKind::Stmt(_) => expr(c, EPrec::Free, f, &loc_expr.value),
}
}
fn def_symbol_help<'a>(
@ -267,7 +272,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
.append(expr(c, Free, f, &body.value))
.group(),
Call(fun, args, _) => {
let (_, fun, _, _) = &**fun;
let (_, fun, _, _, _) = &**fun;
maybe_paren!(
Free,
p,

View file

@ -10,6 +10,7 @@ use crate::annotation::IntroducedVariables;
use crate::annotation::OwnedNamedOrAble;
use crate::derive;
use crate::env::Env;
use crate::env::FxMode;
use crate::expr::canonicalize_record;
use crate::expr::get_lookup_symbols;
use crate::expr::AnnotatedMark;
@ -69,6 +70,7 @@ pub struct Def {
pub expr_var: Variable,
pub pattern_vars: SendMap<Symbol, Variable>,
pub annotation: Option<Annotation>,
pub kind: DefKind,
}
impl Def {
@ -89,6 +91,38 @@ impl Def {
}
}
#[derive(Clone, Copy, Debug)]
pub enum DefKind {
/// A def that introduces identifiers
Let,
/// A standalone statement with an fx variable
Stmt(Variable),
/// Ignored result, must be effectful
Ignored(Variable),
}
impl DefKind {
pub fn map_var<F: Fn(Variable) -> Variable>(self, f: F) -> Self {
match self {
DefKind::Let => DefKind::Let,
DefKind::Stmt(v) => DefKind::Stmt(f(v)),
DefKind::Ignored(v) => DefKind::Ignored(f(v)),
}
}
pub fn from_pattern(var_store: &mut VarStore, pattern: &Loc<Pattern>) -> Self {
if BindingsFromPattern::new(pattern)
.peekable()
.peek()
.is_none()
{
DefKind::Ignored(var_store.fresh())
} else {
DefKind::Let
}
}
}
#[derive(Clone, Debug)]
pub struct Annotation {
pub signature: Type,
@ -127,6 +161,7 @@ impl Annotation {
arg_types,
Box::new(Type::Variable(var_store.fresh())),
Box::new(self.signature.clone()),
Box::new(Type::Variable(var_store.fresh())),
);
}
}
@ -194,22 +229,25 @@ enum PendingValueDef<'a> {
Option<Loc<ast::TypeAnnotation<'a>>>,
Loc<ast::StrLiteral<'a>>,
),
/// A standalone statement
Stmt(&'a Loc<ast::Expr<'a>>),
}
impl PendingValueDef<'_> {
fn loc_pattern(&self) -> &Loc<Pattern> {
fn loc_pattern(&self) -> Option<&Loc<Pattern>> {
match self {
PendingValueDef::AnnotationOnly(loc_pattern, _) => loc_pattern,
PendingValueDef::Body(loc_pattern, _) => loc_pattern,
PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern,
PendingValueDef::AnnotationOnly(loc_pattern, _) => Some(loc_pattern),
PendingValueDef::Body(loc_pattern, _) => Some(loc_pattern),
PendingValueDef::TypedBody(_, loc_pattern, _, _) => Some(loc_pattern),
PendingValueDef::ImportParams {
loc_pattern,
symbol: _,
variable: _,
module_id: _,
opt_provided: _,
} => loc_pattern,
PendingValueDef::IngestedFile(loc_pattern, _, _) => loc_pattern,
} => Some(loc_pattern),
PendingValueDef::IngestedFile(loc_pattern, _, _) => Some(loc_pattern),
PendingValueDef::Stmt(_) => None,
}
}
}
@ -1181,19 +1219,18 @@ fn canonicalize_value_defs<'a>(
}
PendingValue::InvalidIngestedFile => { /* skip */ }
PendingValue::ImportNameConflict => { /* skip */ }
PendingValue::StmtAfterExpr => { /* skip */ }
}
}
let mut symbol_to_index: Vec<(IdentId, u32)> = Vec::with_capacity(pending_value_defs.len());
for (def_index, pending_def) in pending_value_defs.iter().enumerate() {
let mut new_bindings = BindingsFromPattern::new(pending_def.loc_pattern()).peekable();
let Some(loc_pattern) = pending_def.loc_pattern() else {
continue;
};
if new_bindings.peek().is_none() {
env.problem(Problem::NoIdentifiersIntroduced(
pending_def.loc_pattern().region,
));
}
let new_bindings = BindingsFromPattern::new(loc_pattern).peekable();
for (s, r) in new_bindings {
// store the top-level defs, used to ensure that closures won't capture them
@ -1230,6 +1267,14 @@ fn canonicalize_value_defs<'a>(
output = temp_output.output;
if let (PatternType::TopLevelDef, DefKind::Ignored(_)) =
(pattern_type, temp_output.def.kind)
{
env.problems.push(Problem::NoIdentifiersIntroduced(
temp_output.def.loc_pattern.region,
))
}
defs.push(Some(temp_output.def));
def_ordering.insert_symbol_references(def_id as u32, &temp_output.references)
@ -2215,6 +2260,7 @@ fn single_can_def(
expr_var: Variable,
opt_loc_annotation: Option<Loc<crate::annotation::Annotation>>,
pattern_vars: SendMap<Symbol, Variable>,
kind: DefKind,
) -> Def {
let def_annotation = opt_loc_annotation.map(|loc_annotation| Annotation {
signature: loc_annotation.value.typ,
@ -2232,6 +2278,7 @@ fn single_can_def(
},
pattern_vars,
annotation: def_annotation,
kind,
}
}
@ -2354,6 +2401,7 @@ fn canonicalize_pending_value_def<'a>(
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
return_type: var_store.fresh(),
fx_type: var_store.fresh(),
early_returns: scope.early_returns.clone(),
name: symbol,
captured_symbols: Vec::new(),
@ -2371,6 +2419,7 @@ fn canonicalize_pending_value_def<'a>(
expr_var,
Some(Loc::at(loc_ann.region, type_annotation)),
vars_by_symbol.clone(),
DefKind::Let,
);
DefOutput {
@ -2406,10 +2455,13 @@ fn canonicalize_pending_value_def<'a>(
loc_can_pattern,
loc_expr,
Some(Loc::at(loc_ann.region, type_annotation)),
DefKind::Let,
)
}
Body(loc_can_pattern, loc_expr) => {
//
let def_kind = DefKind::from_pattern(var_store, &loc_can_pattern);
canonicalize_pending_body(
env,
output,
@ -2418,6 +2470,20 @@ fn canonicalize_pending_value_def<'a>(
loc_can_pattern,
loc_expr,
None,
def_kind,
)
}
Stmt(loc_expr) => {
let fx_var = var_store.fresh();
canonicalize_pending_body(
env,
output,
scope,
var_store,
Loc::at(loc_expr.region, Pattern::Underscore),
loc_expr,
None,
DefKind::Stmt(fx_var),
)
}
ImportParams {
@ -2458,6 +2524,7 @@ fn canonicalize_pending_value_def<'a>(
var_store.fresh(),
None,
SendMap::default(),
DefKind::Let,
);
DefOutput {
@ -2527,6 +2594,7 @@ fn canonicalize_pending_value_def<'a>(
var_store.fresh(),
opt_loc_can_ann,
SendMap::default(),
DefKind::Let,
);
DefOutput {
@ -2565,6 +2633,7 @@ fn canonicalize_pending_body<'a>(
loc_expr: &'a Loc<ast::Expr>,
opt_loc_annotation: Option<Loc<crate::annotation::Annotation>>,
kind: DefKind,
) -> DefOutput {
let mut loc_value = &loc_expr.value;
@ -2683,6 +2752,7 @@ fn canonicalize_pending_body<'a>(
expr_var,
opt_loc_annotation,
vars_by_symbol,
kind,
);
DefOutput {
@ -3017,6 +3087,7 @@ enum PendingValue<'a> {
SignatureDefMismatch,
InvalidIngestedFile,
ImportNameConflict,
StmtAfterExpr,
}
struct PendingExpectOrDbg<'a> {
@ -3071,10 +3142,7 @@ fn to_pending_value_def<'a>(
loc_pattern.region,
);
PendingValue::Def(PendingValueDef::AnnotationOnly(
loc_can_pattern,
loc_ann,
))
PendingValue::Def(PendingValueDef::AnnotationOnly(loc_can_pattern, loc_ann))
}
Body(loc_pattern, loc_expr) => {
// This takes care of checking for shadowing and adding idents to scope.
@ -3180,15 +3248,17 @@ fn to_pending_value_def<'a>(
// 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_region = module_import
.params
.map(|p| p.params.region)
.unwrap_or(region);
let params_var = var_store.fresh();
let params =
PendingModuleImportParams {
symbol: params_sym,
variable: params_var,
loc_pattern: Loc::at(params_region, Pattern::Identifier(params_sym)),
opt_provided: module_import.params.map(|p| p.params.value),
};
let params = PendingModuleImportParams {
symbol: params_sym,
variable: params_var,
loc_pattern: Loc::at(params_region, Pattern::Identifier(params_sym)),
opt_provided: module_import.params.map(|p| p.params.value),
};
let provided_params = if module_import.params.is_some() {
// Only add params to scope if they are provided
Some((params_var, params_sym))
@ -3217,8 +3287,12 @@ fn to_pending_value_def<'a>(
.map(|kw| kw.item.items)
.unwrap_or_default();
if exposed_names.is_empty() && !env.home.is_builtin() && module_id.is_automatically_imported() {
env.problems.push(Problem::ExplicitBuiltinImport(module_id, region));
if exposed_names.is_empty()
&& !env.home.is_builtin()
&& module_id.is_automatically_imported()
{
env.problems
.push(Problem::ExplicitBuiltinImport(module_id, region));
}
let exposed_ids = env
@ -3238,7 +3312,9 @@ fn to_pending_value_def<'a>(
let symbol = Symbol::new(module_id, ident_id);
exposed_symbols.push((symbol, loc_name.region));
if let Err((_shadowed_symbol, existing_symbol_region)) = scope.import_symbol(ident, symbol, loc_name.region) {
if let Err((_shadowed_symbol, existing_symbol_region)) =
scope.import_symbol(ident, symbol, loc_name.region)
{
if symbol.is_automatically_imported() {
env.problem(Problem::ExplicitBuiltinTypeImport(
symbol,
@ -3253,14 +3329,12 @@ fn to_pending_value_def<'a>(
}
}
}
None => {
env.problem(Problem::RuntimeError(RuntimeError::ValueNotExposed {
module_name: module_name.clone(),
ident,
region: loc_name.region,
exposed_values: exposed_ids.exposed_values(),
}))
}
None => env.problem(Problem::RuntimeError(RuntimeError::ValueNotExposed {
module_name: module_name.clone(),
ident,
region: loc_name.region,
exposed_values: exposed_ids.exposed_values(),
})),
}
}
@ -3275,12 +3349,12 @@ fn to_pending_value_def<'a>(
let loc_name = ingested_file.name.item;
let symbol = match scope.introduce(loc_name.value.into(), loc_name.region) {
Ok(symbol ) => symbol,
Ok(symbol) => symbol,
Err((original, shadow, _)) => {
env.problem(Problem::Shadowing {
original_region: original.region,
shadow,
kind: ShadowKind::Variable
kind: ShadowKind::Variable,
});
return PendingValue::InvalidIngestedFile;
@ -3289,9 +3363,20 @@ fn to_pending_value_def<'a>(
let loc_pattern = Loc::at(loc_name.region, Pattern::Identifier(symbol));
PendingValue::Def(PendingValueDef::IngestedFile(loc_pattern, ingested_file.annotation.map(|ann| ann.annotation), ingested_file.path))
PendingValue::Def(PendingValueDef::IngestedFile(
loc_pattern,
ingested_file.annotation.map(|ann| ann.annotation),
ingested_file.path,
))
}
StmtAfterExpr => PendingValue::StmtAfterExpr,
Stmt(expr) => {
if env.fx_mode == FxMode::Task {
internal_error!("a Stmt was not desugared correctly, should have been converted to a Body(...) in desguar")
}
PendingValue::Def(PendingValueDef::Stmt(expr))
}
Stmt(_) => internal_error!("a Stmt was not desugared correctly, should have been converted to a Body(...) in desguar"),
}
}

View file

@ -1,6 +1,6 @@
#![allow(clippy::manual_map)]
use crate::env::Env;
use crate::env::{Env, FxMode};
use crate::scope::Scope;
use crate::suffixed::{apply_try_function, unwrap_suffixed_expression, EUnwrapped};
use bumpalo::collections::Vec;
@ -11,8 +11,8 @@ use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{
AssignedField, Collection, Defs, ModuleImportParams, Pattern, StrLiteral, StrSegment,
TypeAnnotation, ValueDef, WhenBranch,
is_expr_suffixed, AssignedField, Collection, Defs, ModuleImportParams, Pattern, StrLiteral,
StrSegment, TypeAnnotation, ValueDef, WhenBranch,
};
use roc_problem::can::Problem;
use roc_region::all::{Loc, Region};
@ -200,11 +200,29 @@ fn desugar_value_def<'a>(
}
IngestedFileImport(_) => *def,
StmtAfterExpr => internal_error!(
"StmtAfterExpression is only created during desugaring, so it shouldn't exist here."
),
Stmt(stmt_expr) => {
if env.fx_mode == FxMode::PurityInference {
// In purity inference mode, statements aren't fully desugared here
// so we can provide better errors
return Stmt(desugar_expr(env, scope, stmt_expr));
}
// desugar `stmt_expr!` to
// _ : {}
// _ = stmt_expr!
let desugared_expr = desugar_expr(env, scope, stmt_expr);
if !is_expr_suffixed(&desugared_expr.value) {
env.problems.push(Problem::StmtAfterExpr(stmt_expr.region));
return ValueDef::StmtAfterExpr;
}
let region = stmt_expr.region;
let new_pat = env
.arena
@ -221,7 +239,7 @@ fn desugar_value_def<'a>(
)),
lines_between: &[],
body_pattern: new_pat,
body_expr: desugar_expr(env, scope, stmt_expr),
body_expr: desugared_expr,
}
}
}
@ -364,7 +382,7 @@ pub fn desugar_value_def_suffixed<'a>(arena: &'a Bump, value_def: ValueDef<'a>)
// TODO support desugaring of Dbg and ExpectFx
Dbg { .. } | ExpectFx { .. } => value_def,
ModuleImport { .. } | IngestedFileImport(_) => value_def,
ModuleImport { .. } | IngestedFileImport(_) | StmtAfterExpr => value_def,
Stmt(..) => {
internal_error!(
@ -387,7 +405,6 @@ pub fn desugar_expr<'a>(
| NonBase10Int { .. }
| SingleQuote(_)
| AccessorFunction(_)
| Var { .. }
| Underscore { .. }
| MalformedIdent(_, _)
| MalformedClosure
@ -401,6 +418,23 @@ pub fn desugar_expr<'a>(
| Crash
| Try => loc_expr,
Var { module_name, ident } => {
if env.fx_mode == FxMode::Task && ident.ends_with('!') {
env.arena.alloc(Loc::at(
Region::new(loc_expr.region.start(), loc_expr.region.end().sub(1)),
TrySuffix {
expr: env.arena.alloc(Var {
module_name,
ident: ident.trim_end_matches('!'),
}),
target: roc_parse::ast::TryTarget::Task,
},
))
} else {
loc_expr
}
}
Str(str_literal) => match str_literal {
StrLiteral::PlainLine(_) => loc_expr,
StrLiteral::Line(segments) => {

View file

@ -1,4 +1,4 @@
use crate::def::Def;
use crate::def::{Def, DefKind};
use crate::expr::{AnnotatedMark, ClosureData, Expr, Recursive};
use crate::pattern::Pattern;
use crate::scope::Scope;
@ -33,7 +33,7 @@ pub fn build_host_exposed_def(
let def_body = {
match typ.shallow_structural_dealias() {
Type::Function(args, _, _) => {
Type::Function(args, _, _, fx) if **fx == Type::Pure => {
for i in 0..args.len() {
let name = format!("closure_arg_{ident}_{i}");
@ -72,6 +72,7 @@ pub fn build_host_exposed_def(
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
return_type: var_store.fresh(),
fx_type: var_store.fresh(),
early_returns: vec![],
name: task_closure_symbol,
captured_symbols,
@ -99,6 +100,7 @@ pub fn build_host_exposed_def(
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
return_type: var_store.fresh(),
fx_type: var_store.fresh(),
early_returns: vec![],
name: symbol,
captured_symbols: std::vec::Vec::new(),
@ -107,6 +109,47 @@ pub fn build_host_exposed_def(
loc_body: Box::new(Loc::at_zero(body)),
})
}
Type::Function(args, _, _, fx) if **fx == Type::Effectful => {
for i in 0..args.len() {
let name = format!("{ident}_arg_{i}");
let arg_symbol = {
let ident = name.clone().into();
scope.introduce(ident, Region::zero()).unwrap()
};
let arg_var = var_store.fresh();
arguments.push((
arg_var,
AnnotatedMark::new(var_store),
Loc::at_zero(Pattern::Identifier(arg_symbol)),
));
linked_symbol_arguments.push((arg_var, Expr::Var(arg_symbol, arg_var)));
}
let ident_without_bang = ident.trim_end_matches('!');
let foreign_symbol_name = format!("roc_fx_{ident_without_bang}");
let foreign_call = Expr::ForeignCall {
foreign_symbol: foreign_symbol_name.into(),
args: linked_symbol_arguments,
ret_var: var_store.fresh(),
};
Expr::Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
return_type: var_store.fresh(),
fx_type: var_store.fresh(),
early_returns: vec![],
name: symbol,
captured_symbols: std::vec::Vec::new(),
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Loc::at_zero(foreign_call)),
})
}
_ => {
// not a function
@ -128,6 +171,7 @@ pub fn build_host_exposed_def(
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
return_type: var_store.fresh(),
fx_type: var_store.fresh(),
early_returns: vec![],
name: task_closure_symbol,
captured_symbols,
@ -167,6 +211,7 @@ pub fn build_host_exposed_def(
expr_var,
pattern_vars,
annotation: Some(def_annotation),
kind: DefKind::Let,
}
}
@ -178,11 +223,13 @@ fn build_fresh_opaque_variables(
let ok_var = var_store.fresh();
let err_var = var_store.fresh();
let result_var = var_store.fresh();
let fx_var = var_store.fresh();
let actual = Type::Function(
vec![Type::EmptyRec],
Box::new(Type::Variable(closure_var)),
Box::new(Type::Variable(result_var)),
Box::new(Type::Variable(fx_var)),
);
let type_arguments = vec![

View file

@ -45,6 +45,8 @@ pub struct Env<'a> {
pub opt_shorthand: Option<&'a str>,
pub fx_mode: FxMode,
pub src: &'a str,
/// Lazily calculated line info. This data is only needed if the code contains calls to `dbg`,
@ -54,6 +56,7 @@ pub struct Env<'a> {
}
impl<'a> Env<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
arena: &'a Bump,
src: &'a str,
@ -62,6 +65,7 @@ impl<'a> Env<'a> {
dep_idents: &'a IdentIdsByModule,
qualified_module_ids: &'a PackageModuleIds<'a>,
opt_shorthand: Option<&'a str>,
fx_mode: FxMode,
) -> Env<'a> {
Env {
arena,
@ -79,6 +83,7 @@ impl<'a> Env<'a> {
home_params_record: None,
opt_shorthand,
line_info: arena.alloc(None),
fx_mode,
}
}
@ -237,3 +242,9 @@ impl<'a> Env<'a> {
self.line_info.as_ref().unwrap()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FxMode {
PurityInference,
Task,
}

View file

@ -141,7 +141,9 @@ fn index_var(
| Content::RigidAbleVar(_, _)
| Content::LambdaSet(_)
| Content::ErasedLambda
| Content::RangedNumber(..) => return Err(TypeError),
| Content::RangedNumber(..)
| Content::Pure
| Content::Effectful => return Err(TypeError),
Content::Error => return Err(TypeError),
Content::RecursionVar {
structure,
@ -150,7 +152,8 @@ fn index_var(
var = *structure;
}
Content::Structure(structure) => match structure {
FlatType::Func(_, _, _) => return Err(TypeError),
FlatType::Func(_, _, _, _) => return Err(TypeError),
FlatType::EffectfulFunc => return Err(TypeError),
FlatType::Apply(Symbol::LIST_LIST, args) => {
match (subs.get_subs_slice(*args), ctor) {
([elem_var], IndexCtor::List) => {

View file

@ -1,7 +1,7 @@
use crate::abilities::SpecializationId;
use crate::annotation::{freshen_opaque_def, IntroducedVariables};
use crate::builtins::builtin_defs_map;
use crate::def::{can_defs_with_return, Annotation, Def};
use crate::def::{can_defs_with_return, Annotation, Def, DefKind};
use crate::env::Env;
use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
@ -155,7 +155,7 @@ pub enum Expr {
/// This is *only* for calling functions, not for tag application.
/// The Tag variant contains any applied values inside it.
Call(
Box<(Variable, Loc<Expr>, Variable, Variable)>,
Box<(Variable, Loc<Expr>, Variable, Variable, Variable)>,
Vec<(Variable, Loc<Expr>)>,
CalledVia,
),
@ -407,6 +407,7 @@ pub struct ClosureData {
pub function_type: Variable,
pub closure_type: Variable,
pub return_type: Variable,
pub fx_type: Variable,
pub early_returns: Vec<(Variable, Region)>,
pub name: Symbol,
pub captured_symbols: Vec<(Symbol, Variable)>,
@ -484,6 +485,7 @@ impl StructAccessorData {
function_type: function_var,
closure_type: closure_var,
return_type: field_var,
fx_type: Variable::PURE,
early_returns: vec![],
name,
captured_symbols: vec![],
@ -558,6 +560,7 @@ impl OpaqueWrapFunctionData {
function_type: function_var,
closure_type: closure_var,
return_type: opaque_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: function_name,
captured_symbols: vec![],
@ -927,6 +930,7 @@ pub fn canonicalize_expr<'a>(
fn_expr,
var_store.fresh(),
var_store.fresh(),
var_store.fresh(),
)),
args,
*application_style,
@ -966,6 +970,7 @@ pub fn canonicalize_expr<'a>(
fn_expr,
var_store.fresh(),
var_store.fresh(),
var_store.fresh(),
)),
args,
*application_style,
@ -1687,6 +1692,7 @@ fn canonicalize_closure_body<'a>(
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
return_type: return_type_var,
fx_type: var_store.fresh(),
early_returns: scope.early_returns.clone(),
name: symbol,
captured_symbols,
@ -2282,6 +2288,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
expr_var: def.expr_var,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
kind: def.kind,
});
}
@ -2303,6 +2310,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
expr_var: def.expr_var,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
kind: def.kind,
};
let loc_expr = Loc {
@ -2317,6 +2325,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
function_type,
closure_type,
return_type,
fx_type,
early_returns,
recursive,
name,
@ -2334,6 +2343,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
function_type,
closure_type,
return_type,
fx_type,
early_returns,
recursive,
name,
@ -2442,10 +2452,11 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
}
Call(boxed_tuple, args, called_via) => {
let (fn_var, loc_expr, closure_var, expr_var) = *boxed_tuple;
let (fn_var, loc_expr, closure_var, expr_var, fx_var) = *boxed_tuple;
match loc_expr.value {
Var(symbol, _) if symbol.is_builtin() => {
// NOTE: This assumes builtins are not effectful!
match builtin_defs_map(symbol, var_store) {
Some(Def {
loc_expr:
@ -2489,6 +2500,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
expr_var,
pattern_vars,
annotation: None,
kind: DefKind::Let,
};
loc_answer = Loc {
@ -2513,7 +2525,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
_ => {
// For now, we only inline calls to builtins. Leave this alone!
Call(
Box::new((fn_var, loc_expr, closure_var, expr_var)),
Box::new((fn_var, loc_expr, closure_var, expr_var, fx_var)),
args,
called_via,
)
@ -2781,6 +2793,7 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) ->
fn_expr,
var_store.fresh(),
var_store.fresh(),
var_store.fresh(),
)),
vec![
(var_store.fresh(), empty_string),
@ -2817,6 +2830,7 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) ->
fn_expr,
var_store.fresh(),
var_store.fresh(),
var_store.fresh(),
)),
vec![
(var_store.fresh(), loc_new_expr),
@ -2910,6 +2924,7 @@ impl Declarations {
let function_def = FunctionDef {
closure_type: loc_closure_data.value.closure_type,
return_type: loc_closure_data.value.return_type,
fx_type: loc_closure_data.value.fx_type,
early_returns: loc_closure_data.value.early_returns,
captured_symbols: loc_closure_data.value.captured_symbols,
arguments: loc_closure_data.value.arguments,
@ -2962,6 +2977,7 @@ impl Declarations {
let function_def = FunctionDef {
closure_type: loc_closure_data.value.closure_type,
return_type: loc_closure_data.value.return_type,
fx_type: loc_closure_data.value.fx_type,
early_returns: loc_closure_data.value.early_returns,
captured_symbols: loc_closure_data.value.captured_symbols,
arguments: loc_closure_data.value.arguments,
@ -3143,6 +3159,7 @@ impl Declarations {
let function_def = FunctionDef {
closure_type: closure_data.closure_type,
return_type: closure_data.return_type,
fx_type: closure_data.fx_type,
early_returns: closure_data.early_returns,
captured_symbols: closure_data.captured_symbols,
arguments: closure_data.arguments,
@ -3184,6 +3201,7 @@ impl Declarations {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
return_type: var_store.fresh(),
fx_type: var_store.fresh(),
early_returns: vec![],
name: self.symbols[index].value,
captured_symbols: vec![],
@ -3197,6 +3215,7 @@ impl Declarations {
let function_def = FunctionDef {
closure_type: loc_closure_data.value.closure_type,
return_type: loc_closure_data.value.return_type,
fx_type: loc_closure_data.value.fx_type,
early_returns: loc_closure_data.value.early_returns,
captured_symbols: loc_closure_data.value.captured_symbols,
arguments: loc_closure_data.value.arguments,
@ -3332,6 +3351,7 @@ impl DeclarationTag {
pub struct FunctionDef {
pub closure_type: Variable,
pub return_type: Variable,
pub fx_type: Variable,
pub early_returns: Vec<(Variable, Region)>,
pub captured_symbols: Vec<(Symbol, Variable)>,
pub arguments: Vec<(Variable, AnnotatedMark, Loc<Pattern>)>,

View file

@ -14,6 +14,7 @@ pub mod copy;
pub mod def;
mod derive;
pub mod desugar;
pub mod effect_module;
pub mod env;
pub mod exhaustive;
pub mod expected;
@ -25,7 +26,6 @@ pub mod procedure;
pub mod scope;
pub mod string;
pub mod suffixed;
pub mod task_module;
pub mod traverse;
pub use derive::DERIVED_REGION;

View file

@ -2,9 +2,9 @@ use std::path::Path;
use crate::abilities::{AbilitiesStore, ImplKey, PendingAbilitiesStore, ResolvedImpl};
use crate::annotation::{canonicalize_annotation, AnnotationFor};
use crate::def::{canonicalize_defs, report_unused_imports, Def};
use crate::def::{canonicalize_defs, report_unused_imports, Def, DefKind};
use crate::desugar::desugar_record_destructures;
use crate::env::Env;
use crate::env::{Env, FxMode};
use crate::expr::{
ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives,
};
@ -226,6 +226,7 @@ pub fn canonicalize_module_defs<'a>(
symbols_from_requires: &[(Loc<Symbol>, Loc<TypeAnnotation<'a>>)],
var_store: &mut VarStore,
opt_shorthand: Option<&'a str>,
fx_mode: FxMode,
) -> ModuleOutput {
let mut can_exposed_imports = MutMap::default();
@ -247,6 +248,7 @@ pub fn canonicalize_module_defs<'a>(
dep_idents,
qualified_module_ids,
opt_shorthand,
fx_mode,
);
for (name, alias) in aliases.into_iter() {
@ -533,7 +535,7 @@ pub fn canonicalize_module_defs<'a>(
aliases: Default::default(),
};
let hosted_def = crate::task_module::build_host_exposed_def(
let hosted_def = crate::effect_module::build_host_exposed_def(
&mut scope, *symbol, &ident, var_store, annotation,
);
@ -586,7 +588,7 @@ pub fn canonicalize_module_defs<'a>(
aliases: Default::default(),
};
let hosted_def = crate::task_module::build_host_exposed_def(
let hosted_def = crate::effect_module::build_host_exposed_def(
&mut scope, *symbol, &ident, var_store, annotation,
);
@ -655,6 +657,7 @@ pub fn canonicalize_module_defs<'a>(
expr_var: var_store.fresh(),
pattern_vars,
annotation: None,
kind: DefKind::Let,
};
declarations.push_def(def);

View file

@ -683,6 +683,7 @@ pub fn unwrap_suffixed_expression_defs_help<'a>(
Annotation(..) | Dbg{..} | Expect{..} | ExpectFx{..} | Stmt(..) | ModuleImport{..} | IngestedFileImport(_) => None,
AnnotatedBody { body_pattern, body_expr, ann_type, ann_pattern, .. } => Some((body_pattern, body_expr, Some((ann_pattern, ann_type)))),
Body (def_pattern, def_expr) => Some((def_pattern, def_expr, None)),
StmtAfterExpr => None,
};
match maybe_suffixed_value_def {

View file

@ -292,7 +292,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
visitor.visit_expr(&body.value, body.region, var);
}
Expr::Call(f, args, _called_via) => {
let (fn_var, loc_fn, _closure_var, _ret_var) = &**f;
let (fn_var, loc_fn, _closure_var, _ret_var, _fx_var) = &**f;
walk_call(visitor, *fn_var, loc_fn, args);
}
Expr::Crash { msg, .. } => {

View file

@ -62,6 +62,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
&dep_idents,
&qualified_module_ids,
None,
roc_can::env::FxMode::PurityInference,
);
// Desugar operators (convert them to Apply calls, taking into account

View file

@ -24,19 +24,19 @@ Defs {
@0-4 Identifier {
ident: "main",
},
@11-15 Apply(
@11-15 Var {
@11-14 Apply(
@11-14 Var {
module_name: "Task",
ident: "await",
},
[
@11-15 Defs(
@11-14 Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@11-15,
@11-14,
],
space_before: [
Slice { start: 0, length: 0 },
@ -66,19 +66,19 @@ Defs {
body_pattern: @11-15 Identifier {
ident: "#!0_stmt",
},
body_expr: @11-15 Var {
body_expr: @11-14 Var {
module_name: "",
ident: "foo",
},
},
],
},
@11-15 Var {
@11-14 Var {
module_name: "",
ident: "#!0_stmt",
},
),
@11-15 Closure(
@11-14 Closure(
[
@11-15 Underscore(
"#!stmt",

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -51,19 +52,19 @@ Defs {
ident: "msg",
},
],
@31-42 Apply(
@31-42 Var {
@31-43 Apply(
@31-43 Var {
module_name: "Task",
ident: "await",
},
[
@31-42 Defs(
@31-43 Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@31-42,
@31-43,
],
space_before: [
Slice { start: 0, length: 0 },
@ -93,8 +94,8 @@ Defs {
body_pattern: @31-43 Identifier {
ident: "#!0_stmt",
},
body_expr: @31-42 Apply(
@31-42 Var {
body_expr: @31-43 Apply(
@31-43 Var {
module_name: "",
ident: "line",
},
@ -111,12 +112,12 @@ Defs {
},
],
},
@31-42 Var {
@31-43 Var {
module_name: "",
ident: "#!0_stmt",
},
),
@31-42 Closure(
@31-43 Closure(
[
@31-43 Underscore(
"#!stmt",

View file

@ -53,6 +53,7 @@ Defs {
[],
),
],
Pure,
@22-30 Apply(
"",
"Task",

View file

@ -62,6 +62,7 @@ Defs {
[],
),
],
Pure,
@34-45 Apply(
"",
"Task",

View file

@ -24,17 +24,17 @@ Defs {
@0-4 Identifier {
ident: "main",
},
@17-24 Apply(
@17-24 Var {
@17-23 Apply(
@17-23 Var {
module_name: "Task",
ident: "await",
},
[
@17-24 Var {
@17-23 Var {
module_name: "",
ident: "getFoo",
},
@17-24 Closure(
@17-23 Closure(
[
@11-14 Identifier {
ident: "foo",

View file

@ -30,28 +30,28 @@ Defs {
ident: "await",
},
[
@15-17 Var {
@15-16 Var {
module_name: "",
ident: "a",
},
@11-17 Closure(
[
@15-17 Identifier {
@15-16 Identifier {
ident: "#!0_arg",
},
],
@11-17 LowLevelDbg(
(
"test.roc:2",
"in",
"i",
),
@15-17 Apply(
@15-17 Var {
@15-16 Apply(
@15-16 Var {
module_name: "Inspect",
ident: "toStr",
},
[
@15-17 Var {
@15-16 Var {
module_name: "",
ident: "#!0_arg",
},

View file

@ -48,7 +48,7 @@ Defs {
"1",
),
],
value: @97-99 Var {
value: @97-98 Var {
module_name: "",
ident: "c",
},

View file

@ -128,7 +128,7 @@ Defs {
"#!stmt",
),
],
@45-54 Var {
@45-53 Var {
module_name: "",
ident: "printBar",
},

View file

@ -70,13 +70,13 @@ Defs {
module_name: "",
ident: "a",
},
@92-94 Var {
@92-93 Var {
module_name: "",
ident: "b",
},
),
],
final_else: @128-130 Var {
final_else: @128-129 Var {
module_name: "",
ident: "c",
},
@ -91,7 +91,7 @@ Defs {
"B",
),
],
value: @156-158 Var {
value: @156-157 Var {
module_name: "",
ident: "d",
},

View file

@ -63,7 +63,7 @@ Defs {
},
],
},
@29-31 Var {
@29-30 Var {
module_name: "",
ident: "x",
},

View file

@ -85,26 +85,26 @@ Defs {
),
],
},
@79-87 Apply(
@79-87 Var {
@79-86 Apply(
@79-86 Var {
module_name: "Task",
ident: "await",
},
[
@79-87 Var {
@79-86 Var {
module_name: "",
ident: "isFalse",
},
@79-87 Closure(
@79-86 Closure(
[
@79-87 Identifier {
@79-86 Identifier {
ident: "#!0_arg",
},
],
@76-189 If {
if_thens: [
(
@79-87 Var {
@79-86 Var {
module_name: "",
ident: "#!0_arg",
},
@ -124,26 +124,26 @@ Defs {
),
),
],
final_else: @125-132 Apply(
@125-132 Var {
final_else: @125-131 Apply(
@125-131 Var {
module_name: "Task",
ident: "await",
},
[
@125-132 Var {
@125-131 Var {
module_name: "",
ident: "isTrue",
},
@125-132 Closure(
@125-131 Closure(
[
@125-132 Identifier {
@125-131 Identifier {
ident: "#!1_arg",
},
],
@76-189 If {
if_thens: [
(
@125-132 Var {
@125-131 Var {
module_name: "",
ident: "#!1_arg",
},

View file

@ -24,19 +24,19 @@ Defs {
@0-4 Identifier {
ident: "main",
},
@11-15 Apply(
@11-15 Var {
@11-14 Apply(
@11-14 Var {
module_name: "Task",
ident: "await",
},
[
@11-15 Defs(
@11-14 Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@11-15,
@11-14,
],
space_before: [
Slice { start: 0, length: 0 },
@ -66,37 +66,37 @@ Defs {
body_pattern: @11-15 Identifier {
ident: "#!2_stmt",
},
body_expr: @11-15 Var {
body_expr: @11-14 Var {
module_name: "",
ident: "foo",
},
},
],
},
@11-15 Var {
@11-14 Var {
module_name: "",
ident: "#!2_stmt",
},
),
@11-15 Closure(
@11-14 Closure(
[
@11-15 Underscore(
"#!stmt",
),
],
@20-24 Apply(
@20-24 Var {
@20-23 Apply(
@20-23 Var {
module_name: "Task",
ident: "await",
},
[
@20-24 Defs(
@20-23 Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@20-24,
@20-23,
],
space_before: [
Slice { start: 0, length: 0 },
@ -126,25 +126,25 @@ Defs {
body_pattern: @20-24 Identifier {
ident: "#!1_stmt",
},
body_expr: @20-24 Var {
body_expr: @20-23 Var {
module_name: "",
ident: "bar",
},
},
],
},
@20-24 Var {
@20-23 Var {
module_name: "",
ident: "#!1_stmt",
},
),
@20-24 Closure(
@20-23 Closure(
[
@20-24 Underscore(
"#!stmt",
),
],
@29-33 Var {
@29-32 Var {
module_name: "",
ident: "baz",
},

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -94,14 +95,14 @@ Defs {
"#!stmt",
),
],
@33-55 Apply(
@33-55 Var {
@33-56 Apply(
@33-56 Var {
module_name: "Task",
ident: "await",
},
[
@33-55 Apply(
@33-55 Var {
@33-56 Apply(
@33-56 Var {
module_name: "Stdout",
ident: "line",
},
@ -116,7 +117,7 @@ Defs {
Pizza,
),
),
@33-55 Closure(
@33-56 Closure(
[
@28-30 RecordDestructure(
[],

View file

@ -45,17 +45,17 @@ Defs {
@11-12 Identifier {
ident: "x",
},
@27-29 Apply(
@27-29 Var {
@27-28 Apply(
@27-28 Var {
module_name: "Task",
ident: "await",
},
[
@27-29 Var {
@27-28 Var {
module_name: "",
ident: "b",
},
@27-29 Closure(
@27-28 Closure(
[
@23-24 Identifier {
ident: "a",

View file

@ -1,6 +1,7 @@
---
source: crates/compiler/can/tests/test_suffixed.rs
expression: snapshot
snapshot_kind: text
---
Defs {
tags: [
@ -24,19 +25,19 @@ Defs {
@0-4 Identifier {
ident: "main",
},
@11-56 Apply(
@11-56 Var {
@11-57 Apply(
@11-57 Var {
module_name: "Task",
ident: "await",
},
[
@11-56 Defs(
@11-57 Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@11-56,
@11-57,
],
space_before: [
Slice { start: 0, length: 0 },
@ -66,8 +67,8 @@ Defs {
body_pattern: @11-57 Identifier {
ident: "#!0_stmt",
},
body_expr: @11-56 Apply(
@11-56 Var {
body_expr: @11-57 Apply(
@11-57 Var {
module_name: "",
ident: "line",
},
@ -101,12 +102,12 @@ Defs {
},
],
},
@11-56 Var {
@11-57 Var {
module_name: "",
ident: "#!0_stmt",
},
),
@11-56 Closure(
@11-57 Closure(
[
@11-57 Underscore(
"#!stmt",

View file

@ -24,17 +24,17 @@ Defs {
@0-4 Identifier {
ident: "main",
},
@20-31 Apply(
@20-31 Var {
@20-30 Apply(
@20-30 Var {
module_name: "Task",
ident: "await",
},
[
@20-31 Var {
@20-30 Var {
module_name: "Stdin",
ident: "line",
},
@20-31 Closure(
@20-30 Closure(
[
@11-17 Identifier {
ident: "result",

View file

@ -30,19 +30,19 @@ Defs {
ident: "x",
},
],
@28-30 Apply(
@28-30 Var {
@28-29 Apply(
@28-29 Var {
module_name: "Task",
ident: "await",
},
[
@14-30 Defs(
@14-29 Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@28-30,
@28-29,
],
space_before: [
Slice { start: 0, length: 0 },
@ -73,19 +73,19 @@ Defs {
body_pattern: @24-25 Identifier {
ident: "#!0_expr",
},
body_expr: @28-30 Var {
body_expr: @28-29 Var {
module_name: "",
ident: "x",
},
},
],
},
@28-30 Var {
@28-29 Var {
module_name: "",
ident: "#!0_expr",
},
),
@28-30 Closure(
@28-29 Closure(
[
@24-25 Identifier {
ident: "r",

View file

@ -24,24 +24,24 @@ Defs {
@0-4 Identifier {
ident: "main",
},
@15-19 Apply(
@15-19 Var {
@15-18 Apply(
@15-18 Var {
module_name: "Task",
ident: "await",
},
[
@15-19 Var {
@15-18 Var {
module_name: "",
ident: "foo",
},
@15-19 Closure(
@15-18 Closure(
[
@11-12 Identifier {
ident: "a",
},
],
@15-19 Apply(
@15-19 Var {
@15-18 Apply(
@15-18 Var {
module_name: "Task",
ident: "await",
},
@ -50,7 +50,7 @@ Defs {
module_name: "",
ident: "bar",
},
@15-19 Closure(
@15-18 Closure(
[
@28-33 Identifier {
ident: "#!0_arg",

View file

@ -30,18 +30,18 @@ Defs {
ident: "await",
},
[
@16-24 Var {
@16-23 Var {
module_name: "",
ident: "getList",
},
@11-120 Closure(
[
@16-24 Identifier {
@16-23 Identifier {
ident: "#!2_arg",
},
],
@11-120 When(
@16-24 Var {
@16-23 Var {
module_name: "",
ident: "#!2_arg",
},

View file

@ -30,18 +30,18 @@ Defs {
ident: "await",
},
[
@16-24 Var {
@16-23 Var {
module_name: "",
ident: "getList",
},
@11-74 Closure(
[
@16-24 Identifier {
@16-23 Identifier {
ident: "#!0_arg",
},
],
@11-74 When(
@16-24 Var {
@16-23 Var {
module_name: "",
ident: "#!0_arg",
},

View file

@ -415,7 +415,7 @@ mod test_can {
let arena = Bump::new();
let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src);
assert_eq!(problems.len(), 2);
assert_eq!(problems.len(), 1);
println!("{problems:#?}");
assert!(problems.iter().any(|problem| matches!(
problem,

View file

@ -6,7 +6,7 @@ mod suffixed_tests {
use bumpalo::Bump;
use insta::assert_snapshot;
use roc_can::desugar::desugar_defs_node_values;
use roc_can::env::Env;
use roc_can::env::{Env, FxMode};
use roc_can::scope::Scope;
use roc_module::symbol::{IdentIds, ModuleIds, PackageModuleIds};
use roc_parse::test_helpers::parse_defs_with;
@ -34,6 +34,7 @@ mod suffixed_tests {
&dep_idents,
&qualified_module_ids,
None,
FxMode::Task,
);
let mut defs = parse_defs_with(arena, indoc!($src)).unwrap();

View file

@ -315,6 +315,7 @@
"type": "object",
"required": [
"arguments",
"fx",
"lambda_type",
"ret",
"type"
@ -326,6 +327,9 @@
"$ref": "#/definitions/Variable"
}
},
"fx": {
"$ref": "#/definitions/Variable"
},
"lambda_type": {
"$ref": "#/definitions/Variable"
},
@ -510,6 +514,20 @@
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"EffectfulFunc"
]
}
}
},
{
"type": "object",
"required": [
@ -528,6 +546,34 @@
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Pure"
]
}
}
},
{
"type": "object",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"enum": [
"Effectful"
]
}
}
},
{
"type": "object",
"required": [

View file

@ -77,6 +77,8 @@ impl AsSchema<Content> for subs::Content {
} => B::Recursive(opt_name.as_schema(subs), structure.as_schema(subs)),
A::LambdaSet(lambda_set) => lambda_set.as_schema(subs),
A::ErasedLambda => B::ErasedLambda(),
A::Pure => B::Pure(),
A::Effectful => B::Effectful(),
A::Structure(flat_type) => flat_type.as_schema(subs),
A::Alias(name, type_vars, real_var, kind) => B::Alias(
name.as_schema(subs),
@ -96,10 +98,11 @@ impl AsSchema<Content> for subs::FlatType {
subs::FlatType::Apply(symbol, variables) => {
Content::Apply(symbol.as_schema(subs), variables.as_schema(subs))
}
subs::FlatType::Func(arguments, closure, ret) => Content::Function(
subs::FlatType::Func(arguments, closure, ret, fx) => Content::Function(
arguments.as_schema(subs),
closure.as_schema(subs),
ret.as_schema(subs),
fx.as_schema(subs),
),
subs::FlatType::Record(fields, ext) => {
Content::Record(fields.as_schema(subs), ext.as_schema(subs))
@ -124,6 +127,7 @@ impl AsSchema<Content> for subs::FlatType {
),
subs::FlatType::EmptyRecord => Content::EmptyRecord(),
subs::FlatType::EmptyTagUnion => Content::EmptyTagUnion(),
subs::FlatType::EffectfulFunc => Content::EffectfulFunc(),
}
}
}

View file

@ -73,6 +73,7 @@ impl_content! {
arguments: Vec<Variable>,
lambda_type: Variable,
ret: Variable,
fx: Variable,
},
Record {
fields: HashMap<String, RecordField>,
@ -98,9 +99,12 @@ impl_content! {
},
EmptyRecord {},
EmptyTagUnion {},
EffectfulFunc {},
RangedNumber {
range: NumericRange,
},
Pure {},
Effectful {},
Error {},
}

View file

@ -8,9 +8,10 @@ use crate::builtins::{
use crate::pattern::{constrain_pattern, PatternState};
use roc_can::annotation::IntroducedVariables;
use roc_can::constraint::{
Constraint, Constraints, ExpectedTypeIndex, Generalizable, OpportunisticResolve, TypeOrVar,
Constraint, Constraints, ExpectEffectfulReason, ExpectedTypeIndex, FxCallKind, FxExpectation,
Generalizable, OpportunisticResolve, TypeOrVar,
};
use roc_can::def::Def;
use roc_can::def::{Def, DefKind};
use roc_can::exhaustive::{sketch_pattern_to_rows, sketch_when_branches, ExhaustiveContext};
use roc_can::expected::Expected::{self, *};
use roc_can::expected::PExpected;
@ -58,6 +59,30 @@ pub struct Env {
pub rigids: MutMap<Lowercase, Variable>,
pub resolutions_to_make: Vec<OpportunisticResolve>,
pub home: ModuleId,
/// The enclosing function's fx var to be unified with inner calls
pub fx_expectation: Option<FxExpectation>,
}
impl Env {
pub fn with_fx_expectation<F, T>(
&mut self,
fx_var: Variable,
ann_region: Option<Region>,
f: F,
) -> T
where
F: FnOnce(&mut Env) -> T,
{
let prev = self.fx_expectation.take();
self.fx_expectation = Some(FxExpectation { fx_var, ann_region });
let result = f(self);
self.fx_expectation = prev;
result
}
}
fn constrain_untyped_args(
@ -67,6 +92,7 @@ fn constrain_untyped_args(
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
closure_type: Type,
return_type: Type,
fx_type: Type,
) -> (Vec<Variable>, PatternState, Type) {
let mut vars = Vec::with_capacity(arguments.len());
let mut pattern_types = Vec::with_capacity(arguments.len());
@ -97,8 +123,12 @@ fn constrain_untyped_args(
vars.push(*pattern_var);
}
let function_type =
Type::Function(pattern_types, Box::new(closure_type), Box::new(return_type));
let function_type = Type::Function(
pattern_types,
Box::new(closure_type),
Box::new(return_type),
Box::new(fx_type),
);
(vars, pattern_state, function_type)
}
@ -109,10 +139,10 @@ fn constrain_untyped_closure(
env: &mut Env,
region: Region,
expected: ExpectedTypeIndex,
fn_var: Variable,
closure_var: Variable,
ret_var: Variable,
fx_var: Variable,
early_returns: &[(Variable, Region)],
arguments: &[(Variable, AnnotatedMark, Loc<Pattern>)],
loc_body_expr: &Loc<Expr>,
@ -122,6 +152,7 @@ fn constrain_untyped_closure(
let closure_type = Type::Variable(closure_var);
let return_type = Type::Variable(ret_var);
let return_type_index = constraints.push_variable(ret_var);
let fx_type = Type::Variable(fx_var);
let (mut vars, pattern_state, function_type) = constrain_untyped_args(
types,
constraints,
@ -129,9 +160,11 @@ fn constrain_untyped_closure(
arguments,
closure_type,
return_type,
fx_type,
);
vars.push(ret_var);
vars.push(fx_var);
vars.push(closure_var);
vars.push(fn_var);
@ -141,14 +174,16 @@ fn constrain_untyped_closure(
loc_body_expr.region,
));
let ret_constraint = constrain_expr(
types,
constraints,
env,
loc_body_expr.region,
&loc_body_expr.value,
body_type,
);
let ret_constraint = env.with_fx_expectation(fx_var, None, |env| {
constrain_expr(
types,
constraints,
env,
loc_body_expr.region,
&loc_body_expr.value,
body_type,
)
});
let mut early_return_constraints = Vec::with_capacity(early_returns.len());
for (early_return_variable, early_return_region) in early_returns {
@ -199,6 +234,7 @@ fn constrain_untyped_closure(
ret_constraint,
Generalizable(true),
),
constraints.and_constraint(pattern_state.delayed_fx_suffix_constraints),
constraints.equal_types_with_storage(
function_type,
expected,
@ -208,6 +244,7 @@ fn constrain_untyped_closure(
),
early_returns_constraint,
closure_constraint,
constraints.flex_to_pure(fx_var),
];
constraints.exists_many(vars, cons)
@ -247,6 +284,10 @@ pub fn constrain_expr(
let (field_type, field_con) =
constrain_field(types, constraints, env, field_var, loc_field_expr);
let check_field_con =
constraints.fx_record_field_suffix(label.suffix(), field_var, field.region);
let field_con = constraints.and_constraint([field_con, check_field_con]);
field_vars.push(field_var);
field_types.insert(label.clone(), RecordField::Required(field_type));
@ -472,7 +513,7 @@ pub fn constrain_expr(
}
}
Call(boxed, loc_args, called_via) => {
let (fn_var, loc_fn, closure_var, ret_var) = &**boxed;
let (fn_var, loc_fn, closure_var, ret_var, fx_var) = &**boxed;
// The expression that evaluates to the function being called, e.g. `foo` in
// (foo) bar baz
let opt_symbol = if let Var(symbol, _) | AbilityMember(symbol, _, _) = loc_fn.value {
@ -503,6 +544,9 @@ pub fn constrain_expr(
// The function's return type
let ret_type = Variable(*ret_var);
// The function's effect type
let fx_type = Variable(*fx_var);
// type of values captured in the closure
let closure_type = Variable(*closure_var);
@ -512,6 +556,7 @@ pub fn constrain_expr(
vars.push(*fn_var);
vars.push(*ret_var);
vars.push(*closure_var);
vars.push(*fx_var);
let mut arg_types = Vec::with_capacity(loc_args.len());
let mut arg_cons = Vec::with_capacity(loc_args.len());
@ -546,7 +591,8 @@ pub fn constrain_expr(
let arguments = types.from_old_type_slice(arg_types.iter());
let lambda_set = types.from_old_type(&closure_type);
let ret = types.from_old_type(&ret_type);
let typ = types.function(arguments, lambda_set, ret);
let fx = types.from_old_type(&fx_type);
let typ = types.function(arguments, lambda_set, ret, fx);
constraints.push_type(types, typ)
};
let expected_fn_type =
@ -560,7 +606,18 @@ pub fn constrain_expr(
fn_con,
constraints.equal_types_var(*fn_var, expected_fn_type, category.clone(), fn_region),
constraints.and_constraint(arg_cons),
constraints.equal_types_var(*ret_var, expected_final_type, category, region),
constraints.equal_types_var(
*ret_var,
expected_final_type,
category.clone(),
region,
),
constraints.fx_call(
*fx_var,
FxCallKind::Call(opt_symbol),
region,
env.fx_expectation,
),
];
let and_constraint = constraints.and_constraint(and_cons);
@ -646,6 +703,7 @@ pub fn constrain_expr(
function_type: fn_var,
closure_type: closure_var,
return_type: ret_var,
fx_type: fx_var,
early_returns,
arguments,
loc_body: boxed,
@ -663,6 +721,7 @@ pub fn constrain_expr(
*fn_var,
*closure_var,
*ret_var,
*fx_var,
early_returns,
arguments,
boxed,
@ -1288,6 +1347,7 @@ pub fn constrain_expr(
vec![record_type],
Box::new(closure_type),
Box::new(field_type),
Box::new(Type::Variable(Variable::PURE)),
));
constraints.push_type(types, typ)
};
@ -1397,7 +1457,15 @@ pub fn constrain_expr(
);
while let Some(def) = stack.pop() {
body_con = constrain_def(types, constraints, env, def, body_con)
body_con = match def.kind {
DefKind::Let => constrain_let_def(types, constraints, env, def, body_con, None),
DefKind::Stmt(fx_var) => {
constrain_stmt_def(types, constraints, env, def, body_con, fx_var)
}
DefKind::Ignored(fx_var) => {
constrain_let_def(types, constraints, env, def, body_con, Some(fx_var))
}
};
}
body_con
@ -1668,6 +1736,7 @@ pub fn constrain_expr(
vec![argument_type],
Box::new(closure_type),
Box::new(opaque_type),
Box::new(Type::Variable(Variable::PURE)),
));
constraints.push_type(types, typ)
};
@ -1853,11 +1922,12 @@ fn constrain_function_def(
let signature_index = constraints.push_type(types, signature);
let (arg_types, _signature_closure_type, ret_type) = match types[signature] {
TypeTag::Function(signature_closure_type, ret_type) => (
let (arg_types, _signature_closure_type, ret_type, fx_type) = match types[signature] {
TypeTag::Function(signature_closure_type, ret_type, fx_type) => (
types.get_type_arguments(signature),
signature_closure_type,
ret_type,
fx_type,
),
_ => {
// aliases, or just something weird
@ -1917,6 +1987,7 @@ fn constrain_function_def(
expr_var,
function_def.closure_type,
function_def.return_type,
function_def.fx_type,
&function_def.early_returns,
&function_def.arguments,
loc_body_expr,
@ -1949,6 +2020,10 @@ fn constrain_function_def(
home: env.home,
rigids: ftv,
resolutions_to_make: vec![],
fx_expectation: Some(FxExpectation {
fx_var: function_def.fx_type,
ann_region: Some(annotation.region),
}),
};
let region = loc_function_def.region;
@ -1958,14 +2033,17 @@ fn constrain_function_def(
vars: Vec::with_capacity(function_def.arguments.len()),
constraints: Vec::with_capacity(1),
delayed_is_open_constraints: vec![],
delayed_fx_suffix_constraints: Vec::with_capacity(function_def.arguments.len()),
};
let mut vars = Vec::with_capacity(argument_pattern_state.vars.capacity() + 1);
let closure_var = function_def.closure_type;
let ret_type_index = constraints.push_type(types, ret_type);
let fx_type_index = constraints.push_type(types, fx_type);
vars.push(function_def.return_type);
vars.push(function_def.closure_type);
vars.push(function_def.fx_type);
let mut def_pattern_state = PatternState::default();
@ -2043,8 +2121,9 @@ fn constrain_function_def(
);
let lambda_set = types.from_old_type(&Type::Variable(function_def.closure_type));
let ret_var = types.from_old_type(&Type::Variable(function_def.return_type));
let fx_var = types.from_old_type(&Type::Variable(function_def.fx_type));
let fn_type = types.function(pattern_types, lambda_set, ret_var);
let fn_type = types.function(pattern_types, lambda_set, ret_var, fx_var);
constraints.push_type(types, fn_type)
};
@ -2065,6 +2144,13 @@ fn constrain_function_def(
let defs_constraint = constraints.and_constraint(argument_pattern_state.constraints);
let cons = [
// Store fx type first so errors are reported at call site
constraints.store(
fx_type_index,
function_def.fx_type,
std::file!(),
std::line!(),
),
constraints.let_constraint(
[],
argument_pattern_state.vars,
@ -2090,9 +2176,12 @@ fn constrain_function_def(
Category::Lambda,
region,
),
// Check argument suffixes against usage
constraints.and_constraint(argument_pattern_state.delayed_fx_suffix_constraints),
// Finally put the solved closure type into the dedicated def expr variable.
constraints.store(signature_index, expr_var, std::file!(), std::line!()),
closure_constraint,
constraints.flex_to_pure(function_def.fx_type),
];
let expr_con = constraints.exists_many(vars, cons);
@ -2119,6 +2208,7 @@ fn constrain_function_def(
expr_var,
function_def.closure_type,
function_def.return_type,
function_def.fx_type,
&function_def.early_returns,
&function_def.arguments,
loc_expr,
@ -2190,6 +2280,7 @@ fn constrain_destructure_def(
home: env.home,
rigids: ftv,
resolutions_to_make: vec![],
fx_expectation: env.fx_expectation,
};
let signature_index = constraints.push_type(types, signature);
@ -2292,6 +2383,7 @@ fn constrain_value_def(
home: env.home,
rigids: ftv,
resolutions_to_make: vec![],
fx_expectation: env.fx_expectation,
};
let loc_pattern = Loc::at(loc_symbol.region, Pattern::Identifier(loc_symbol.value));
@ -2404,6 +2496,7 @@ fn constrain_when_branch_help(
vars: Vec::with_capacity(2),
constraints: Vec::with_capacity(2),
delayed_is_open_constraints: Vec::new(),
delayed_fx_suffix_constraints: Vec::new(),
};
for (i, loc_pattern) in when_branch.patterns.iter().enumerate() {
@ -2428,6 +2521,9 @@ fn constrain_when_branch_help(
state
.delayed_is_open_constraints
.extend(partial_state.delayed_is_open_constraints);
state
.delayed_fx_suffix_constraints
.extend(partial_state.delayed_fx_suffix_constraints);
if i == 0 {
state.headers.extend(partial_state.headers);
@ -2579,6 +2675,7 @@ pub fn constrain_decls(
home,
rigids: MutMap::default(),
resolutions_to_make: vec![],
fx_expectation: None,
};
debug_assert_eq!(declarations.declarations.len(), declarations.symbols.len());
@ -2755,6 +2852,7 @@ pub(crate) fn constrain_def_pattern(
vars: Vec::with_capacity(1),
constraints: Vec::with_capacity(1),
delayed_is_open_constraints: vec![],
delayed_fx_suffix_constraints: vec![],
};
constrain_pattern(
@ -2810,6 +2908,7 @@ fn constrain_typed_def(
home: env.home,
resolutions_to_make: vec![],
rigids: ftv,
fx_expectation: env.fx_expectation,
};
let signature_index = constraints.push_type(types, signature);
@ -2841,13 +2940,14 @@ fn constrain_typed_def(
function_type: fn_var,
closure_type: closure_var,
return_type: ret_var,
fx_type: fx_var,
captured_symbols,
arguments,
loc_body,
name,
..
}),
TypeTag::Function(_signature_closure_type, ret_type),
TypeTag::Function(_signature_closure_type, ret_type, fx_type),
) => {
let arg_types = types.get_type_arguments(signature);
@ -2862,14 +2962,18 @@ fn constrain_typed_def(
vars: Vec::with_capacity(arguments.len()),
constraints: Vec::with_capacity(1),
delayed_is_open_constraints: vec![],
delayed_fx_suffix_constraints: Vec::with_capacity(arguments.len()),
};
let mut vars = Vec::with_capacity(argument_pattern_state.vars.capacity() + 1);
let ret_var = *ret_var;
let closure_var = *closure_var;
let fx_var = *fx_var;
let ret_type_index = constraints.push_type(types, ret_type);
let fx_type_index = constraints.push_type(types, fx_type);
vars.push(ret_var);
vars.push(closure_var);
vars.push(fx_var);
constrain_typed_function_arguments(
types,
@ -2899,8 +3003,9 @@ fn constrain_typed_def(
types.from_old_type_slice(arguments.iter().map(|a| Type::Variable(a.0)));
let lambda_set = types.from_old_type(&Type::Variable(closure_var));
let ret_var = types.from_old_type(&Type::Variable(ret_var));
let fx_var = types.from_old_type(&Type::Variable(fx_var));
let fn_type = types.function(arg_types, lambda_set, ret_var);
let fn_type = types.function(arg_types, lambda_set, ret_var, fx_var);
constraints.push_type(types, fn_type)
};
@ -2913,20 +3018,25 @@ fn constrain_typed_def(
ret_type_index,
));
let ret_constraint = constrain_expr(
types,
constraints,
env,
loc_body_expr.region,
&loc_body_expr.value,
body_type,
);
let ret_constraint = env.with_fx_expectation(fx_var, Some(annotation.region), |env| {
constrain_expr(
types,
constraints,
env,
loc_body_expr.region,
&loc_body_expr.value,
body_type,
)
});
let ret_constraint = attach_resolution_constraints(constraints, env, ret_constraint);
vars.push(*fn_var);
let defs_constraint = constraints.and_constraint(argument_pattern_state.constraints);
let cons = [
// Store fx type first so errors are reported at call site
constraints.store(fx_type_index, fx_var, std::file!(), std::line!()),
constraints.let_constraint(
[],
argument_pattern_state.vars,
@ -2936,6 +3046,8 @@ fn constrain_typed_def(
// This is a syntactic function, it can be generalized
Generalizable(true),
),
// Check argument suffixes against usage
constraints.and_constraint(argument_pattern_state.delayed_fx_suffix_constraints),
// Store the inferred ret var into the function type now, so that
// when we check that the solved function type matches the annotation, we can
// display the fully inferred return variable.
@ -2951,6 +3063,7 @@ fn constrain_typed_def(
constraints.store(signature_index, *fn_var, std::file!(), std::line!()),
constraints.store(signature_index, expr_var, std::file!(), std::line!()),
closure_constraint,
constraints.flex_to_pure(fx_var),
];
let expr_con = constraints.exists_many(vars, cons);
@ -3318,12 +3431,13 @@ fn attach_resolution_constraints(
constraints.and_constraint([constraint, resolution_constrs])
}
fn constrain_def(
fn constrain_let_def(
types: &mut Types,
constraints: &mut Constraints,
env: &mut Env,
def: &Def,
body_con: Constraint,
ignored_fx_var: Option<Variable>,
) -> Constraint {
match &def.annotation {
Some(annotation) => constrain_typed_def(types, constraints, env, def, body_con, annotation),
@ -3338,14 +3452,49 @@ fn constrain_def(
// no annotation, so no extra work with rigids
let expected = constraints.push_expected_type(NoExpectation(expr_type_index));
let expr_con = constrain_expr(
types,
constraints,
env,
def.loc_expr.region,
&def.loc_expr.value,
expected,
);
let expr_con = match ignored_fx_var {
None => constrain_expr(
types,
constraints,
env,
def.loc_expr.region,
&def.loc_expr.value,
expected,
),
Some(fx_var) => {
let expr_con = env.with_fx_expectation(fx_var, None, |env| {
constrain_expr(
types,
constraints,
env,
def.loc_expr.region,
&def.loc_expr.value,
expected,
)
});
// Ignored def must be effectful, otherwise it's dead code
let effectful_constraint = Constraint::ExpectEffectful(
fx_var,
ExpectEffectfulReason::Ignored,
def.loc_pattern.region,
);
let enclosing_fx_constraint = constraints.fx_call(
fx_var,
FxCallKind::Ignored,
def.loc_pattern.region,
env.fx_expectation,
);
constraints.and_constraint([
expr_con,
enclosing_fx_constraint,
effectful_constraint,
])
}
};
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
let generalizable = Generalizable(is_generalizable_expr(&def.loc_expr.value));
@ -3363,6 +3512,77 @@ fn constrain_def(
}
}
fn constrain_stmt_def(
types: &mut Types,
constraints: &mut Constraints,
env: &mut Env,
def: &Def,
body_con: Constraint,
fx_var: Variable,
) -> Constraint {
let region = def.loc_expr.region;
// Try to extract the fn name and region if the stmt is a call to a named function
let (fn_name, error_region) = if let Expr::Call(boxed, _, _) = &def.loc_expr.value {
let loc_fn_expr = &boxed.1;
match loc_fn_expr.value {
Var(symbol, _) | ParamsVar { symbol, .. } => (Some(symbol), loc_fn_expr.region),
_ => (None, def.loc_expr.region),
}
} else {
(None, def.loc_expr.region)
};
// Statement expressions must return an empty record
let empty_record_index = constraints.push_type(types, Types::EMPTY_RECORD);
let expect_empty_record = constraints.push_expected_type(ForReason(
Reason::Stmt(fn_name),
empty_record_index,
error_region,
));
let expr_con = env.with_fx_expectation(fx_var, None, |env| {
constrain_expr(
types,
constraints,
env,
region,
&def.loc_expr.value,
expect_empty_record,
)
});
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
let generalizable = Generalizable(is_generalizable_expr(&def.loc_expr.value));
let body_con = constraints.let_constraint(
std::iter::empty(),
std::iter::empty(),
std::iter::empty(),
expr_con,
body_con,
generalizable,
);
// Stmt expr must be effectful, otherwise it's dead code
let effectful_constraint =
Constraint::ExpectEffectful(fx_var, ExpectEffectfulReason::Stmt, region);
let fx_call_kind = match fn_name {
None => FxCallKind::Stmt,
Some(name) => FxCallKind::Call(Some(name)),
};
// We have to unify the stmt fx with the enclosing fx
// since we used the former to constrain the expr.
let enclosing_fx_constraint =
constraints.fx_call(fx_var, fx_call_kind, error_region, env.fx_expectation);
constraints.and_constraint([body_con, effectful_constraint, enclosing_fx_constraint])
}
/// Create a let-constraint for a non-recursive def.
/// Recursive defs should always use `constrain_recursive_defs`.
pub(crate) fn constrain_def_make_constraint(
@ -3700,6 +3920,7 @@ fn constraint_recursive_function(
expr_var,
function_def.closure_type,
function_def.return_type,
function_def.fx_type,
&function_def.early_returns,
&function_def.arguments,
loc_expr,
@ -3747,11 +3968,12 @@ fn constraint_recursive_function(
signature_index,
));
let (arg_types, _signature_closure_type, ret_type) = match types[signature] {
TypeTag::Function(signature_closure_type, ret_type) => (
let (arg_types, _signature_closure_type, ret_type, fx_type) = match types[signature] {
TypeTag::Function(signature_closure_type, ret_type, fx_type) => (
types.get_type_arguments(signature),
signature_closure_type,
ret_type,
fx_type,
),
_ => todo!("TODO {:?}", (loc_symbol, types[signature])),
};
@ -3764,14 +3986,18 @@ fn constraint_recursive_function(
vars: Vec::with_capacity(function_def.arguments.len()),
constraints: Vec::with_capacity(1),
delayed_is_open_constraints: vec![],
delayed_fx_suffix_constraints: Vec::with_capacity(function_def.arguments.len()),
};
let mut vars = Vec::with_capacity(argument_pattern_state.vars.capacity() + 1);
let ret_var = function_def.return_type;
let fx_var = function_def.fx_type;
let closure_var = function_def.closure_type;
let ret_type_index = constraints.push_type(types, ret_type);
let fx_type_index = constraints.push_type(types, fx_type);
vars.push(ret_var);
vars.push(closure_var);
vars.push(fx_var);
let mut def_pattern_state = PatternState::default();
@ -3819,11 +4045,11 @@ fn constraint_recursive_function(
let fn_type = {
// TODO(types-soa) optimize for Variable
let lambda_set = types.from_old_type(&Type::Variable(closure_var));
let typ = types.function(pattern_types, lambda_set, ret_type);
let typ = types.function(pattern_types, lambda_set, ret_type, fx_type);
constraints.push_type(types, typ)
};
let expr_con = {
let expr_con = env.with_fx_expectation(fx_var, Some(annotation.region), |env| {
let expected = constraints.push_expected_type(NoExpectation(ret_type_index));
constrain_expr(
types,
@ -3833,13 +4059,14 @@ fn constraint_recursive_function(
&loc_body_expr.value,
expected,
)
};
});
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
vars.push(expr_var);
let state_constraints = constraints.and_constraint(argument_pattern_state.constraints);
let cons = [
constraints.store(fx_type_index, fx_var, std::file!(), std::line!()),
constraints.let_constraint(
[],
argument_pattern_state.vars,
@ -3849,12 +4076,15 @@ fn constraint_recursive_function(
// Syntactic function can be generalized
Generalizable(true),
),
// Check argument suffixes against usage
constraints.and_constraint(argument_pattern_state.delayed_fx_suffix_constraints),
constraints.equal_types(fn_type, annotation_expected, Category::Lambda, region),
// "fn_var is equal to the closure's type" - fn_var is used in code gen
// Store type into AST vars. We use Store so errors aren't reported twice
constraints.store(signature_index, expr_var, std::file!(), std::line!()),
constraints.store(ret_type_index, ret_var, std::file!(), std::line!()),
closure_constraint,
constraints.flex_to_pure(fx_var),
];
let and_constraint = constraints.and_constraint(cons);
@ -4305,13 +4535,14 @@ fn rec_defs_help(
function_type: fn_var,
closure_type: closure_var,
return_type: ret_var,
fx_type: fx_var,
captured_symbols,
arguments,
loc_body,
name,
..
}),
TypeTag::Function(_closure_type, ret_type),
TypeTag::Function(_closure_type, ret_type, fx_type),
) => {
// NOTE if we ever have trouble with closure type unification, the ignored
// `_closure_type` here is a good place to start investigating
@ -4327,15 +4558,19 @@ fn rec_defs_help(
vars: Vec::with_capacity(arguments.len()),
constraints: Vec::with_capacity(1),
delayed_is_open_constraints: vec![],
delayed_fx_suffix_constraints: Vec::with_capacity(arguments.len()),
};
let mut vars =
Vec::with_capacity(argument_pattern_state.vars.capacity() + 1);
let ret_var = *ret_var;
let fx_var = *fx_var;
let closure_var = *closure_var;
let ret_type_index = constraints.push_type(types, ret_type);
let fx_type_index = constraints.push_type(types, fx_type);
vars.push(ret_var);
vars.push(closure_var);
vars.push(fx_var);
constrain_typed_function_arguments(
types,
@ -4364,22 +4599,24 @@ fn rec_defs_help(
let fn_type_index = {
// TODO(types-soa) optimize for variable
let lambda_set = types.from_old_type(&Type::Variable(closure_var));
let typ = types.function(pattern_types, lambda_set, ret_type);
let typ = types.function(pattern_types, lambda_set, ret_type, fx_type);
constraints.push_type(types, typ)
};
let expr_con = {
let body_type =
constraints.push_expected_type(NoExpectation(ret_type_index));
let expr_con =
env.with_fx_expectation(fx_var, Some(annotation.region), |env| {
let body_type =
constraints.push_expected_type(NoExpectation(ret_type_index));
constrain_expr(
types,
constraints,
env,
loc_body_expr.region,
&loc_body_expr.value,
body_type,
)
});
constrain_expr(
types,
constraints,
env,
loc_body_expr.region,
&loc_body_expr.value,
body_type,
)
};
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
vars.push(*fn_var);
@ -4388,6 +4625,8 @@ fn rec_defs_help(
constraints.and_constraint(argument_pattern_state.constraints);
let expected_index = constraints.push_expected_type(expected);
let cons = [
// Store fx type first so errors are reported at call site
constraints.store(fx_type_index, fx_var, std::file!(), std::line!()),
constraints.let_constraint(
[],
argument_pattern_state.vars,
@ -4396,6 +4635,10 @@ fn rec_defs_help(
expr_con,
generalizable,
),
// Check argument suffixes against usage
constraints.and_constraint(
argument_pattern_state.delayed_fx_suffix_constraints,
),
constraints.equal_types(
fn_type_index,
expected_index,
@ -4413,6 +4656,7 @@ fn rec_defs_help(
),
constraints.store(ret_type_index, ret_var, std::file!(), std::line!()),
closure_constraint,
constraints.flex_to_pure(fx_var),
];
let and_constraint = constraints.and_constraint(cons);

View file

@ -56,6 +56,7 @@ fn constrain_params(
home,
rigids: MutMap::default(),
resolutions_to_make: vec![],
fx_expectation: None,
};
let index = constraints.push_variable(module_params.whole_var);
@ -114,6 +115,7 @@ fn constrain_symbols_from_requires(
home,
rigids,
resolutions_to_make: vec![],
fx_expectation: None,
};
let pattern = Loc::at_zero(roc_can::pattern::Pattern::Identifier(loc_symbol.value));
@ -181,6 +183,7 @@ pub fn frontload_ability_constraints(
home,
rigids,
resolutions_to_make: vec![],
fx_expectation: None,
};
let pattern = Loc::at_zero(roc_can::pattern::Pattern::Identifier(*member_name));

View file

@ -6,7 +6,7 @@ use roc_can::pattern::Pattern::{self, *};
use roc_can::pattern::{DestructType, ListPatterns, RecordDestruct, TupleDestruct};
use roc_collections::all::{HumanIndex, SendMap};
use roc_collections::VecMap;
use roc_module::ident::Lowercase;
use roc_module::ident::{IdentSuffix, Lowercase};
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
@ -22,6 +22,7 @@ pub struct PatternState {
pub vars: Vec<Variable>,
pub constraints: Vec<Constraint>,
pub delayed_is_open_constraints: Vec<Constraint>,
pub delayed_fx_suffix_constraints: Vec<Constraint>,
}
/// If there is a type annotation, the pattern state headers can be optimized by putting the
@ -247,6 +248,29 @@ pub fn constrain_pattern(
region: Region,
expected: PExpectedTypeIndex,
state: &mut PatternState,
) {
constrain_pattern_help(
types,
constraints,
env,
pattern,
region,
expected,
state,
true,
);
}
#[allow(clippy::too_many_arguments)]
pub fn constrain_pattern_help(
types: &mut Types,
constraints: &mut Constraints,
env: &mut Env,
pattern: &Pattern,
region: Region,
expected: PExpectedTypeIndex,
state: &mut PatternState,
is_shallow: bool,
) {
match pattern {
Underscore => {
@ -276,6 +300,27 @@ pub fn constrain_pattern(
.push(constraints.is_open_type(type_index));
}
// Identifiers introduced in nested patterns get let constraints
// and therefore don't need fx_pattern_suffix constraints.
if is_shallow {
match symbol.suffix() {
IdentSuffix::None => {
// Unsuffixed identifiers should be constrained after we know if they're functions
state
.delayed_fx_suffix_constraints
.push(constraints.fx_pattern_suffix(*symbol, type_index, region));
}
IdentSuffix::Bang => {
// Bang suffixed identifiers are always required to be functions
// We constrain this before the function's body,
// so that we don't think it's pure and complain about leftover statements
state
.constraints
.push(constraints.fx_pattern_suffix(*symbol, type_index, region));
}
}
}
state.headers.insert(
*symbol,
Loc {
@ -297,7 +342,7 @@ pub fn constrain_pattern(
},
);
constrain_pattern(
constrain_pattern_help(
types,
constraints,
env,
@ -305,6 +350,7 @@ pub fn constrain_pattern(
subpattern.region,
expected,
state,
false,
)
}
@ -530,7 +576,7 @@ pub fn constrain_pattern(
));
state.vars.push(*guard_var);
constrain_pattern(
constrain_pattern_help(
types,
constraints,
env,
@ -538,6 +584,7 @@ pub fn constrain_pattern(
loc_pattern.region,
expected,
state,
false,
);
pat_type
@ -628,7 +675,7 @@ pub fn constrain_pattern(
));
state.vars.push(*guard_var);
constrain_pattern(
constrain_pattern_help(
types,
constraints,
env,
@ -636,6 +683,7 @@ pub fn constrain_pattern(
loc_guard.region,
expected,
state,
false,
);
RecordField::Demanded(pat_type)
@ -751,7 +799,7 @@ pub fn constrain_pattern(
loc_pat.region,
));
constrain_pattern(
constrain_pattern_help(
types,
constraints,
env,
@ -759,6 +807,7 @@ pub fn constrain_pattern(
loc_pat.region,
expected,
state,
false,
);
}
@ -807,7 +856,7 @@ pub fn constrain_pattern(
pattern_type,
region,
));
constrain_pattern(
constrain_pattern_help(
types,
constraints,
env,
@ -815,6 +864,7 @@ pub fn constrain_pattern(
loc_pattern.region,
expected,
state,
false,
);
}
@ -872,7 +922,7 @@ pub fn constrain_pattern(
// First, add a constraint for the argument "who"
let arg_pattern_expected = constraints
.push_pat_expected_type(PExpected::NoExpectation(arg_pattern_type_index));
constrain_pattern(
constrain_pattern_help(
types,
constraints,
env,
@ -880,6 +930,7 @@ pub fn constrain_pattern(
loc_arg_pattern.region,
arg_pattern_expected,
state,
false,
);
// Next, link `whole_var` to the opaque type of "@Id who"

View file

@ -78,6 +78,7 @@ fn wrap_in_decode_custom_decode_with(
this_decode_with_var_slice,
this_decode_with_clos_var,
this_decode_with_ret_var,
Variable::PURE,
)),
);
@ -91,6 +92,7 @@ fn wrap_in_decode_custom_decode_with(
Loc::at_zero(decode_with_var),
this_decode_with_clos_var,
this_decode_with_ret_var,
Variable::PURE,
));
let decode_with_call = Call(
decode_with_fn,
@ -139,6 +141,7 @@ fn wrap_in_decode_custom_decode_with(
args_slice,
fn_clos_var,
decode_with_result_var,
Variable::PURE,
)),
);
@ -147,6 +150,7 @@ fn wrap_in_decode_custom_decode_with(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: decode_with_result_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: sorted_inner_decoder_captures,
@ -184,6 +188,7 @@ fn wrap_in_decode_custom_decode_with(
this_decode_custom_args,
this_decode_custom_clos_var,
this_decode_custom_ret_var,
Variable::PURE,
)),
);
@ -197,6 +202,7 @@ fn wrap_in_decode_custom_decode_with(
Loc::at_zero(decode_custom_var),
this_decode_custom_clos_var,
this_decode_custom_ret_var,
Variable::PURE,
));
let decode_custom_call = Call(
decode_custom_fn,

View file

@ -65,6 +65,7 @@ pub(crate) fn decoder(env: &mut Env<'_>, _def_symbol: Symbol) -> (Expr, Variable
elem_decoder_var_slice,
this_decode_list_clos_var,
this_decode_list_ret_var,
Variable::PURE,
)),
);
@ -78,6 +79,7 @@ pub(crate) fn decoder(env: &mut Env<'_>, _def_symbol: Symbol) -> (Expr, Variable
Loc::at_zero(decode_list_member),
this_decode_list_clos_var,
this_decode_list_ret_var,
Variable::PURE,
));
let decode_list_call = Call(

View file

@ -112,6 +112,7 @@ pub(crate) fn decoder(
.insert_into_vars([initial_state_var, step_var, finalizer_var]),
decode_record_lambda_set,
record_decoder_var,
Variable::PURE,
);
synth_var(env.subs, Content::Structure(flat_type))
@ -130,6 +131,7 @@ pub(crate) fn decoder(
)),
decode_record_lambda_set,
record_decoder_var,
Variable::PURE,
)),
vec![
(initial_state_var, Loc::at_zero(initial_state)),
@ -342,7 +344,12 @@ pub(super) fn step_field(
env.subs.set_content(
function_type,
Content::Structure(FlatType::Func(args_slice, closure_type, keep_or_skip_var)),
Content::Structure(FlatType::Func(
args_slice,
closure_type,
keep_or_skip_var,
Variable::PURE,
)),
)
};
@ -350,6 +357,7 @@ pub(super) fn step_field(
function_type,
closure_type,
return_type: keep_or_skip_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: step_field_closure,
captured_symbols: Vec::new(),
@ -406,8 +414,12 @@ fn custom_decoder(env: &mut Env<'_>, args: DecodingFieldArgs) -> (Variable, Expr
let decode_custom_closure_var = env.subs.fresh_unnamed_flex_var();
let this_decode_custom_var = {
let subs_slice = env.subs.insert_into_vars([this_custom_callback_var]);
let flat_type =
FlatType::Func(subs_slice, decode_custom_closure_var, decode_custom_ret_var);
let flat_type = FlatType::Func(
subs_slice,
decode_custom_closure_var,
decode_custom_ret_var,
Variable::PURE,
);
synth_var(env.subs, Content::Structure(flat_type))
};
@ -421,6 +433,7 @@ fn custom_decoder(env: &mut Env<'_>, args: DecodingFieldArgs) -> (Variable, Expr
Loc::at_zero(Expr::Var(Symbol::DECODE_CUSTOM, this_decode_custom_var)),
decode_custom_closure_var,
decode_custom_ret_var,
Variable::PURE,
)),
vec![(this_custom_callback_var, Loc::at_zero(custom_callback))],
CalledVia::Space,
@ -576,6 +589,7 @@ fn custom_decoder_lambda(env: &mut Env<'_>, args: DecodingFieldArgs) -> (Variabl
subs_slice,
custom_callback_lambda_set_var,
custom_callback_ret_var,
Variable::PURE,
)),
);
@ -587,6 +601,7 @@ fn custom_decoder_lambda(env: &mut Env<'_>, args: DecodingFieldArgs) -> (Variabl
function_type: this_custom_callback_var,
closure_type: custom_callback_lambda_set_var,
return_type: custom_callback_ret_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: custom_closure_symbol,
captured_symbols: vec![(state_arg_symbol, state_record_var)],
@ -985,6 +1000,7 @@ pub(super) fn finalizer(
env.subs.insert_into_vars([state_record_var, fmt_arg_var]),
closure_type,
return_type_var,
Variable::PURE,
);
// Fix up function_var so it's not Content::Error anymore
@ -995,6 +1011,7 @@ pub(super) fn finalizer(
function_type: function_var,
closure_type,
return_type: return_type_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: function_symbol,
captured_symbols: Vec::new(),
@ -1275,7 +1292,12 @@ fn make_decode_with_vars(
.insert_into_vars([bytes_arg_var, decoder_var, fmt_arg_var]);
let this_decode_with_var = synth_var(
env.subs,
Content::Structure(FlatType::Func(subs_slice, lambda_set_var, rec_var)),
Content::Structure(FlatType::Func(
subs_slice,
lambda_set_var,
rec_var,
Variable::PURE,
)),
);
env.unify(decode_with_var, this_decode_with_var);
@ -1325,6 +1347,7 @@ pub(super) fn decode_with(
Loc::at_zero(Expr::Var(Symbol::DECODE_DECODE_WITH, this_decode_with_var)),
lambda_set_var,
rec_var,
Variable::PURE,
)),
vec![
(Variable::LIST_U8, Loc::at_zero(bytes_arg_expr)),

View file

@ -102,6 +102,7 @@ pub(crate) fn decoder(env: &mut Env, _def_symbol: Symbol, arity: u32) -> (Expr,
.insert_into_vars([state_var, step_var, finalizer_var]),
decode_record_lambda_set,
tuple_decoder_var,
Variable::PURE,
);
synth_var(env.subs, Content::Structure(flat_type))
@ -120,6 +121,7 @@ pub(crate) fn decoder(env: &mut Env, _def_symbol: Symbol, arity: u32) -> (Expr,
)),
decode_record_lambda_set,
tuple_decoder_var,
Variable::PURE,
)),
vec![
(state_var, Loc::at_zero(initial_state)),
@ -276,7 +278,12 @@ fn step_elem(
.insert_into_vars([bytes_arg_var, decoder_var, fmt_arg_var]);
let this_decode_with_var = synth_var(
env.subs,
Content::Structure(FlatType::Func(subs_slice, lambda_set_var, rec_var)),
Content::Structure(FlatType::Func(
subs_slice,
lambda_set_var,
rec_var,
Variable::PURE,
)),
);
env.unify(decode_with_var, this_decode_with_var);
@ -490,6 +497,7 @@ fn step_elem(
Loc::at_zero(Expr::Var(Symbol::DECODE_DECODE_WITH, this_decode_with_var)),
lambda_set_var,
rec_var,
Variable::PURE,
)),
vec![
(
@ -545,6 +553,7 @@ fn step_elem(
subs_slice,
custom_callback_lambda_set_var,
custom_callback_ret_var,
Variable::PURE,
)),
);
@ -556,6 +565,7 @@ fn step_elem(
function_type: this_custom_callback_var,
closure_type: custom_callback_lambda_set_var,
return_type: custom_callback_ret_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: custom_closure_symbol,
captured_symbols: vec![(state_arg_symbol, state_record_var)],
@ -582,8 +592,12 @@ fn step_elem(
let decode_custom_closure_var = env.subs.fresh_unnamed_flex_var();
let this_decode_custom_var = {
let subs_slice = env.subs.insert_into_vars([this_custom_callback_var]);
let flat_type =
FlatType::Func(subs_slice, decode_custom_closure_var, decode_custom_ret_var);
let flat_type = FlatType::Func(
subs_slice,
decode_custom_closure_var,
decode_custom_ret_var,
Variable::PURE,
);
synth_var(env.subs, Content::Structure(flat_type))
};
@ -597,6 +611,7 @@ fn step_elem(
Loc::at_zero(Expr::Var(Symbol::DECODE_CUSTOM, this_decode_custom_var)),
decode_custom_closure_var,
decode_custom_ret_var,
Variable::PURE,
)),
vec![(this_custom_callback_var, Loc::at_zero(custom_callback))],
CalledVia::Space,
@ -703,7 +718,12 @@ fn step_elem(
env.subs.set_content(
function_type,
Content::Structure(FlatType::Func(args_slice, closure_type, keep_or_skip_var)),
Content::Structure(FlatType::Func(
args_slice,
closure_type,
keep_or_skip_var,
Variable::PURE,
)),
)
};
@ -711,6 +731,7 @@ fn step_elem(
function_type,
closure_type,
return_type: keep_or_skip_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: step_elem_closure,
captured_symbols: Vec::new(),
@ -888,6 +909,7 @@ fn finalizer(
env.subs.insert_into_vars([state_record_var]),
closure_type,
return_type_var,
Variable::PURE,
);
// Fix up function_var so it's not Content::Error anymore
@ -898,6 +920,7 @@ fn finalizer(
function_type: function_var,
closure_type,
return_type: return_type_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: function_symbol,
captured_symbols: Vec::new(),

View file

@ -132,6 +132,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
elem_var_slice,
to_encoder_clos_var,
elem_encoder_var,
Variable::PURE,
)),
);
@ -146,6 +147,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
Loc::at_zero(to_encoder_var),
to_encoder_clos_var,
elem_encoder_var,
Variable::PURE,
));
// toEncoder elem
@ -180,6 +182,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
elem_var_slice,
to_elem_encoder_lset,
elem_encoder_var,
Variable::PURE,
)),
);
@ -188,6 +191,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
function_type: to_elem_encoder_fn_var,
closure_type: to_elem_encoder_lset,
return_type: elem_encoder_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: to_elem_encoder_sym,
captured_symbols: vec![],
@ -216,6 +220,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
this_encode_list_args_slice,
this_encode_list_clos_var,
this_list_encoder_var,
Variable::PURE,
)),
);
@ -230,6 +235,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
Loc::at_zero(encode_list),
this_encode_list_clos_var,
this_list_encoder_var,
Variable::PURE,
));
// Encode.list lst to_elem_encoder
@ -274,6 +280,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
list_var_slice,
fn_clos_var,
this_encoder_var,
Variable::PURE,
)),
);
@ -282,6 +289,7 @@ fn to_encoder_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_encoder_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
@ -354,6 +362,7 @@ fn to_encoder_record(
field_var_slice,
to_encoder_clos_var,
encoder_var,
Variable::PURE,
)),
);
@ -368,6 +377,7 @@ fn to_encoder_record(
Loc::at_zero(to_encoder_var),
to_encoder_clos_var,
encoder_var,
Variable::PURE,
));
// toEncoder rcd.a
@ -435,6 +445,7 @@ fn to_encoder_record(
fields_list_var_slice,
encode_record_clos_var,
encoder_var,
Variable::PURE,
)),
);
@ -449,6 +460,7 @@ fn to_encoder_record(
Loc::at_zero(encode_record_var),
encode_record_clos_var,
encoder_var,
Variable::PURE,
));
// Encode.record [ { key: .., value: .. }, .. ]
@ -484,6 +496,7 @@ fn to_encoder_record(
record_var_slice,
fn_clos_var,
this_encoder_var,
Variable::PURE,
)),
);
@ -492,6 +505,7 @@ fn to_encoder_record(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_encoder_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
@ -557,6 +571,7 @@ fn to_encoder_tuple(
elem_var_slice,
to_encoder_clos_var,
encoder_var,
Variable::PURE,
)),
);
@ -571,6 +586,7 @@ fn to_encoder_tuple(
Loc::at_zero(to_encoder_var),
to_encoder_clos_var,
encoder_var,
Variable::PURE,
));
// toEncoder tup.0
@ -618,6 +634,7 @@ fn to_encoder_tuple(
elem_encoders_list_var_slice,
encode_tuple_clos_var,
encoder_var,
Variable::PURE,
)),
);
@ -632,6 +649,7 @@ fn to_encoder_tuple(
Loc::at_zero(encode_tuple_var),
encode_tuple_clos_var,
encoder_var,
Variable::PURE,
));
// Encode.tuple [ { key: .., value: .. }, .. ]
@ -667,6 +685,7 @@ fn to_encoder_tuple(
tuple_var_slice,
fn_clos_var,
this_encoder_var,
Variable::PURE,
)),
);
@ -675,6 +694,7 @@ fn to_encoder_tuple(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_encoder_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
@ -757,6 +777,7 @@ fn to_encoder_tag_union(
var_slice_of_sym_var,
to_encoder_clos_var,
encoder_var,
Variable::PURE,
)),
);
@ -772,6 +793,7 @@ fn to_encoder_tag_union(
Loc::at_zero(to_encoder_var),
to_encoder_clos_var,
encoder_var,
Variable::PURE,
));
// toEncoder rcd.a
@ -817,6 +839,7 @@ fn to_encoder_tag_union(
this_encode_tag_args_var_slice,
this_encode_tag_clos_var,
this_encoder_var,
Variable::PURE,
)),
);
@ -831,6 +854,7 @@ fn to_encoder_tag_union(
Loc::at_zero(encode_tag_var),
this_encode_tag_clos_var,
this_encoder_var,
Variable::PURE,
));
// Encode.tag "A" [ Encode.toEncoder v1, Encode.toEncoder v2 ]
@ -905,6 +929,7 @@ fn to_encoder_tag_union(
tag_union_var_slice,
fn_clos_var,
this_encoder_var,
Variable::PURE,
)),
);
@ -918,6 +943,7 @@ fn to_encoder_tag_union(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_encoder_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
@ -973,6 +999,7 @@ fn wrap_in_encode_custom(
this_append_with_args_var_slice,
this_append_with_clos_var,
Variable::LIST_U8,
Variable::PURE,
)),
);
@ -986,6 +1013,7 @@ fn wrap_in_encode_custom(
Loc::at_zero(Var(Symbol::ENCODE_APPEND_WITH, this_append_with_fn_var)),
this_append_with_clos_var,
Variable::LIST_U8,
Variable::PURE,
));
// Encode.appendWith bytes encoder fmt
@ -1022,7 +1050,12 @@ fn wrap_in_encode_custom(
let args_slice = env.subs.insert_into_vars(vec![bytes_var, fmt_var]);
env.subs.set_content(
fn_var,
Content::Structure(FlatType::Func(args_slice, fn_clos_var, Variable::LIST_U8)),
Content::Structure(FlatType::Func(
args_slice,
fn_clos_var,
Variable::LIST_U8,
Variable::PURE,
)),
);
// \bytes, fmt -[[fn_name captured_var]]-> Encode.appendWith bytes encoder fmt
@ -1030,6 +1063,7 @@ fn wrap_in_encode_custom(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: Variable::LIST_U8,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![(captured_symbol, captured_var)],
@ -1065,6 +1099,7 @@ fn wrap_in_encode_custom(
this_custom_args_var_slice,
this_custom_clos_var,
this_custom_encoder_var,
Variable::PURE,
)),
);
@ -1078,6 +1113,7 @@ fn wrap_in_encode_custom(
Loc::at_zero(Var(Symbol::ENCODE_CUSTOM, this_custom_fn_var)),
this_custom_clos_var, // -[clos]->
this_custom_encoder_var, // t' ~ Encoder fmt
Variable::PURE,
));
// Encode.custom \bytes, fmt -> Encode.appendWith bytes encoder fmt

View file

@ -475,6 +475,7 @@ fn call_hash_ability_member(
this_arguments_slice,
this_hash_clos_var,
this_out_hasher_var,
Variable::PURE,
)),
);
@ -489,6 +490,7 @@ fn call_hash_ability_member(
Loc::at_zero(hash_fn_head),
this_hash_clos_var,
this_out_hasher_var,
Variable::PURE,
));
let hash_arguments = vec![
@ -532,7 +534,12 @@ fn build_outer_derived_closure(
let args_slice = env.subs.insert_into_vars([hasher_var, val_var]);
env.subs.set_content(
fn_var,
Content::Structure(FlatType::Func(args_slice, fn_clos_var, body_var)),
Content::Structure(FlatType::Func(
args_slice,
fn_clos_var,
body_var,
Variable::PURE,
)),
);
(fn_var, fn_clos_var)
@ -542,6 +549,7 @@ fn build_outer_derived_closure(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: body_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],

View file

@ -137,6 +137,7 @@ fn to_inspector_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
elem_var_slice,
to_inspector_clos_var,
elem_inspector_var,
Variable::PURE,
)),
);
@ -152,6 +153,7 @@ fn to_inspector_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
Loc::at_zero(to_inspector_var),
to_inspector_clos_var,
elem_inspector_var,
Variable::PURE,
));
// toInspector elem
@ -186,6 +188,7 @@ fn to_inspector_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
elem_var_slice,
to_elem_inspector_lset,
elem_inspector_var,
Variable::PURE,
)),
);
@ -194,6 +197,7 @@ fn to_inspector_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
function_type: to_elem_inspector_fn_var,
closure_type: to_elem_inspector_lset,
return_type: elem_inspector_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: to_elem_inspector_sym,
captured_symbols: vec![],
@ -223,6 +227,7 @@ fn to_inspector_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
this_inspect_list_args_slice,
this_inspect_list_clos_var,
this_list_inspector_var,
Variable::PURE,
)),
);
@ -237,6 +242,7 @@ fn to_inspector_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
Loc::at_zero(inspect_list),
this_inspect_list_clos_var,
this_list_inspector_var,
Variable::PURE,
));
// Inspect.list lst to_elem_inspector
@ -285,6 +291,7 @@ fn to_inspector_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
list_var_slice,
fn_clos_var,
this_inspector_var,
Variable::PURE,
)),
);
@ -293,6 +300,7 @@ fn to_inspector_list(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_inspector_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
@ -365,6 +373,7 @@ fn to_inspector_record(
field_var_slice,
to_inspector_clos_var,
inspector_var,
Variable::PURE,
)),
);
@ -380,6 +389,7 @@ fn to_inspector_record(
Loc::at_zero(to_inspector_var),
to_inspector_clos_var,
inspector_var,
Variable::PURE,
));
// toInspector rcd.a
@ -447,6 +457,7 @@ fn to_inspector_record(
fields_list_var_slice,
inspect_record_clos_var,
inspector_var,
Variable::PURE,
)),
);
@ -461,6 +472,7 @@ fn to_inspector_record(
Loc::at_zero(inspect_record_var),
inspect_record_clos_var,
inspector_var,
Variable::PURE,
));
// Inspect.record [ { key: .., value: .. }, .. ]
@ -496,6 +508,7 @@ fn to_inspector_record(
record_var_slice,
fn_clos_var,
this_inspector_var,
Variable::PURE,
)),
);
@ -504,6 +517,7 @@ fn to_inspector_record(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_inspector_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
@ -569,6 +583,7 @@ fn to_inspector_tuple(
elem_var_slice,
to_inspector_clos_var,
inspector_var,
Variable::PURE,
)),
);
@ -584,6 +599,7 @@ fn to_inspector_tuple(
Loc::at_zero(to_inspector_var),
to_inspector_clos_var,
inspector_var,
Variable::PURE,
));
// toInspector tup.0
@ -631,6 +647,7 @@ fn to_inspector_tuple(
elem_inspectors_list_var_slice,
inspect_tuple_clos_var,
inspector_var,
Variable::PURE,
)),
);
@ -645,6 +662,7 @@ fn to_inspector_tuple(
Loc::at_zero(inspect_tuple_var),
inspect_tuple_clos_var,
inspector_var,
Variable::PURE,
));
// Inspect.tuple [ { key: .., value: .. }, .. ]
@ -680,6 +698,7 @@ fn to_inspector_tuple(
tuple_var_slice,
fn_clos_var,
this_inspector_var,
Variable::PURE,
)),
);
@ -688,6 +707,7 @@ fn to_inspector_tuple(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_inspector_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
@ -770,6 +790,7 @@ fn to_inspector_tag_union(
var_slice_of_sym_var,
to_inspector_clos_var,
inspector_var,
Variable::PURE,
)),
);
@ -785,6 +806,7 @@ fn to_inspector_tag_union(
Loc::at_zero(to_inspector_var),
to_inspector_clos_var,
inspector_var,
Variable::PURE,
));
// toInspector rcd.a
@ -834,6 +856,7 @@ fn to_inspector_tag_union(
this_inspect_tag_args_var_slice,
this_inspect_tag_clos_var,
this_inspector_var,
Variable::PURE,
)),
);
@ -848,6 +871,7 @@ fn to_inspector_tag_union(
Loc::at_zero(inspect_tag_var),
this_inspect_tag_clos_var,
this_inspector_var,
Variable::PURE,
));
// Inspect.tag "A" [ Inspect.toInspector v1, Inspect.toInspector v2 ]
@ -922,6 +946,7 @@ fn to_inspector_tag_union(
tag_union_var_slice,
fn_clos_var,
this_inspector_var,
Variable::PURE,
)),
);
@ -935,6 +960,7 @@ fn to_inspector_tag_union(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: this_inspector_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![],
@ -979,6 +1005,7 @@ fn wrap_in_inspect_custom(
this_apply_args_var_slice,
this_apply_clos_var,
fmt_var,
Variable::PURE,
)),
);
@ -992,6 +1019,7 @@ fn wrap_in_inspect_custom(
Loc::at_zero(Var(Symbol::INSPECT_APPLY, this_apply_fn_var)),
this_apply_clos_var,
fmt_var,
Variable::PURE,
));
// Inspect.apply inspector fmt
@ -1026,7 +1054,12 @@ fn wrap_in_inspect_custom(
let args_slice = env.subs.insert_into_vars(vec![fmt_var]);
env.subs.set_content(
fn_var,
Content::Structure(FlatType::Func(args_slice, fn_clos_var, fmt_var)),
Content::Structure(FlatType::Func(
args_slice,
fn_clos_var,
fmt_var,
Variable::PURE,
)),
);
// \fmt -[[fn_name captured_var]]-> Inspect.apply inspector fmt
@ -1034,6 +1067,7 @@ fn wrap_in_inspect_custom(
function_type: fn_var,
closure_type: fn_clos_var,
return_type: fmt_var,
fx_type: Variable::PURE,
early_returns: vec![],
name: fn_name,
captured_symbols: vec![(captured_symbol, captured_var)],
@ -1062,6 +1096,7 @@ fn wrap_in_inspect_custom(
this_custom_args_var_slice,
this_custom_clos_var,
this_custom_inspector_var,
Variable::PURE,
)),
);
@ -1075,6 +1110,7 @@ fn wrap_in_inspect_custom(
Loc::at_zero(Var(Symbol::INSPECT_CUSTOM, this_custom_fn_var)),
this_custom_clos_var, // -[clos]->
this_custom_inspector_var, // t' ~ Inspector fmt
Variable::PURE,
));
// Inspect.custom \fmt -> Inspect.apply inspector fmt

View file

@ -4,6 +4,7 @@ use std::iter::once;
use std::sync::{Arc, Mutex};
use roc_can::abilities::SpecializationLambdaSets;
use roc_can::def::DefKind;
use roc_can::expr::Expr;
use roc_can::pattern::Pattern;
use roc_can::{def::Def, module::ExposedByModule};
@ -90,6 +91,7 @@ fn build_derived_body(
expr_var: body_type,
pattern_vars: once((derived_symbol, body_type)).collect(),
annotation: None,
kind: DefKind::Let,
};
(def, specialization_lambda_sets)

View file

@ -81,7 +81,7 @@ impl FlatDecodable {
Err(Underivable) // yet
}
//
FlatType::Func(..) => Err(Underivable),
FlatType::Func(..) | FlatType::EffectfulFunc => Err(Underivable),
},
Content::Alias(sym, _, real_var, _) => match from_builtin_symbol(sym) {
Some(lambda) => lambda,
@ -101,6 +101,7 @@ impl FlatDecodable {
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
Content::LambdaSet(_) | Content::ErasedLambda => Err(Underivable),
Content::Pure | Content::Effectful => Err(Underivable),
}
}

View file

@ -117,7 +117,7 @@ impl FlatEncodable {
}
FlatType::EmptyRecord => Ok(Key(FlatEncodableKey::Record(vec![]))),
FlatType::EmptyTagUnion => Ok(Key(FlatEncodableKey::TagUnion(vec![]))),
FlatType::Func(..) => Err(Underivable),
FlatType::Func(..) | FlatType::EffectfulFunc => Err(Underivable),
},
Content::Alias(sym, _, real_var, _) => match from_builtin_symbol(sym) {
Some(lambda) => lambda,
@ -135,6 +135,7 @@ impl FlatEncodable {
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
Content::LambdaSet(_) | Content::ErasedLambda => Err(Underivable),
Content::Pure | Content::Effectful => Err(Underivable),
}
}

View file

@ -112,7 +112,7 @@ impl FlatHash {
FlatType::EmptyRecord => Ok(Key(FlatHashKey::Record(vec![]))),
FlatType::EmptyTagUnion => Ok(Key(FlatHashKey::TagUnion(vec![]))),
//
FlatType::Func(..) => Err(Underivable),
FlatType::Func(..) | FlatType::EffectfulFunc => Err(Underivable),
},
Content::Alias(sym, _, real_var, _) => match builtin_symbol_to_hash_lambda(sym) {
Some(lambda) => Ok(lambda),
@ -146,6 +146,7 @@ impl FlatHash {
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnboundVar),
Content::LambdaSet(_) | Content::ErasedLambda => Err(Underivable),
Content::Pure | Content::Effectful => Err(Underivable),
}
}

View file

@ -129,6 +129,9 @@ impl FlatInspectable {
FlatType::EmptyRecord => Key(FlatInspectableKey::Record(Vec::new())),
FlatType::EmptyTagUnion => Key(FlatInspectableKey::TagUnion(Vec::new())),
FlatType::Func(..) => Immediate(Symbol::INSPECT_FUNCTION),
FlatType::EffectfulFunc => {
unreachable!("There must have been a bug in the solver, because we're trying to derive Inspect on a non-concrete type.");
}
},
Content::Alias(sym, _, real_var, kind) => match Self::from_builtin_alias(sym) {
Some(lambda) => lambda,
@ -178,7 +181,9 @@ impl FlatInspectable {
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _)
| Content::LambdaSet(_)
| Content::ErasedLambda => {
| Content::ErasedLambda
| Content::Pure
| Content::Effectful => {
unreachable!("There must have been a bug in the solver, because we're trying to derive Inspect on a non-concrete type.");
}
}

View file

@ -4,8 +4,8 @@ use crate::{
Buf,
};
use roc_parse::ast::{
AbilityImpls, AssignedField, Collection, Expr, ExtractSpaces, ImplementsAbilities,
ImplementsAbility, ImplementsClause, Tag, TypeAnnotation, TypeHeader,
AbilityImpls, AssignedField, Collection, Expr, ExtractSpaces, FunctionArrow,
ImplementsAbilities, ImplementsAbility, ImplementsClause, Tag, TypeAnnotation, TypeHeader,
};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
@ -149,7 +149,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
}
Wildcard | Inferred | BoundVariable(_) | Malformed(_) => false,
Function(args, result) => {
Function(args, _arrow, result) => {
result.value.is_multiline()
|| args.iter().any(|loc_arg| loc_arg.value.is_multiline())
}
@ -195,7 +195,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
let self_is_multiline = self.is_multiline();
match self {
Function(args, ret) => {
Function(args, arrow, ret) => {
let needs_parens = parens != Parens::NotNeeded;
buf.indent(indent);
@ -236,7 +236,11 @@ impl<'a> Formattable for TypeAnnotation<'a> {
buf.spaces(1);
}
buf.push_str("->");
match arrow {
FunctionArrow::Pure => buf.push_str("->"),
FunctionArrow::Effectful => buf.push_str("=>"),
}
buf.spaces(1);
ret.value

View file

@ -4,6 +4,7 @@ use crate::expr::fmt_str_literal;
use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_default_newline, fmt_default_spaces, fmt_spaces, INDENT};
use crate::Buf;
use roc_error_macros::internal_error;
use roc_parse::ast::{
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
@ -423,6 +424,7 @@ impl<'a> Formattable for ValueDef<'a> {
ModuleImport(module_import) => module_import.is_multiline(),
IngestedFileImport(ingested_file_import) => ingested_file_import.is_multiline(),
Stmt(loc_expr) => loc_expr.is_multiline(),
StmtAfterExpr => internal_error!("shouldn't exist before can"),
}
}
@ -464,6 +466,7 @@ impl<'a> Formattable for ValueDef<'a> {
ModuleImport(module_import) => module_import.format(buf, indent),
IngestedFileImport(ingested_file_import) => ingested_file_import.format(buf, indent),
Stmt(loc_expr) => loc_expr.format_with_options(buf, parens, newlines, indent),
StmtAfterExpr => internal_error!("shouldn't exist before can"),
}
}
}

View file

@ -4324,7 +4324,7 @@ fn expose_function_to_host<'a, 'ctx>(
return_layout: InLayout<'a>,
layout_ids: &mut LayoutIds<'a>,
) {
let ident_string = symbol.as_str(&env.interns);
let ident_string = symbol.as_unsuffixed_str(&env.interns);
let proc_layout = ProcLayout {
arguments,
@ -5564,7 +5564,7 @@ pub fn build_procedures<'a>(
let getter_fn = function_value_by_func_spec(env, FuncBorrowSpec::Some(*func_spec), symbol);
let name = getter_fn.get_name().to_str().unwrap();
let getter_name = symbol.as_str(&env.interns);
let getter_name = symbol.as_unsuffixed_str(&env.interns);
// Add the getter function to the module.
let _ = expose_function_to_host_help_c_abi(
@ -5830,7 +5830,7 @@ fn build_procedures_help<'a>(
GenTest | WasmGenTest | CliTest => { /* no host, or exposing types is not supported */ }
Binary | BinaryDev | BinaryGlue => {
for (proc_name, alias_name, hels) in host_exposed_lambda_sets.iter() {
let ident_string = proc_name.name().as_str(&env.interns);
let ident_string = proc_name.name().as_unsuffixed_str(&env.interns);
let fn_name: String = format!("{}_{}", ident_string, hels.id.0);
expose_alias_to_host(

View file

@ -177,6 +177,7 @@ pub fn can_expr_with<'a>(
&dep_idents,
&module_ids,
None,
roc_can::env::FxMode::PurityInference,
);
// Desugar operators (convert them to Apply calls, taking into account
@ -203,6 +204,7 @@ pub fn can_expr_with<'a>(
rigids: MutMap::default(),
home,
resolutions_to_make: vec![],
fx_expectation: None,
},
loc_expr.region,
&loc_expr.value,

File diff suppressed because it is too large Load diff

View file

@ -4,8 +4,8 @@ use roc_can::scope::Scope;
use roc_collections::VecSet;
use roc_module::ident::ModuleName;
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::AssignedField;
use roc_parse::ast::{self, ExtractSpaces, TypeHeader};
use roc_parse::ast::{AssignedField, FunctionArrow};
use roc_parse::ast::{CommentOrNewline, TypeDef, ValueDef};
// Documentation generation requirements
@ -53,6 +53,7 @@ pub enum TypeAnnotation {
},
Function {
args: Vec<TypeAnnotation>,
arrow: FunctionArrow,
output: Box<TypeAnnotation>,
},
ObscuredTagUnion,
@ -282,6 +283,10 @@ fn generate_entry_docs(
// Don't generate docs for ingested file imports
}
ValueDef::StmtAfterExpr { .. } => {
// Ignore. Canonicalization will produce an error.
}
ValueDef::Stmt(loc_expr) => {
if let roc_parse::ast::Expr::Var {
ident: identifier, ..
@ -443,7 +448,7 @@ fn contains_unexposed_type(
Malformed(_) | Inferred | Wildcard | BoundVariable(_) => false,
Function(loc_args, loc_ret) => {
Function(loc_args, _arrow, loc_ret) => {
let loc_args_contains_unexposed_type = loc_args.iter().any(|loc_arg| {
contains_unexposed_type(&loc_arg.value, exposed_module_ids, module_ids)
});
@ -611,7 +616,7 @@ fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) ->
ast::TypeAnnotation::SpaceAfter(&sub_type_ann, _) => {
type_to_docs(in_func_type_ann, sub_type_ann)
}
ast::TypeAnnotation::Function(ast_arg_anns, output_ann) => {
ast::TypeAnnotation::Function(ast_arg_anns, arrow, output_ann) => {
let mut doc_arg_anns = Vec::new();
for ast_arg_ann in ast_arg_anns {
@ -620,6 +625,7 @@ fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) ->
Function {
args: doc_arg_anns,
arrow,
output: Box::new(type_to_docs(true, output_ann.value)),
}
}

View file

@ -15,6 +15,7 @@ use parking_lot::Mutex;
use roc_builtins::roc::module_source;
use roc_can::abilities::{AbilitiesStore, PendingAbilitiesStore, ResolvedImpl};
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints, TypeOrVar};
use roc_can::env::FxMode;
use roc_can::expr::{DbgLookup, Declarations, ExpectLookup, PendingDerives};
use roc_can::module::{
canonicalize_module_defs, ExposedByModule, ExposedForModule, ExposedModuleTypes, Module,
@ -33,7 +34,7 @@ use roc_debug_flags::{
use roc_derive::SharedDerivedModule;
use roc_error_macros::internal_error;
use roc_late_solve::{AbilitiesView, WorldAbilities};
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName};
use roc_module::ident::{Ident, IdentSuffix, ModuleName, QualifiedModuleName};
use roc_module::symbol::{
IdentIds, IdentIdsByModule, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds,
PackageQualified, Symbol,
@ -318,6 +319,7 @@ fn start_phase<'a>(
exposed_module_ids: state.exposed_modules,
exec_mode: state.exec_mode,
imported_module_params,
fx_mode: state.fx_mode,
}
}
@ -710,6 +712,7 @@ struct State<'a> {
pub platform_path: PlatformPath<'a>,
pub target: Target,
pub(self) function_kind: FunctionKind,
pub fx_mode: FxMode,
/// Note: only packages and platforms actually expose any modules;
/// for all others, this will be empty.
@ -797,6 +800,7 @@ impl<'a> State<'a> {
cache_dir,
target,
function_kind,
fx_mode: FxMode::Task,
platform_data: None,
platform_path: PlatformPath::NotSpecified,
module_cache: ModuleCache::default(),
@ -900,6 +904,7 @@ enum BuildTask<'a> {
skip_constraint_gen: bool,
exec_mode: ExecutionMode,
imported_module_params: VecMap<ModuleId, ModuleParams>,
fx_mode: FxMode,
},
Solve {
module: Module,
@ -2235,6 +2240,7 @@ fn update<'a>(
config_shorthand,
provides,
exposes_ids,
requires,
..
} => {
work.extend(state.dependencies.notify_package(config_shorthand));
@ -2269,6 +2275,12 @@ fn update<'a>(
if header.is_root_module {
state.exposed_modules = exposes_ids;
}
if requires.iter().any(|requires| {
IdentSuffix::from_name(requires.value.ident.value).is_bang()
}) {
state.fx_mode = FxMode::PurityInference;
}
}
Builtin { .. } | Module { .. } => {
if header.is_root_module {
@ -2276,11 +2288,18 @@ fn update<'a>(
state.platform_path = PlatformPath::RootIsModule;
}
}
Hosted { .. } => {
Hosted { exposes, .. } => {
if header.is_root_module {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::RootIsHosted;
}
if exposes
.iter()
.any(|exposed| exposed.value.is_effectful_fn())
{
state.fx_mode = FxMode::PurityInference;
}
}
}
@ -4394,7 +4413,12 @@ fn synth_list_len_type(subs: &mut Subs) -> Variable {
let fn_args_slice = slice_extend_new(&mut subs.variables, [list_a]);
subs.set_content(
fn_var,
Content::Structure(FlatType::Func(fn_args_slice, clos_list_len, Variable::U64)),
Content::Structure(FlatType::Func(
fn_args_slice,
clos_list_len,
Variable::U64,
Variable::PURE,
)),
);
fn_var
}
@ -5050,6 +5074,7 @@ fn canonicalize_and_constrain<'a>(
exposed_module_ids: &[ModuleId],
exec_mode: ExecutionMode,
imported_module_params: VecMap<ModuleId, ModuleParams>,
fx_mode: FxMode,
) -> CanAndCon {
let canonicalize_start = Instant::now();
@ -5093,6 +5118,7 @@ fn canonicalize_and_constrain<'a>(
&symbols_from_requires,
&mut var_store,
opt_shorthand,
fx_mode,
);
let mut types = Types::new();
@ -6276,6 +6302,7 @@ fn run_task<'a>(
exposed_module_ids,
exec_mode,
imported_module_params,
fx_mode,
} => {
let can_and_con = canonicalize_and_constrain(
arena,
@ -6289,6 +6316,7 @@ fn run_task<'a>(
exposed_module_ids,
exec_mode,
imported_module_params,
fx_mode,
);
Ok(Msg::CanonicalizedAndConstrained(can_and_con))

View file

@ -846,49 +846,6 @@ fn platform_does_not_exist() {
}
}
#[test]
fn platform_parse_error() {
let modules = vec![
(
"platform/main.roc",
indoc!(
r#"
platform "hello-c"
requires {} { main : Str }
exposes []
packages {}
imports []
provides [mainForHost]
blah 1 2 3 # causing a parse error on purpose
mainForHost : Str
"#
),
),
(
"main.roc",
indoc!(
r#"
app "hello-world"
packages { pf: "platform/main.roc" }
imports []
provides [main] to pf
main = "Hello, World!\n"
"#
),
),
];
match multiple_modules("platform_parse_error", modules) {
Err(report) => {
assert!(report.contains("STATEMENT AFTER EXPRESSION"));
assert!(report.contains("blah 1 2 3 # causing a parse error on purpose"));
}
Ok(_) => unreachable!("we expect failure here"),
}
}
#[test]
// See https://github.com/roc-lang/roc/issues/2413
fn platform_exposes_main_return_by_pointer_issue() {

View file

@ -92,7 +92,7 @@ impl<'a> LowerParams<'a> {
.retain(|(sym, _)| !home_param_symbols.contains(sym));
if let Some(ann) = &mut decls.annotations[index] {
if let Type::Function(args, _, _) = &mut ann.signature {
if let Type::Function(args, _, _, _) = &mut ann.signature {
args.push(Type::Variable(var));
}
}
@ -218,6 +218,7 @@ impl<'a> LowerParams<'a> {
captured_symbols: _,
name: _,
function_type: _,
fx_type: _,
closure_type: _,
return_type: _,
early_returns: _,
@ -519,6 +520,7 @@ impl<'a> LowerParams<'a> {
Loc::at_zero(Var(symbol, var)),
self.var_store.fresh(),
self.var_store.fresh(),
self.var_store.fresh(),
));
let body = Call(
@ -539,6 +541,7 @@ impl<'a> LowerParams<'a> {
function_type: self.var_store.fresh(),
closure_type: self.var_store.fresh(),
return_type: self.var_store.fresh(),
fx_type: self.var_store.fresh(),
early_returns: vec![],
name: self.unique_symbol(),
captured_symbols,
@ -563,6 +566,7 @@ impl<'a> LowerParams<'a> {
Loc::at_zero(Var(symbol, var)),
self.var_store.fresh(),
self.var_store.fresh(),
self.var_store.fresh(),
));
Call(

View file

@ -57,8 +57,8 @@ pub fn remove_module_param_arguments(
drop_last_argument(expected);
if let (
ErrorType::Function(found_args, _, _),
ErrorType::Function(expected_args, _, _),
ErrorType::Function(found_args, _, _, _),
ErrorType::Function(expected_args, _, _, _),
) = (found, expected)
{
if found_args.len() > expected_args.len() {
@ -99,7 +99,12 @@ pub fn remove_module_param_arguments(
| TypeError::IngestedFileUnsupportedType(_, _)
| TypeError::UnexpectedModuleParams(_, _)
| TypeError::MissingModuleParams(_, _, _)
| TypeError::ModuleParamsMismatch(_, _, _, _) => {}
| TypeError::ModuleParamsMismatch(_, _, _, _)
| TypeError::FxInPureFunction(_, _, _)
| TypeError::FxInTopLevel(_, _)
| TypeError::ExpectedEffectful(_, _)
| TypeError::UnsuffixedEffectfulFunction(_, _)
| TypeError::SuffixedPureFunction(_, _) => {}
}
}
}
@ -182,13 +187,14 @@ fn remove_for_reason(
}
| Reason::CrashArg
| Reason::ImportParams(_)
| Reason::Stmt(_)
| Reason::FunctionOutput => {}
}
}
fn drop_last_argument(err_type: &mut ErrorType) {
match err_type {
ErrorType::Function(arguments, _, _) => {
ErrorType::Function(arguments, _, _, _) => {
arguments.pop();
}
// Irrelevant
@ -204,6 +210,7 @@ fn drop_last_argument(err_type: &mut ErrorType) {
| ErrorType::RecursiveTagUnion(_, _, _, _)
| ErrorType::Alias(_, _, _, _)
| ErrorType::Range(_)
| ErrorType::Error => {}
| ErrorType::Error
| ErrorType::EffectfulFunc => {}
}
}

View file

@ -236,6 +236,10 @@ impl Lowercase {
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn suffix(&self) -> IdentSuffix {
IdentSuffix::from_name(self.0.as_str())
}
}
impl From<Lowercase> for String {
@ -362,3 +366,67 @@ impl fmt::Display for Uppercase {
fmt::Display::fmt(&self.0, f)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum IdentSuffix {
None,
Bang,
}
impl IdentSuffix {
#[inline(always)]
pub const fn from_name(name: &str) -> Self {
// Checking bytes directly so it can be const.
// This should be fine since the suffix is ASCII.
let bytes = name.as_bytes();
let len = bytes.len();
debug_assert!(len > 0, "Ident name must not be empty");
if bytes[len - 1] == b'!' {
IdentSuffix::Bang
} else {
IdentSuffix::None
}
}
pub fn is_none(&self) -> bool {
match self {
IdentSuffix::None => true,
IdentSuffix::Bang => false,
}
}
pub fn is_bang(&self) -> bool {
match self {
IdentSuffix::None => false,
IdentSuffix::Bang => true,
}
}
}
#[cfg(test)]
mod suffix_test {
use crate::ident::IdentSuffix;
#[test]
fn ends_with_bang() {
assert_eq!(IdentSuffix::from_name("foo!"), IdentSuffix::Bang)
}
#[test]
fn ends_without_bang() {
assert_eq!(IdentSuffix::from_name("foo"), IdentSuffix::None)
}
#[test]
fn invalid() {
assert_eq!(IdentSuffix::from_name("foo!bar"), IdentSuffix::None)
}
#[test]
#[should_panic]
fn empty_panics() {
IdentSuffix::from_name("");
}
}

View file

@ -1,4 +1,4 @@
use crate::ident::{Ident, Lowercase, ModuleName};
use crate::ident::{Ident, IdentSuffix, Lowercase, ModuleName};
use crate::module_err::{ModuleError, ModuleResult};
use roc_collections::{SmallStringInterner, VecMap};
use roc_error_macros::internal_error;
@ -79,7 +79,7 @@ impl Symbol {
Self {
module_id: module_id.0,
ident_id: ident_id.0,
ident_id: ident_id.raw(),
}
}
@ -88,13 +88,17 @@ impl Symbol {
}
pub const fn ident_id(self) -> IdentId {
IdentId(self.ident_id)
IdentId::from_raw(self.ident_id)
}
pub const fn is_builtin(self) -> bool {
self.module_id().is_builtin()
}
pub const fn suffix(self) -> IdentSuffix {
self.ident_id().suffix()
}
pub fn is_derivable_ability(self) -> bool {
self.derivable_ability().is_some()
}
@ -146,12 +150,16 @@ impl Symbol {
.unwrap_or_else(|| {
internal_error!(
"ident_string's IdentIds did not contain an entry for {} in module {:?}",
self.ident_id().0,
self.ident_id().index(),
self.module_id()
)
})
}
pub fn as_unsuffixed_str(self, interns: &Interns) -> &str {
self.as_str(interns).trim_end_matches('!')
}
pub const fn as_u64(self) -> u64 {
u64::from_ne_bytes(self.to_ne_bytes())
}
@ -246,11 +254,9 @@ impl fmt::Debug for Symbol {
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let module_id = self.module_id();
let ident_id = self.ident_id();
let ident_id = self.ident_id().index();
match ident_id {
IdentId(value) => write!(f, "{module_id:?}.{value:?}"),
}
write!(f, "{module_id:?}.{ident_id:?}")
}
}
@ -319,10 +325,6 @@ impl Interns {
}
}
}
pub fn from_index(module_id: ModuleId, ident_id: u32) -> Symbol {
Symbol::new(module_id, IdentId(ident_id))
}
}
pub fn get_module_ident_ids<'a>(
@ -637,28 +639,59 @@ impl ModuleIds {
}
}
/// An ID that is assigned to interned string identifiers within a module.
/// By turning these strings into numbers, post-canonicalization processes
/// like unification and optimization can run a lot faster.
///
/// This ID is unique within a given module, not globally - so to turn this back into
/// a string, you would need a ModuleId, an IdentId, and a Map<ModuleId, Map<IdentId, String>>.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct IdentId(u32);
mod ident_id {
use crate::ident::IdentSuffix;
impl IdentId {
pub const fn index(self) -> usize {
self.0 as usize
}
/// # Safety
/// An ID that is assigned to interned string identifiers within a module.
/// By turning these strings into numbers, post-canonicalization processes
/// like unification and optimization can run a lot faster.
///
/// The index is not guaranteed to know to exist.
pub unsafe fn from_index(index: u32) -> Self {
Self(index)
/// This ID is unique within a given module, not globally - so to turn this back into
/// a string, you would need a ModuleId, an IdentId, and a Map<ModuleId, Map<IdentId, String>>.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct IdentId(u32);
const BANG_FLAG: u32 = 1u32 << 31;
const UNSUFFIXED: u32 = !BANG_FLAG;
impl IdentId {
pub const fn index(self) -> usize {
(self.0 & UNSUFFIXED) as usize
}
pub const fn suffix(self) -> IdentSuffix {
if self.0 & BANG_FLAG > 0 {
IdentSuffix::Bang
} else {
IdentSuffix::None
}
}
pub(super) const fn raw(self) -> u32 {
self.0
}
pub(super) const fn from_raw(raw: u32) -> Self {
Self(raw)
}
pub(super) const fn from_index(index: usize, suffix: IdentSuffix) -> Self {
assert!(index as u32 <= UNSUFFIXED, "IdentId index too large");
match suffix {
IdentSuffix::None => Self(index as u32),
IdentSuffix::Bang => Self((index as u32) | BANG_FLAG),
}
}
pub(super) const fn from_index_named(index: usize, name: &str) -> Self {
Self::from_index(index, IdentSuffix::from_name(name))
}
}
}
pub use ident_id::IdentId;
/// Stores a mapping between Ident and IdentId.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct IdentIds {
@ -670,15 +703,15 @@ impl IdentIds {
self.interner
.iter()
.enumerate()
.map(|(index, ident)| (IdentId(index as u32), ident))
.map(|(index, ident)| (IdentId::from_index_named(index, ident), ident))
}
pub fn add_str(&mut self, ident_name: &str) -> IdentId {
IdentId(self.interner.insert(ident_name) as u32)
IdentId::from_index_named(self.interner.insert(ident_name), ident_name)
}
pub fn duplicate_ident(&mut self, ident_id: IdentId) -> IdentId {
IdentId(self.interner.duplicate(ident_id.0 as usize) as u32)
IdentId::from_index(self.interner.duplicate(ident_id.index()), ident_id.suffix())
}
pub fn get_or_insert(&mut self, name: &str) -> IdentId {
@ -692,7 +725,7 @@ impl IdentIds {
// TODO fix when same ident_name is present multiple times, see issue #2548
pub fn update_key(&mut self, old_name: &str, new_name: &str) -> Result<IdentId, String> {
match self.interner.find_and_update(old_name, new_name) {
Some(index) => Ok(IdentId(index as u32)),
Some(index) => Ok(IdentId::from_index_named(index, new_name)),
None => Err(format!("The identifier {old_name:?} is not in IdentIds")),
}
}
@ -705,12 +738,12 @@ impl IdentIds {
/// This is used, for example, during canonicalization of an Expr::Closure
/// to generate a unique symbol to refer to that closure.
pub fn gen_unique(&mut self) -> IdentId {
IdentId(self.interner.insert_index_str() as u32)
IdentId::from_index(self.interner.insert_index_str(), IdentSuffix::None)
}
pub fn is_generated_id(&self, id: IdentId) -> bool {
self.interner
.try_get(id.0 as usize)
.try_get(id.index())
.map_or(false, |str| str.starts_with(|c: char| c.is_ascii_digit()))
}
@ -718,18 +751,18 @@ impl IdentIds {
pub fn get_id(&self, ident_name: &str) -> Option<IdentId> {
self.interner
.find_index(ident_name)
.map(|i| IdentId(i as u32))
.map(|i| IdentId::from_index_named(i, ident_name))
}
#[inline(always)]
pub fn get_id_many<'a>(&'a self, ident_name: &'a str) -> impl Iterator<Item = IdentId> + 'a {
self.interner
.find_indices(ident_name)
.map(|i| IdentId(i as u32))
.map(|i| IdentId::from_index_named(i, ident_name))
}
pub fn get_name(&self, id: IdentId) -> Option<&str> {
self.interner.try_get(id.0 as usize)
self.interner.try_get(id.index())
}
pub fn get_name_str_res(&self, ident_id: IdentId) -> ModuleResult<&str> {
@ -1013,10 +1046,10 @@ macro_rules! define_builtins {
$(
$(
$(#[$ident_meta])*
pub const $ident_const: Symbol = Symbol::new(ModuleId::$module_const, IdentId($ident_id));
pub const $ident_const: Symbol = Symbol::new(ModuleId::$module_const, IdentId::from_index_named($ident_id, $ident_name));
)*
$(
pub const $u_ident_const: Symbol = Symbol::new(ModuleId::$module_const, IdentId($u_ident_id));
pub const $u_ident_const: Symbol = Symbol::new(ModuleId::$module_const, IdentId::from_index_named($u_ident_id, $u_ident_name));
)*
)+
@ -1038,9 +1071,11 @@ macro_rules! define_builtins {
// release builds, this condition is either `if true`
// or `if false` and will get optimized out.
debug_assert_eq!($exposed_apply_type, $ident_name.chars().next().unwrap().is_uppercase());
// Types should not be suffixed
debug_assert!(IdentSuffix::from_name($ident_name).is_none());
if $exposed_apply_type {
scope.insert($ident_name.into(), (Symbol::new(ModuleId::$module_const, IdentId($ident_id)), Region::zero()));
scope.insert($ident_name.into(), (Symbol::new(ModuleId::$module_const, IdentId::from_index($ident_id, IdentSuffix::None)), Region::zero()));
}
)?
)*
@ -1059,7 +1094,7 @@ macro_rules! define_builtins {
$(
$(
if $exposed_type {
($ident_name, (Symbol::new(ModuleId::$module_const, IdentId($ident_id)), Region::zero()))
($ident_name, (Symbol::new(ModuleId::$module_const, IdentId::from_raw($ident_id)), Region::zero()))
} else {
unreachable!()
},
@ -1474,6 +1509,7 @@ define_builtins! {
87 LIST_CLONE: "clone"
88 LIST_LEN_USIZE: "lenUsize"
89 LIST_CONCAT_UTF8: "concatUtf8"
90 LIST_WALK_FX: "walk!"
}
7 RESULT: "Result" => {
0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias

View file

@ -2482,6 +2482,9 @@ fn from_can_let<'a>(
lower_rest!(variable, cont.value)
}
ImportParams(_, _, None) => {
lower_rest!(variable, cont.value)
}
Var(original, _) | AbilityMember(original, _, _)
if procs.get_partial_proc(original).is_none() =>
{
@ -2617,6 +2620,7 @@ fn from_can_let<'a>(
expr_var: def.expr_var,
pattern_vars: std::iter::once((anon_name, def.expr_var)).collect(),
annotation: None,
kind: def.kind,
});
// f = #lam
@ -2626,6 +2630,7 @@ fn from_can_let<'a>(
expr_var: def.expr_var,
pattern_vars: def.pattern_vars,
annotation: def.annotation,
kind: def.kind,
});
let new_inner = LetNonRec(new_def, cont);
@ -2645,6 +2650,7 @@ fn from_can_let<'a>(
pattern_vars: def.pattern_vars,
annotation: def.annotation,
expr_var: def.expr_var,
kind: def.kind,
};
let new_inner = LetNonRec(Box::new(new_def), cont);
@ -2684,6 +2690,7 @@ fn from_can_let<'a>(
pattern_vars: def.pattern_vars,
annotation: def.annotation,
expr_var: def.expr_var,
kind: def.kind,
};
let new_inner = LetNonRec(Box::new(new_def), cont);
@ -4491,7 +4498,7 @@ pub fn with_hole<'a>(
debug_assert!(!matches!(
env.subs.get_content_without_compacting(variant_var),
Content::Structure(FlatType::Func(_, _, _))
Content::Structure(FlatType::Func(_, _, _, _))
));
convert_tag_union(
env,
@ -4516,7 +4523,7 @@ pub fn with_hole<'a>(
let content = env.subs.get_content_without_compacting(variable);
if let Content::Structure(FlatType::Func(arg_vars, _, ret_var)) = content {
if let Content::Structure(FlatType::Func(arg_vars, _, ret_var, _fx_var)) = content {
let ret_var = *ret_var;
let arg_vars = *arg_vars;
@ -5452,7 +5459,7 @@ pub fn with_hole<'a>(
}
Call(boxed, loc_args, _) => {
let (fn_var, loc_expr, _lambda_set_var, _ret_var) = *boxed;
let (fn_var, loc_expr, _lambda_set_var, _ret_var, _fx_var) = *boxed;
// even if a call looks like it's by name, it may in fact be by-pointer.
// E.g. in `(\f, x -> f x)` the call is in fact by pointer.
@ -6160,7 +6167,7 @@ fn late_resolve_ability_specialization(
if let Some(spec_symbol) = opt_resolved {
// Fast path: specialization is monomorphic, was found during solving.
spec_symbol
} else if let Content::Structure(FlatType::Func(_, lambda_set, _)) =
} else if let Content::Structure(FlatType::Func(_, lambda_set, _, _fx_var)) =
env.subs.get_content_without_compacting(specialization_var)
{
// Fast path: the member is a function, so the lambda set will tell us the
@ -6885,7 +6892,7 @@ fn register_capturing_closure<'a>(
let is_self_recursive = !matches!(recursive, roc_can::expr::Recursive::NotRecursive);
let captured_symbols = match *env.subs.get_content_without_compacting(function_type) {
Content::Structure(FlatType::Func(args, closure_var, ret)) => {
Content::Structure(FlatType::Func(args, closure_var, ret, _fx_var)) => {
let lambda_set_layout = {
LambdaSet::from_var_pub(
layout_cache,
@ -7289,6 +7296,7 @@ fn to_opt_branches<'a>(
roc_can::pattern::Pattern::Identifier(symbol),
),
pattern_vars: std::iter::once((symbol, variable)).collect(),
kind: roc_can::def::DefKind::Let,
};
let new_expr =
roc_can::expr::Expr::LetNonRec(Box::new(def), Box::new(loc_expr));
@ -10089,7 +10097,7 @@ pub fn find_lambda_sets(
// ignore the lambda set of top-level functions
match subs.get_without_compacting(initial).content {
Content::Structure(FlatType::Func(arguments, _, result)) => {
Content::Structure(FlatType::Func(arguments, _, result, _fx)) => {
let arguments = &subs.variables[arguments.indices()];
stack.extend(arguments.iter().copied());
@ -10126,7 +10134,7 @@ fn find_lambda_sets_help(
FlatType::Apply(_, arguments) => {
stack.extend(subs.get_subs_slice(*arguments).iter().rev());
}
FlatType::Func(arguments, lambda_set_var, ret_var) => {
FlatType::Func(arguments, lambda_set_var, ret_var, _fx_var) => {
use std::collections::hash_map::Entry;
// Only insert a lambda_set_var if we didn't already have a value for this key.
if let Entry::Vacant(entry) = result.entry(*lambda_set_var) {
@ -10172,6 +10180,7 @@ fn find_lambda_sets_help(
}
FlatType::EmptyRecord => {}
FlatType::EmptyTagUnion => {}
FlatType::EffectfulFunc => {}
},
Content::Alias(_, _, actual, _) => {
stack.push(*actual);
@ -10185,6 +10194,7 @@ fn find_lambda_sets_help(
}
}
Content::ErasedLambda => {}
Content::Pure | Content::Effectful => {}
}
}

View file

@ -515,6 +515,7 @@ impl<'a> RawFunctionLayout<'a> {
internal_error!("lambda set should only appear under a function, where it's handled independently.");
}
ErasedLambda => internal_error!("erased lambda type should only appear under a function, where it's handled independently"),
Pure | Effectful => internal_error!("fx vars should only appear under a function"),
Structure(flat_type) => Self::layout_from_flat_type(env, flat_type),
RangedNumber(..) => Layout::new_help(env, var, content).then(Self::ZeroArgumentThunk),
@ -592,7 +593,7 @@ impl<'a> RawFunctionLayout<'a> {
let arena = env.arena;
match flat_type {
Func(args, closure_var, ret_var) => {
Func(args, closure_var, ret_var, _fx_var) => {
let mut fn_args = Vec::with_capacity_in(args.len(), arena);
let mut cache_criteria = CACHEABLE;
@ -2150,7 +2151,7 @@ fn lambda_set_size(subs: &Subs, var: Variable) -> (usize, usize, usize) {
stack.push((*var, depth_any + 1, depth_lset));
}
}
FlatType::Func(args, lset, ret) => {
FlatType::Func(args, lset, ret, _fx_var) => {
for var in subs.get_subs_slice(*args) {
stack.push((*var, depth_any + 1, depth_lset));
}
@ -2191,7 +2192,7 @@ fn lambda_set_size(subs: &Subs, var: Variable) -> (usize, usize, usize) {
}
stack.push((ext.var(), depth_any + 1, depth_lset));
}
FlatType::EmptyRecord | FlatType::EmptyTagUnion => {}
FlatType::EmptyRecord | FlatType::EmptyTagUnion | FlatType::EffectfulFunc => {}
},
Content::FlexVar(_)
| Content::RigidVar(_)
@ -2199,7 +2200,9 @@ fn lambda_set_size(subs: &Subs, var: Variable) -> (usize, usize, usize) {
| Content::RigidAbleVar(_, _)
| Content::RangedNumber(_)
| Content::Error
| Content::ErasedLambda => {}
| Content::ErasedLambda
| Content::Pure
| Content::Effectful => {}
}
}
(max_depth_any_ctor, max_depth_only_lset, total)
@ -2479,6 +2482,9 @@ impl<'a> Layout<'a> {
ErasedLambda => {
internal_error!("erased lambda type should only appear under a function, where it's handled independently.");
}
Pure | Effectful => {
internal_error!("fx vars should only appear under a function, where they're handled independently.");
}
Structure(flat_type) => layout_from_flat_type(env, flat_type),
Alias(symbol, _args, actual_var, _) => {
@ -3315,7 +3321,7 @@ fn layout_from_flat_type<'a>(
}
}
}
Func(args, closure_var, ret_var) => {
Func(args, closure_var, ret_var, _fx_var) => {
if env.is_seen(closure_var) {
// TODO(recursive-layouts): after the naked pointer is updated, we can cache `var` to
// point to the updated layout.
@ -3459,6 +3465,9 @@ fn layout_from_flat_type<'a>(
}
EmptyTagUnion => cacheable(Ok(Layout::VOID)),
EmptyRecord => cacheable(Ok(Layout::UNIT)),
EffectfulFunc => {
internal_error!("Cannot create a layout for an unconstrained EffectfulFunc")
}
}
}
@ -4598,7 +4607,7 @@ fn layout_from_num_content<'a>(content: &Content) -> Cacheable<LayoutResult<'a>>
Alias(_, _, _, _) => {
todo!("TODO recursively resolve type aliases in num_from_content");
}
Structure(_) | RangedNumber(..) | LambdaSet(_) | ErasedLambda => {
Structure(_) | RangedNumber(..) | LambdaSet(_) | ErasedLambda | Pure | Effectful => {
panic!("Invalid Num.Num type application: {content:?}");
}
Error => Err(LayoutProblem::Erroneous),
@ -4640,7 +4649,7 @@ impl LayoutId {
// Returns something like "#UserApp_foo_1" when given a symbol that interns to "foo"
// and a LayoutId of 1.
pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
let ident_string = symbol.as_str(interns);
let ident_string = symbol.as_unsuffixed_str(interns);
let module_string = interns.module_ids.get_name(symbol.module_id()).unwrap();
format!("{}_{}_{}", module_string, ident_string, self.0)
}
@ -4648,12 +4657,12 @@ impl LayoutId {
// Returns something like "roc__foo_1_exposed" when given a symbol that interns to "foo"
// and a LayoutId of 1.
pub fn to_exposed_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
let ident_string = symbol.as_str(interns);
let ident_string = symbol.as_unsuffixed_str(interns);
format!("roc__{}_{}_exposed", ident_string, self.0)
}
pub fn to_exposed_generic_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
let ident_string = symbol.as_str(interns);
let ident_string = symbol.as_unsuffixed_str(interns);
format!("roc__{}_{}_exposed_generic", ident_string, self.0)
}
}

View file

@ -394,6 +394,7 @@ pub enum StrLiteral<'a> {
/// Values that can be tried, extracting success values or "returning early" on failure
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TryTarget {
// TODO: Remove when purity inference replaces Task fully
/// Tasks suffixed with ! are `Task.await`ed
Task,
/// Results suffixed with ? are `Result.try`ed
@ -841,6 +842,8 @@ pub enum ValueDef<'a> {
IngestedFileImport(IngestedFileImport<'a>),
Stmt(&'a Loc<Expr<'a>>),
StmtAfterExpr,
}
impl<'a> ValueDef<'a> {
@ -1093,7 +1096,9 @@ impl<'a, 'b> Iterator for RecursiveValueDefIter<'a, 'b> {
}
}
ValueDef::Stmt(loc_expr) => self.push_pending_from_expr(&loc_expr.value),
ValueDef::Annotation(_, _) | ValueDef::IngestedFileImport(_) => {}
ValueDef::Annotation(_, _)
| ValueDef::IngestedFileImport(_)
| ValueDef::StmtAfterExpr => {}
}
self.index += 1;
@ -1530,10 +1535,22 @@ impl ImplementsAbilities<'_> {
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum FunctionArrow {
/// ->
Pure,
/// =>
Effectful,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum TypeAnnotation<'a> {
/// A function. The types of its arguments, then the type of its return value.
Function(&'a [Loc<TypeAnnotation<'a>>], &'a Loc<TypeAnnotation<'a>>),
/// A function. The types of its arguments, the type of arrow used, then the type of its return value.
Function(
&'a [Loc<TypeAnnotation<'a>>],
FunctionArrow,
&'a Loc<TypeAnnotation<'a>>,
),
/// Applying a type to some arguments (e.g. Map.Map String Int)
Apply(&'a str, &'a str, &'a [Loc<TypeAnnotation<'a>>]),
@ -2740,6 +2757,7 @@ impl<'a> Malformed for ValueDef<'a> {
annotation,
}) => path.is_malformed() || annotation.is_malformed(),
ValueDef::Stmt(loc_expr) => loc_expr.is_malformed(),
ValueDef::StmtAfterExpr => false,
}
}
}
@ -2755,7 +2773,7 @@ impl<'a> Malformed for ModuleImportParams<'a> {
impl<'a> Malformed for TypeAnnotation<'a> {
fn is_malformed(&self) -> bool {
match self {
TypeAnnotation::Function(args, ret) => {
TypeAnnotation::Function(args, _arrow, ret) => {
args.iter().any(|arg| arg.is_malformed()) || ret.is_malformed()
}
TypeAnnotation::Apply(_, _, args) => args.iter().any(|arg| arg.is_malformed()),

Some files were not shown because too many files have changed in this diff Show more