Merge branch 'dev-backend-num-is-zero' of https://github.com/rtfeldman/roc into dev-backend-num-is-zero

This commit is contained in:
satotake 2021-11-22 15:10:12 +00:00 committed by GitHub
commit d59a5b2f73
67 changed files with 2502 additions and 1105 deletions

View file

@ -8,7 +8,7 @@ env:
jobs: jobs:
spell-check: spell-check:
name: spell check name: spell check
runs-on: [self-hosted] runs-on: [self-hosted, linux]
timeout-minutes: 10 timeout-minutes: 10
env: env:
FORCE_COLOR: 1 FORCE_COLOR: 1

View file

@ -9,7 +9,7 @@ on:
jobs: jobs:
deploy: deploy:
name: 'Deploy to Netlify' name: 'Deploy to Netlify'
runs-on: [self-hosted] runs-on: [self-hosted, linux]
steps: steps:
- uses: jsmrcaga/action-netlify-deploy@v1.6.0 - uses: jsmrcaga/action-netlify-deploy@v1.6.0
with: with:

View file

@ -54,3 +54,5 @@ Takeshi Sato <doublequotation@gmail.com>
Joost Baas <joost@joostbaas.eu> Joost Baas <joost@joostbaas.eu>
Callum Dunster <cdunster@users.noreply.github.com> Callum Dunster <cdunster@users.noreply.github.com>
Martin Stewart <MartinSStewart@gmail.com> Martin Stewart <MartinSStewart@gmail.com>
James Hegedus <jthegedus@hey.com>
Cristiano Piemontese <cristiano.piemontese@vidiemme.it>

View file

@ -94,13 +94,7 @@ Install nix:
`curl -L https://nixos.org/nix/install | sh` `curl -L https://nixos.org/nix/install | sh`
If you're on MacOS and using a OS version >= 10.15: You will need to start a fresh terminal session to use nix.
`sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume`
You may prefer to setup up the volume manually by following nix documentation.
> You may need to restart your terminal
### Usage ### Usage

1
Cargo.lock generated
View file

@ -3554,6 +3554,7 @@ version = "0.1.0"
name = "roc_types" name = "roc_types"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo",
"roc_collections", "roc_collections",
"roc_module", "roc_module",
"roc_region", "roc_region",

View file

@ -79,17 +79,17 @@ test-rust:
# not pre-compiling the host can cause race conditions # not pre-compiling the host can cause race conditions
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release --features with_sound --workspace && sccache --show-stats cargo test --locked --release --features with_sound --workspace && sccache --show-stats
# test the dev and wasm backend: they require an explicit feature flag. # test the dev and wasm backend: they require an explicit feature flag.
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats cargo test --locked --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats
# gen-wasm has some multithreading problems to do with the wasmer runtime. Run it single-threaded as a separate job # gen-wasm has some multithreading problems to do with the wasmer runtime. Run it single-threaded as a separate job
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats cargo test --locked --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats
# run i386 (32-bit linux) cli tests # run i386 (32-bit linux) cli tests
RUN echo "4" | cargo run --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc RUN echo "4" | cargo run --locked --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
verify-no-git-changes: verify-no-git-changes:
FROM +test-rust FROM +test-rust

View file

@ -277,7 +277,7 @@ pub fn constrain_expr<'a>(
expr_id: expr_node_id, expr_id: expr_node_id,
closure_var, closure_var,
fn_var, fn_var,
.. called_via,
} => { } => {
// The expression that evaluates to the function being called, e.g. `foo` in // The expression that evaluates to the function being called, e.g. `foo` in
// (foo) bar baz // (foo) bar baz
@ -349,7 +349,7 @@ pub fn constrain_expr<'a>(
region, region,
); );
let category = Category::CallResult(opt_symbol); let category = Category::CallResult(opt_symbol, *called_via);
let mut and_constraints = BumpVec::with_capacity_in(4, arena); let mut and_constraints = BumpVec::with_capacity_in(4, arena);
@ -1919,7 +1919,7 @@ pub mod test_constrain {
aliases, aliases,
}; };
let mut subs = Subs::new(var_store); let mut subs = Subs::new_from_varstore(var_store);
for (var, name) in rigid_variables { for (var, name) in rigid_variables {
subs.rigid_var(var, name); subs.rigid_var(var, name);

View file

@ -6,8 +6,8 @@ use crate::{
mem_pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}, mem_pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec},
}; };
use roc_can::expr::Recursive; use roc_can::expr::Recursive;
use roc_module::called_via::CalledVia;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use super::record_field::RecordField; use super::record_field::RecordField;

View file

@ -1,4 +1,4 @@
use roc_module::{operator::CalledVia, symbol::Symbol}; use roc_module::{called_via::CalledVia, symbol::Symbol};
use roc_parse::ast::StrLiteral; use roc_parse::ast::StrLiteral;
use crate::{ use crate::{

View file

@ -1,7 +1,7 @@
use crate::mem_pool::pool::{NodeId, Pool}; use crate::mem_pool::pool::{NodeId, Pool};
use bumpalo::{collections::Vec as BumpVec, Bump}; use bumpalo::{collections::Vec as BumpVec, Bump};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, ModuleName}; use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -134,23 +134,37 @@ impl<'a> Env<'a> {
)), )),
} }
} else { } else {
match self match self.dep_idents.get(&module_id) {
.dep_idents Some(exposed_ids) => match exposed_ids.get_id(&ident) {
.get(&module_id) Some(ident_id) => {
.and_then(|exposed_ids| exposed_ids.get_id(&ident)) let symbol = Symbol::new(module_id, *ident_id);
{
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
self.qualified_lookups.insert(symbol); self.qualified_lookups.insert(symbol);
Ok(symbol) Ok(symbol)
}
None => {
let exposed_values = exposed_ids
.idents()
.filter(|(_, ident)| {
ident.as_ref().starts_with(|c: char| c.is_lowercase())
})
.map(|(_, ident)| Lowercase::from(ident.as_ref()))
.collect();
Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
exposed_values,
})
}
},
None => {
panic!(
"Module {} exists, but is not recorded in dep_idents",
module_name
)
} }
None => Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
}),
} }
} }
} }

View file

@ -295,6 +295,8 @@ fn spawn_rebuild_thread(
) -> std::thread::JoinHandle<u128> { ) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone(); let thread_local_target = target.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
print!("🔨 Rebuilding host... ");
let rebuild_host_start = SystemTime::now(); let rebuild_host_start = SystemTime::now();
if !precompiled { if !precompiled {
if surgically_link { if surgically_link {
@ -322,6 +324,9 @@ fn spawn_rebuild_thread(
std::fs::copy(prehost, binary_path.as_path()).unwrap(); std::fs::copy(prehost, binary_path.as_path()).unwrap();
} }
let rebuild_host_end = rebuild_host_start.elapsed().unwrap(); let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
println!("Done!");
rebuild_host_end.as_millis() rebuild_host_end.as_millis()
}) })
} }

View file

@ -2,8 +2,8 @@ use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use libloading::Library; use libloading::Library;
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type}; use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
use roc_module::called_via::CalledVia;
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::ProcLayout; use roc_mono::ir::ProcLayout;
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant}; use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};

View file

@ -1112,6 +1112,36 @@ pub fn listAny(
return false; return false;
} }
pub fn listAll(
list: RocList,
caller: Caller1,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
element_width: usize,
) callconv(.C) bool {
if (list.bytes) |source_ptr| {
const size = list.len();
if (data_is_owned) {
inc_n_data(data, size);
}
var i: usize = 0;
while (i < size) : (i += 1) {
var satisfied = false;
const element = source_ptr + i * element_width;
caller(data, element, @ptrCast(?[*]u8, &satisfied));
if (!satisfied) {
return false;
}
}
return true;
}
return true;
}
// SWAP ELEMENTS // SWAP ELEMENTS
inline fn swapHelp(width: usize, temporary: [*]u8, ptr1: [*]u8, ptr2: [*]u8) void { inline fn swapHelp(width: usize, temporary: [*]u8, ptr1: [*]u8, ptr2: [*]u8) void {

View file

@ -51,6 +51,7 @@ comptime {
exportListFn(list.listSetInPlace, "set_in_place"); exportListFn(list.listSetInPlace, "set_in_place");
exportListFn(list.listSwap, "swap"); exportListFn(list.listSwap, "swap");
exportListFn(list.listAny, "any"); exportListFn(list.listAny, "any");
exportListFn(list.listAll, "all");
exportListFn(list.listFindUnsafe, "find_unsafe"); exportListFn(list.listFindUnsafe, "find_unsafe");
} }

View file

@ -691,6 +691,10 @@ all : List elem, (elem -> Bool) -> Bool
## any of the elements satisfy it. ## any of the elements satisfy it.
any : List elem, (elem -> Bool) -> Bool any : List elem, (elem -> Bool) -> Bool
## Run the given predicate on each element of the list, returning `True` if
## all of the elements satisfy it.
all : List elem, (elem -> Bool) -> Bool
## Returns the first element of the list satisfying a predicate function. ## Returns the first element of the list satisfying a predicate function.
## If no satisfying element is found, an `Err NotFound` is returned. ## If no satisfying element is found, an `Err NotFound` is returned.
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*

View file

@ -196,6 +196,7 @@ pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
pub const LIST_SET: &str = "roc_builtins.list.set"; pub const LIST_SET: &str = "roc_builtins.list.set";
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place"; pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place";
pub const LIST_ANY: &str = "roc_builtins.list.any"; pub const LIST_ANY: &str = "roc_builtins.list.any";
pub const LIST_ALL: &str = "roc_builtins.list.all";
pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe"; pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64"; pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";

View file

@ -1128,6 +1128,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(bool_type()), Box::new(bool_type()),
); );
// all: List elem, (elem -> Bool) -> Bool
add_top_level_function_type!(
Symbol::LIST_ALL,
vec![
list_type(flex(TVAR1)),
closure(vec![flex(TVAR1)], TVAR2, Box::new(bool_type())),
],
Box::new(bool_type()),
);
// sortWith : List a, (a, a -> Ordering) -> List a // sortWith : List a, (a, a -> Ordering) -> List a
add_top_level_function_type!( add_top_level_function_type!(
Symbol::LIST_SORT_WITH, Symbol::LIST_SORT_WITH,
@ -1159,6 +1169,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
) )
} }
// intersperse : List elem, elem -> List elem
add_top_level_function_type!(
Symbol::LIST_INTERSPERSE,
vec![list_type(flex(TVAR1)), flex(TVAR1)],
Box::new(list_type(flex(TVAR1))),
);
// Dict module // Dict module
// len : Dict * * -> Nat // len : Dict * * -> Nat

View file

@ -3,9 +3,9 @@ use crate::expr::{ClosureData, Expr::*};
use crate::expr::{Expr, Field, Recursive, WhenBranch}; use crate::expr::{Expr, Field, Recursive, WhenBranch};
use crate::pattern::Pattern; use crate::pattern::Pattern;
use roc_collections::all::SendMap; use roc_collections::all::SendMap;
use roc_module::called_via::CalledVia;
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
@ -97,6 +97,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_TAKE_LAST => list_take_last, LIST_TAKE_LAST => list_take_last,
LIST_SUBLIST => list_sublist, LIST_SUBLIST => list_sublist,
LIST_SPLIT => list_split, LIST_SPLIT => list_split,
LIST_INTERSPERSE => list_intersperse,
LIST_DROP => list_drop, LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at, LIST_DROP_AT => list_drop_at,
LIST_DROP_FIRST => list_drop_first, LIST_DROP_FIRST => list_drop_first,
@ -112,6 +113,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_WALK_UNTIL => list_walk_until, LIST_WALK_UNTIL => list_walk_until,
LIST_SORT_WITH => list_sort_with, LIST_SORT_WITH => list_sort_with,
LIST_ANY => list_any, LIST_ANY => list_any,
LIST_ALL => list_all,
LIST_FIND => list_find, LIST_FIND => list_find,
DICT_LEN => dict_len, DICT_LEN => dict_len,
DICT_EMPTY => dict_empty, DICT_EMPTY => dict_empty,
@ -2148,6 +2150,84 @@ fn list_sublist(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// List.intersperse : List elem, elem -> List elem
fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let sep_var = var_store.fresh();
let list_sym = Symbol::ARG_1;
let sep_sym = Symbol::ARG_2;
let clos_var = var_store.fresh();
let clos_acc_var = var_store.fresh();
let clos_sym = Symbol::LIST_INTERSPERSE_CLOS;
let clos_acc_sym = Symbol::ARG_3;
let clos_elem_sym = Symbol::ARG_4;
let int_var = var_store.fresh();
let zero = int(int_var, Variable::NATURAL, 0);
// \acc, elem -> acc |> List.append sep |> List.append elem
let clos = Closure(ClosureData {
function_type: clos_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: clos_acc_var,
name: clos_sym,
recursive: Recursive::NotRecursive,
captured_symbols: vec![(sep_sym, sep_var)],
arguments: vec![
(clos_acc_var, no_region(Pattern::Identifier(clos_acc_sym))),
(sep_var, no_region(Pattern::Identifier(clos_elem_sym))),
],
loc_body: {
let append_sep = RunLowLevel {
op: LowLevel::ListAppend,
args: vec![(clos_acc_var, Var(clos_acc_sym)), (sep_var, Var(sep_sym))],
ret_var: clos_acc_var,
};
Box::new(no_region(RunLowLevel {
op: LowLevel::ListAppend,
args: vec![(clos_acc_var, append_sep), (sep_var, Var(clos_elem_sym))],
ret_var: clos_acc_var,
}))
},
});
// List.walk [] l (\acc, elem -> acc |> List.append sep |> List.append elem)
let acc = RunLowLevel {
op: LowLevel::ListWalk,
args: vec![
(list_var, Var(list_sym)),
(
clos_acc_var,
List {
elem_var: sep_var,
loc_elems: vec![],
},
),
(clos_var, clos),
],
ret_var: clos_acc_var,
};
let body = RunLowLevel {
op: LowLevel::ListDropAt,
args: vec![(clos_acc_var, acc), (int_var, zero)],
ret_var: clos_acc_var,
};
defn(
symbol,
vec![(list_var, list_sym), (sep_var, sep_sym)],
var_store,
body,
clos_acc_var,
)
}
/// List.split : List elem, Nat -> { before: List elem, others: List elem } /// List.split : List elem, Nat -> { before: List elem, others: List elem }
fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
@ -2966,6 +3046,11 @@ fn list_any(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListAny, var_store) lowlevel_2(symbol, LowLevel::ListAny, var_store)
} }
/// List.all: List elem, (elem -> Bool) -> Bool
fn list_all(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListAll, var_store)
}
/// List.find : List elem, (elem -> Bool) -> Result elem [ NotFound ]* /// List.find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
fn list_find(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_find(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list = Symbol::ARG_1; let list = Symbol::ARG_1;

View file

@ -1,6 +1,6 @@
use crate::procedure::References; use crate::procedure::References;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, ModuleName}; use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -99,23 +99,37 @@ impl<'a> Env<'a> {
)), )),
} }
} else { } else {
match self match self.dep_idents.get(&module_id) {
.dep_idents Some(exposed_ids) => match exposed_ids.get_id(&ident) {
.get(&module_id) Some(ident_id) => {
.and_then(|exposed_ids| exposed_ids.get_id(&ident)) let symbol = Symbol::new(module_id, *ident_id);
{
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
self.qualified_lookups.insert(symbol); self.qualified_lookups.insert(symbol);
Ok(symbol) Ok(symbol)
}
None => {
let exposed_values = exposed_ids
.idents()
.filter(|(_, ident)| {
ident.as_ref().starts_with(|c: char| c.is_lowercase())
})
.map(|(_, ident)| Lowercase::from(ident.as_ref()))
.collect();
Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
exposed_values,
})
}
},
None => {
panic!(
"Module {} exists, but is not recorded in dep_idents",
module_name
)
} }
None => Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
}),
} }
} }
} }

View file

@ -10,9 +10,9 @@ use crate::pattern::{canonicalize_pattern, Pattern};
use crate::procedure::References; use crate::procedure::References;
use crate::scope::Scope; use crate::scope::Scope;
use roc_collections::all::{ImSet, MutMap, MutSet, SendMap}; use roc_collections::all::{ImSet, MutMap, MutSet, SendMap};
use roc_module::called_via::CalledVia;
use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast::{self, EscapedChar, StrLiteral}; use roc_parse::ast::{self, EscapedChar, StrLiteral};
use roc_parse::pattern::PatternType::*; use roc_parse::pattern::PatternType::*;
@ -1711,7 +1711,7 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) ->
(var_store.fresh(), loc_new_expr), (var_store.fresh(), loc_new_expr),
(var_store.fresh(), loc_expr), (var_store.fresh(), loc_expr),
], ],
CalledVia::Space, CalledVia::StringInterpolation,
); );
loc_expr = Located::new(0, 0, 0, 0, expr); loc_expr = Located::new(0, 0, 0, 0, expr);

View file

@ -2,9 +2,9 @@
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_module::called_via::BinOp::Pizza;
use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName; use roc_module::ident::ModuleName;
use roc_module::operator::BinOp::Pizza;
use roc_module::operator::{BinOp, CalledVia};
use roc_parse::ast::Expr::{self, *}; use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{AssignedField, Def, WhenBranch}; use roc_parse::ast::{AssignedField, Def, WhenBranch};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -277,7 +277,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
}) })
} }
UnaryOp(loc_arg, loc_op) => { UnaryOp(loc_arg, loc_op) => {
use roc_module::operator::UnaryOp::*; use roc_module::called_via::UnaryOp::*;
let region = loc_op.region; let region = loc_op.region;
let op = loc_op.value; let op = loc_op.value;
@ -475,7 +475,7 @@ fn binop_step<'a>(
op_stack: &mut Vec<Located<BinOp>>, op_stack: &mut Vec<Located<BinOp>>,
next_op: Located<BinOp>, next_op: Located<BinOp>,
) -> Step<'a> { ) -> Step<'a> {
use roc_module::operator::Associativity::*; use roc_module::called_via::Associativity::*;
use std::cmp::Ordering; use std::cmp::Ordering;
match op_stack.pop() { match op_stack.pop() {

View file

@ -254,7 +254,7 @@ pub fn constrain_expr(
exists(vec![*elem_var], And(constraints)) exists(vec![*elem_var], And(constraints))
} }
} }
Call(boxed, loc_args, _application_style) => { 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) = &**boxed;
// The expression that evaluates to the function being called, e.g. `foo` in // The expression that evaluates to the function being called, e.g. `foo` in
// (foo) bar baz // (foo) bar baz
@ -317,7 +317,7 @@ pub fn constrain_expr(
region, region,
); );
let category = Category::CallResult(opt_symbol); let category = Category::CallResult(opt_symbol, *called_via);
exists( exists(
vars, vars,

View file

@ -3,7 +3,7 @@ use crate::def::fmt_def;
use crate::pattern::fmt_pattern; use crate::pattern::fmt_pattern;
use crate::spaces::{add_spaces, fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT}; use crate::spaces::{add_spaces, fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT};
use bumpalo::collections::String; use bumpalo::collections::String;
use roc_module::operator::{self, BinOp}; use roc_module::called_via::{self, BinOp};
use roc_parse::ast::StrSegment; use roc_parse::ast::StrSegment;
use roc_parse::ast::{ use roc_parse::ast::{
AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch, AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch,
@ -296,10 +296,10 @@ impl<'a> Formattable<'a> for Expr<'a> {
BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent), BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
UnaryOp(sub_expr, unary_op) => { UnaryOp(sub_expr, unary_op) => {
match &unary_op.value { match &unary_op.value {
operator::UnaryOp::Negate => { called_via::UnaryOp::Negate => {
buf.push('-'); buf.push('-');
} }
operator::UnaryOp::Not => { called_via::UnaryOp::Not => {
buf.push('!'); buf.push('!');
} }
} }
@ -354,26 +354,26 @@ fn format_str_segment<'a>(seg: &StrSegment<'a>, buf: &mut String<'a>, indent: u1
fn push_op(buf: &mut String, op: BinOp) { fn push_op(buf: &mut String, op: BinOp) {
match op { match op {
operator::BinOp::Caret => buf.push('^'), called_via::BinOp::Caret => buf.push('^'),
operator::BinOp::Star => buf.push('*'), called_via::BinOp::Star => buf.push('*'),
operator::BinOp::Slash => buf.push('/'), called_via::BinOp::Slash => buf.push('/'),
operator::BinOp::DoubleSlash => buf.push_str("//"), called_via::BinOp::DoubleSlash => buf.push_str("//"),
operator::BinOp::Percent => buf.push('%'), called_via::BinOp::Percent => buf.push('%'),
operator::BinOp::DoublePercent => buf.push_str("%%"), called_via::BinOp::DoublePercent => buf.push_str("%%"),
operator::BinOp::Plus => buf.push('+'), called_via::BinOp::Plus => buf.push('+'),
operator::BinOp::Minus => buf.push('-'), called_via::BinOp::Minus => buf.push('-'),
operator::BinOp::Equals => buf.push_str("=="), called_via::BinOp::Equals => buf.push_str("=="),
operator::BinOp::NotEquals => buf.push_str("!="), called_via::BinOp::NotEquals => buf.push_str("!="),
operator::BinOp::LessThan => buf.push('<'), called_via::BinOp::LessThan => buf.push('<'),
operator::BinOp::GreaterThan => buf.push('>'), called_via::BinOp::GreaterThan => buf.push('>'),
operator::BinOp::LessThanOrEq => buf.push_str("<="), called_via::BinOp::LessThanOrEq => buf.push_str("<="),
operator::BinOp::GreaterThanOrEq => buf.push_str(">="), called_via::BinOp::GreaterThanOrEq => buf.push_str(">="),
operator::BinOp::And => buf.push_str("&&"), called_via::BinOp::And => buf.push_str("&&"),
operator::BinOp::Or => buf.push_str("||"), called_via::BinOp::Or => buf.push_str("||"),
operator::BinOp::Pizza => buf.push_str("|>"), called_via::BinOp::Pizza => buf.push_str("|>"),
operator::BinOp::Assignment => unreachable!(), called_via::BinOp::Assignment => unreachable!(),
operator::BinOp::HasType => unreachable!(), called_via::BinOp::HasType => unreachable!(),
operator::BinOp::Backpassing => unreachable!(), called_via::BinOp::Backpassing => unreachable!(),
} }
} }

View file

@ -1634,6 +1634,34 @@ mod tests {
} }
} }
#[test]
fn test_sub_reg64_reg64() {
let arena = bumpalo::Bump::new();
let mut buf = bumpalo::vec![in &arena];
for ((dst, src), expected) in &[
(
(X86_64GeneralReg::RAX, X86_64GeneralReg::RAX),
[0x48, 0x29, 0xC0],
),
(
(X86_64GeneralReg::RAX, X86_64GeneralReg::R15),
[0x4C, 0x29, 0xF8],
),
(
(X86_64GeneralReg::R15, X86_64GeneralReg::RAX),
[0x49, 0x29, 0xC7],
),
(
(X86_64GeneralReg::R15, X86_64GeneralReg::R15),
[0x4D, 0x29, 0xFF],
),
] {
buf.clear();
sub_reg64_reg64(&mut buf, *dst, *src);
assert_eq!(expected, &buf[..]);
}
}
#[test] #[test]
fn test_addsd_freg64_freg64() { fn test_addsd_freg64_freg64() {
let arena = bumpalo::Bump::new(); let arena = bumpalo::Bump::new();

View file

@ -8,11 +8,11 @@ use crate::llvm::build_dict::{
}; };
use crate::llvm::build_hash::generic_hash; use crate::llvm::build_hash::generic_hash;
use crate::llvm::build_list::{ use crate::llvm::build_list::{
self, allocate_list, empty_list, empty_polymorphic_list, list_any, list_append, list_concat, self, allocate_list, empty_list, empty_polymorphic_list, list_all, list_any, list_append,
list_contains, list_drop_at, list_find_trivial_not_found, list_find_unsafe, list_get_unsafe, list_concat, list_contains, list_drop_at, list_find_trivial_not_found, list_find_unsafe,
list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_keep_oks, list_len, list_map,
list_map3, list_map4, list_map_with_index, list_prepend, list_range, list_repeat, list_reverse, list_map2, list_map3, list_map4, list_map_with_index, list_prepend, list_range, list_repeat,
list_set, list_single, list_sort_with, list_sublist, list_swap, list_reverse, list_set, list_single, list_sort_with, list_sublist, list_swap,
}; };
use crate::llvm::build_str::{ use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int,
@ -4136,7 +4136,7 @@ fn build_proc_header<'a, 'ctx, 'env>(
env.module, env.module,
fn_name.as_str(), fn_name.as_str(),
fn_type, fn_type,
Linkage::Private, Linkage::Internal,
FAST_CALL_CONV, FAST_CALL_CONV,
); );
@ -5141,6 +5141,33 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
_ => unreachable!("invalid list layout"), _ => unreachable!("invalid list layout"),
} }
} }
ListAll { xs } => {
let (list, list_layout) = load_symbol_and_layout(scope, xs);
let (function, closure, closure_layout) = function_details!();
match list_layout {
Layout::Builtin(Builtin::EmptyList) => {
env.context.bool_type().const_int(1, false).into()
}
Layout::Builtin(Builtin::List(element_layout)) => {
let argument_layouts = &[**element_layout];
let roc_function_call = roc_function_call(
env,
layout_ids,
function,
closure,
closure_layout,
function_owns_closure_data,
argument_layouts,
Layout::Builtin(Builtin::Int1),
);
list_all(env, roc_function_call, list, element_layout)
}
_ => unreachable!("invalid list layout"),
}
}
ListFindUnsafe { xs } => { ListFindUnsafe { xs } => {
let (list, list_layout) = load_symbol_and_layout(scope, xs); let (list, list_layout) = load_symbol_and_layout(scope, xs);
@ -6041,7 +6068,7 @@ fn run_low_level<'a, 'ctx, 'env>(
ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
| ListAny | ListFindUnsafe | DictWalk => { | ListAny | ListAll | ListFindUnsafe | DictWalk => {
unreachable!("these are higher order, and are handled elsewhere") unreachable!("these are higher order, and are handled elsewhere")
} }
} }
@ -6213,7 +6240,7 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
env.module, env.module,
&fastcc_function_name, &fastcc_function_name,
fastcc_type, fastcc_type,
Linkage::Private, Linkage::Internal,
FAST_CALL_CONV, FAST_CALL_CONV,
); );

View file

@ -926,6 +926,27 @@ pub fn list_any<'a, 'ctx, 'env>(
) )
} }
/// List.all : List elem, \(elem -> Bool) -> Bool
pub fn list_all<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
roc_function_call: RocFunctionCall<'ctx>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
call_bitcode_fn(
env,
&[
pass_list_cc(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
layout_width(env, element_layout),
],
bitcode::LIST_ALL,
)
}
/// List.findUnsafe : List elem, (elem -> Bool) -> { value: elem, found: bool } /// List.findUnsafe : List elem, (elem -> Bool) -> { value: elem, found: bool }
pub fn list_find_unsafe<'a, 'ctx, 'env>( pub fn list_find_unsafe<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,

View file

@ -183,7 +183,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
env.module, env.module,
fn_name, fn_name,
fn_type, fn_type,
Linkage::Private, Linkage::Internal,
FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention. FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention.
); );

View file

@ -28,17 +28,38 @@ pub fn decode_low_level<'a>(
match lowlevel { match lowlevel {
StrConcat => return BuiltinCall(bitcode::STR_CONCAT), StrConcat => return BuiltinCall(bitcode::STR_CONCAT),
StrJoinWith => return NotImplemented, // needs Array
StrIsEmpty => {
code_builder.i64_const(i64::MIN);
code_builder.i64_eq();
}
StrStartsWith => return BuiltinCall(bitcode::STR_STARTS_WITH),
StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT),
StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH),
StrSplit => return NotImplemented, // needs Array
StrCountGraphemes => return NotImplemented, // test needs Array
StrFromInt => return NotImplemented, // choose builtin based on storage size
StrFromUtf8 => return NotImplemented, // needs Array
StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT),
StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT),
StrFromUtf8Range => return NotImplemented, // needs Array
StrToUtf8 => return NotImplemented, // needs Array
StrRepeat => return BuiltinCall(bitcode::STR_REPEAT),
StrFromFloat => {
// linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3
// https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html
// https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html
return NotImplemented;
}
StrTrim => return BuiltinCall(bitcode::STR_TRIM),
StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt | StrEndsWith | StrSplit ListLen | ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
| StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft | StrTrimRight
| StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim | ListLen
| ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
| ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2 | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2
| ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist
| ListDropAt | ListSwap | ListAny | ListFindUnsafe | DictSize | DictEmpty | DictInsert | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe | DictSize | DictEmpty
| DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues | DictUnion | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues
| DictIntersection | DictDifference | DictWalk | SetFromList => { | DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => {
return NotImplemented; return NotImplemented;
} }

View file

@ -5,8 +5,8 @@ use roc_can::expr::{ClosureData, Expr, Recursive};
use roc_can::pattern::Pattern; use roc_can::pattern::Pattern;
use roc_can::scope::Scope; use roc_can::scope::Scope;
use roc_collections::all::{MutSet, SendMap}; use roc_collections::all::{MutSet, SendMap};
use roc_module::called_via::CalledVia;
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};

View file

@ -19,8 +19,7 @@ use roc_module::symbol::{
Symbol, Symbol,
}; };
use roc_mono::ir::{ use roc_mono::ir::{
CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs,
ProcLayout, Procs,
}; };
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem}; use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, StrLiteral, TypeAnnotation}; use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
@ -356,7 +355,7 @@ struct ModuleCache<'a> {
constrained: MutMap<ModuleId, ConstrainedModule>, constrained: MutMap<ModuleId, ConstrainedModule>,
typechecked: MutMap<ModuleId, TypeCheckedModule<'a>>, typechecked: MutMap<ModuleId, TypeCheckedModule<'a>>,
found_specializations: MutMap<ModuleId, FoundSpecializationsModule<'a>>, found_specializations: MutMap<ModuleId, FoundSpecializationsModule<'a>>,
external_specializations_requested: MutMap<ModuleId, ExternalSpecializations<'a>>, external_specializations_requested: MutMap<ModuleId, Vec<ExternalSpecializations>>,
/// Various information /// Various information
imports: MutMap<ModuleId, MutSet<ModuleId>>, imports: MutMap<ModuleId, MutSet<ModuleId>>,
@ -587,7 +586,7 @@ fn start_phase<'a>(
.module_cache .module_cache
.external_specializations_requested .external_specializations_requested
.remove(&module_id) .remove(&module_id)
.unwrap_or_else(|| ExternalSpecializations::new_in(arena)); .unwrap_or_default();
let FoundSpecializationsModule { let FoundSpecializationsModule {
module_id, module_id,
@ -831,7 +830,7 @@ enum Msg<'a> {
module_id: ModuleId, module_id: ModuleId,
ident_ids: IdentIds, ident_ids: IdentIds,
layout_cache: LayoutCache<'a>, layout_cache: LayoutCache<'a>,
external_specializations_requested: BumpMap<ModuleId, ExternalSpecializations<'a>>, external_specializations_requested: BumpMap<ModuleId, ExternalSpecializations>,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
problems: Vec<roc_mono::ir::MonoProblem>, problems: Vec<roc_mono::ir::MonoProblem>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
@ -911,9 +910,6 @@ struct State<'a> {
/// pending specializations in the same thread. /// pending specializations in the same thread.
pub needs_specialization: MutSet<ModuleId>, pub needs_specialization: MutSet<ModuleId>,
pub all_pending_specializations:
MutMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>,
pub specializations_in_flight: u32, pub specializations_in_flight: u32,
pub timings: MutMap<ModuleId, ModuleTiming>, pub timings: MutMap<ModuleId, ModuleTiming>,
@ -1054,7 +1050,7 @@ enum BuildTask<'a> {
subs: Subs, subs: Subs,
procs_base: ProcsBase<'a>, procs_base: ProcsBase<'a>,
layout_cache: LayoutCache<'a>, layout_cache: LayoutCache<'a>,
specializations_we_must_make: ExternalSpecializations<'a>, specializations_we_must_make: Vec<ExternalSpecializations>,
module_timing: ModuleTiming, module_timing: ModuleTiming,
}, },
} }
@ -1538,7 +1534,6 @@ where
unsolved_modules: MutMap::default(), unsolved_modules: MutMap::default(),
timings: MutMap::default(), timings: MutMap::default(),
needs_specialization: MutSet::default(), needs_specialization: MutSet::default(),
all_pending_specializations: MutMap::default(),
specializations_in_flight: 0, specializations_in_flight: 0,
layout_caches: std::vec::Vec::with_capacity(num_cpus::get()), layout_caches: std::vec::Vec::with_capacity(num_cpus::get()),
procs: Procs::new_in(arena), procs: Procs::new_in(arena),
@ -2067,17 +2062,6 @@ fn update<'a>(
log!("found specializations for {:?}", module_id); log!("found specializations for {:?}", module_id);
let subs = solved_subs.into_inner(); let subs = solved_subs.into_inner();
for (symbol, specs) in &procs_base.specializations_for_host {
let existing = match state.all_pending_specializations.entry(*symbol) {
Vacant(entry) => entry.insert(MutMap::default()),
Occupied(entry) => entry.into_mut(),
};
for (layout, pend) in specs {
existing.insert(*layout, pend.clone());
}
}
state state
.module_cache .module_cache
.top_level_thunks .top_level_thunks
@ -2171,11 +2155,11 @@ fn update<'a>(
.external_specializations_requested .external_specializations_requested
.entry(module_id) .entry(module_id)
{ {
Vacant(entry) => entry.insert(ExternalSpecializations::new_in(arena)), Vacant(entry) => entry.insert(vec![]),
Occupied(entry) => entry.into_mut(), Occupied(entry) => entry.into_mut(),
}; };
existing.extend(requested); existing.push(requested);
} }
msg_tx msg_tx
@ -2198,11 +2182,11 @@ fn update<'a>(
.external_specializations_requested .external_specializations_requested
.entry(module_id) .entry(module_id)
{ {
Vacant(entry) => entry.insert(ExternalSpecializations::new_in(arena)), Vacant(entry) => entry.insert(vec![]),
Occupied(entry) => entry.into_mut(), Occupied(entry) => entry.into_mut(),
}; };
existing.extend(requested); existing.push(requested);
} }
start_tasks(arena, &mut state, work, injector, worker_listeners)?; start_tasks(arena, &mut state, work, injector, worker_listeners)?;
@ -3936,7 +3920,7 @@ fn make_specializations<'a>(
mut subs: Subs, mut subs: Subs,
procs_base: ProcsBase<'a>, procs_base: ProcsBase<'a>,
mut layout_cache: LayoutCache<'a>, mut layout_cache: LayoutCache<'a>,
specializations_we_must_make: ExternalSpecializations<'a>, specializations_we_must_make: Vec<ExternalSpecializations>,
mut module_timing: ModuleTiming, mut module_timing: ModuleTiming,
ptr_bytes: u32, ptr_bytes: u32,
) -> Msg<'a> { ) -> Msg<'a> {
@ -3972,7 +3956,7 @@ fn make_specializations<'a>(
&mut mono_env, &mut mono_env,
procs, procs,
specializations_we_must_make, specializations_we_must_make,
procs_base.specializations_for_host, procs_base.host_specializations,
&mut layout_cache, &mut layout_cache,
); );
@ -4004,27 +3988,11 @@ struct ProcsBase<'a> {
partial_procs: BumpMap<Symbol, PartialProc<'a>>, partial_procs: BumpMap<Symbol, PartialProc<'a>>,
module_thunks: &'a [Symbol], module_thunks: &'a [Symbol],
/// A host-exposed function must be specialized; it's a seed for subsequent specializations /// A host-exposed function must be specialized; it's a seed for subsequent specializations
specializations_for_host: BumpMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>, host_specializations: roc_mono::ir::HostSpecializations,
runtime_errors: BumpMap<Symbol, &'a str>, runtime_errors: BumpMap<Symbol, &'a str>,
imported_module_thunks: &'a [Symbol], imported_module_thunks: &'a [Symbol],
} }
impl<'a> ProcsBase<'a> {
fn add_specialization_for_host(
&mut self,
symbol: Symbol,
layout: ProcLayout<'a>,
pending: PendingSpecialization<'a>,
) {
let all_pending = self
.specializations_for_host
.entry(symbol)
.or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher()));
all_pending.insert(layout, pending);
}
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn build_pending_specializations<'a>( fn build_pending_specializations<'a>(
arena: &'a Bump, arena: &'a Bump,
@ -4046,7 +4014,7 @@ fn build_pending_specializations<'a>(
let mut procs_base = ProcsBase { let mut procs_base = ProcsBase {
partial_procs: BumpMap::default(), partial_procs: BumpMap::default(),
module_thunks: &[], module_thunks: &[],
specializations_for_host: BumpMap::default(), host_specializations: roc_mono::ir::HostSpecializations::new(),
runtime_errors: BumpMap::default(), runtime_errors: BumpMap::default(),
imported_module_thunks, imported_module_thunks,
}; };
@ -4134,7 +4102,7 @@ fn add_def_to_module<'a>(
match def.loc_pattern.value { match def.loc_pattern.value {
Identifier(symbol) => { Identifier(symbol) => {
let is_exposed = exposed_to_host.contains_key(&symbol); let is_host_exposed = exposed_to_host.contains_key(&symbol);
match def.loc_expr.value { match def.loc_expr.value {
Closure(ClosureData { Closure(ClosureData {
@ -4152,42 +4120,37 @@ fn add_def_to_module<'a>(
// register it as such. Otherwise, since it // register it as such. Otherwise, since it
// never gets called by Roc code, it will never // never gets called by Roc code, it will never
// get specialized! // get specialized!
if is_exposed { if is_host_exposed {
let layout = match layout_cache.raw_from_var( let layout_result =
mono_env.arena, layout_cache.raw_from_var(mono_env.arena, annotation, mono_env.subs);
annotation,
mono_env.subs,
) {
Ok(l) => l,
Err(LayoutProblem::Erroneous) => {
let message = "top level function has erroneous type";
procs.runtime_errors.insert(symbol, message);
return;
}
Err(LayoutProblem::UnresolvedTypeVar(v)) => {
let message = format!(
"top level function has unresolved type variable {:?}",
v
);
procs
.runtime_errors
.insert(symbol, mono_env.arena.alloc(message));
return;
}
};
let pending = PendingSpecialization::from_exposed_function( // cannot specialize when e.g. main's type contains type variables
mono_env.arena, if let Err(e) = layout_result {
match e {
LayoutProblem::Erroneous => {
let message = "top level function has erroneous type";
procs.runtime_errors.insert(symbol, message);
return;
}
LayoutProblem::UnresolvedTypeVar(v) => {
let message = format!(
"top level function has unresolved type variable {:?}",
v
);
procs
.runtime_errors
.insert(symbol, mono_env.arena.alloc(message));
return;
}
}
}
procs.host_specializations.insert_host_exposed(
mono_env.subs, mono_env.subs,
symbol,
def.annotation, def.annotation,
annotation, annotation,
); );
procs.add_specialization_for_host(
symbol,
ProcLayout::from_raw(mono_env.arena, layout),
pending,
);
} }
let partial_proc = PartialProc::from_named_function( let partial_proc = PartialProc::from_named_function(
@ -4207,51 +4170,47 @@ fn add_def_to_module<'a>(
// mark this symbols as a top-level thunk before any other work on the procs // mark this symbols as a top-level thunk before any other work on the procs
module_thunks.push(symbol); module_thunks.push(symbol);
let annotation = def.expr_var;
// If this is an exposed symbol, we need to // If this is an exposed symbol, we need to
// register it as such. Otherwise, since it // register it as such. Otherwise, since it
// never gets called by Roc code, it will never // never gets called by Roc code, it will never
// get specialized! // get specialized!
if is_exposed { if is_host_exposed {
let annotation = def.expr_var; let layout_result =
layout_cache.raw_from_var(mono_env.arena, annotation, mono_env.subs);
let top_level = match layout_cache.from_var( // cannot specialize when e.g. main's type contains type variables
mono_env.arena, if let Err(e) = layout_result {
annotation, match e {
mono_env.subs, LayoutProblem::Erroneous => {
) { let message = "top level function has erroneous type";
Ok(l) => { procs.runtime_errors.insert(symbol, message);
// remember, this is a 0-argument thunk return;
ProcLayout::new(mono_env.arena, &[], l) }
LayoutProblem::UnresolvedTypeVar(v) => {
let message = format!(
"top level function has unresolved type variable {:?}",
v
);
procs
.runtime_errors
.insert(symbol, mono_env.arena.alloc(message));
return;
}
} }
Err(LayoutProblem::Erroneous) => { }
let message = "top level function has erroneous type";
procs.runtime_errors.insert(symbol, message);
return;
}
Err(LayoutProblem::UnresolvedTypeVar(v)) => {
let message = format!(
"top level function has unresolved type variable {:?}",
v
);
procs
.runtime_errors
.insert(symbol, mono_env.arena.alloc(message));
return;
}
};
let pending = PendingSpecialization::from_exposed_function( procs.host_specializations.insert_host_exposed(
mono_env.arena,
mono_env.subs, mono_env.subs,
symbol,
def.annotation, def.annotation,
annotation, annotation,
); );
procs.add_specialization_for_host(symbol, top_level, pending);
} }
let proc = PartialProc { let proc = PartialProc {
annotation: def.expr_var, annotation,
// This is a 0-arity thunk, so it has no arguments. // This is a 0-arity thunk, so it has no arguments.
pattern_symbols: &[], pattern_symbols: &[],
// This is a top-level definition, so it cannot capture anything // This is a top-level definition, so it cannot capture anything

View file

@ -12,6 +12,10 @@ pub enum CalledVia {
/// Calling with a unary operator, e.g. (!foo bar baz) or (-foo bar baz) /// Calling with a unary operator, e.g. (!foo bar baz) or (-foo bar baz)
UnaryOp(UnaryOp), UnaryOp(UnaryOp),
/// This call is the result of desugaring string interpolation,
/// e.g. "\(first) \(last)" is transformed into Str.concat (Str.concat first " ") last.
StringInterpolation,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]

View file

@ -2,10 +2,10 @@
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
pub mod called_via;
pub mod ident; pub mod ident;
pub mod low_level; pub mod low_level;
pub mod module_err; pub mod module_err;
pub mod operator;
pub mod symbol; pub mod symbol;
#[macro_use] #[macro_use]

View file

@ -50,6 +50,7 @@ pub enum LowLevel {
ListDropAt, ListDropAt,
ListSwap, ListSwap,
ListAny, ListAny,
ListAll,
ListFindUnsafe, ListFindUnsafe,
DictSize, DictSize,
DictEmpty, DictEmpty,
@ -131,6 +132,7 @@ macro_rules! higher_order {
| ListKeepErrs | ListKeepErrs
| ListSortWith | ListSortWith
| ListAny | ListAny
| ListAll
| ListFindUnsafe | ListFindUnsafe
| DictWalk | DictWalk
}; };
@ -162,6 +164,7 @@ impl LowLevel {
ListKeepErrs => 1, ListKeepErrs => 1,
ListSortWith => 1, ListSortWith => 1,
ListAny => 1, ListAny => 1,
ListAll => 1,
ListFindUnsafe => 1, ListFindUnsafe => 1,
DictWalk => 2, DictWalk => 2,
_ => unreachable!(), _ => unreachable!(),
@ -220,6 +223,7 @@ impl LowLevel {
Symbol::LIST_DROP_AT => Some(ListDropAt), Symbol::LIST_DROP_AT => Some(ListDropAt),
Symbol::LIST_SWAP => Some(ListSwap), Symbol::LIST_SWAP => Some(ListSwap),
Symbol::LIST_ANY => Some(ListAny), Symbol::LIST_ANY => Some(ListAny),
Symbol::LIST_ALL => Some(ListAll),
Symbol::LIST_FIND => None, Symbol::LIST_FIND => None,
Symbol::DICT_LEN => Some(DictSize), Symbol::DICT_LEN => Some(DictSize),
Symbol::DICT_EMPTY => Some(DictEmpty), Symbol::DICT_EMPTY => Some(DictEmpty),

View file

@ -877,6 +877,9 @@ define_builtins! {
// used in wasm dev backend to mark temporary values in the VM stack // used in wasm dev backend to mark temporary values in the VM stack
24 WASM_TMP: "#wasm_tmp" 24 WASM_TMP: "#wasm_tmp"
// the _ used in mono when a specialized symbol is deleted
25 REMOVED_SPECIALIZATION: "#removed_specialization"
} }
1 NUM: "Num" => { 1 NUM: "Num" => {
0 NUM_NUM: "Num" imported // the Num.Num type alias 0 NUM_NUM: "Num" imported // the Num.Num type alias
@ -989,12 +992,16 @@ define_builtins! {
} }
2 BOOL: "Bool" => { 2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
1 BOOL_AND: "and" 1 BOOL_FALSE: "False" imported // Bool.Bool = [ False, True ]
2 BOOL_OR: "or" // NB: not strictly needed; used for finding global tag names in error suggestions
3 BOOL_NOT: "not" 2 BOOL_TRUE: "True" imported // Bool.Bool = [ False, True ]
4 BOOL_XOR: "xor" // NB: not strictly needed; used for finding global tag names in error suggestions
5 BOOL_EQ: "isEq" 3 BOOL_AND: "and"
6 BOOL_NEQ: "isNotEq" 4 BOOL_OR: "or"
5 BOOL_NOT: "not"
6 BOOL_XOR: "xor"
7 BOOL_EQ: "isEq"
8 BOOL_NEQ: "isNotEq"
} }
3 STR: "Str" => { 3 STR: "Str" => {
0 STR_STR: "Str" imported // the Str.Str type alias 0 STR_STR: "Str" imported // the Str.Str type alias
@ -1072,17 +1079,24 @@ define_builtins! {
47 LIST_FIND: "find" 47 LIST_FIND: "find"
48 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find 48 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find
49 LIST_SUBLIST: "sublist" 49 LIST_SUBLIST: "sublist"
50 LIST_SPLIT: "split" 50 LIST_INTERSPERSE: "intersperse"
51 LIST_SPLIT_CLOS: "#splitClos" 51 LIST_INTERSPERSE_CLOS: "#intersperseClos"
52 LIST_SPLIT: "split"
53 LIST_SPLIT_CLOS: "#splitClos"
54 LIST_ALL: "all"
} }
5 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias
1 RESULT_MAP: "map" 1 RESULT_OK: "Ok" imported // Result.Result a e = [ Ok a, Err e ]
2 RESULT_MAP_ERR: "mapErr" // NB: not strictly needed; used for finding global tag names in error suggestions
3 RESULT_WITH_DEFAULT: "withDefault" 2 RESULT_ERR: "Err" imported // Result.Result a e = [ Ok a, Err e ]
4 RESULT_AFTER: "after" // NB: not strictly needed; used for finding global tag names in error suggestions
5 RESULT_IS_OK: "isOk" 3 RESULT_MAP: "map"
6 RESULT_IS_ERR: "isErr" 4 RESULT_MAP_ERR: "mapErr"
5 RESULT_WITH_DEFAULT: "withDefault"
6 RESULT_AFTER: "after"
7 RESULT_IS_OK: "isOk"
8 RESULT_IS_ERR: "isErr"
} }
6 DICT: "Dict" => { 6 DICT: "Dict" => {
0 DICT_DICT: "Dict" imported // the Dict.Dict type alias 0 DICT_DICT: "Dict" imported // the Dict.Dict type alias

View file

@ -1093,6 +1093,25 @@ fn call_spec(
add_loop(builder, block, state_type, init_state, loop_body) add_loop(builder, block, state_type, init_state, loop_body)
} }
ListAll { xs } => {
let list = env.symbols[xs];
let loop_body = |builder: &mut FuncDefBuilder, block, _state| {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let element = builder.add_bag_get(block, bag)?;
let new_state = call_function!(builder, block, [element]);
Ok(new_state)
};
let state_layout = Layout::Builtin(Builtin::Int1);
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_num(builder, block)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListFindUnsafe { xs } => { ListFindUnsafe { xs } => {
let list = env.symbols[xs]; let list = env.symbols[xs];

View file

@ -619,6 +619,7 @@ impl<'a> BorrowInfState<'a> {
| ListKeepOks { xs } | ListKeepOks { xs }
| ListKeepErrs { xs } | ListKeepErrs { xs }
| ListAny { xs } | ListAny { xs }
| ListAll { xs }
| ListFindUnsafe { xs } => { | ListFindUnsafe { xs } => {
// own the list if the function wants to own the element // own the list if the function wants to own the element
if !function_ps[0].borrow { if !function_ps[0].borrow {
@ -953,7 +954,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListMap2 => arena.alloc_slice_copy(&[owned, owned, function, closure_data]), ListMap2 => arena.alloc_slice_copy(&[owned, owned, function, closure_data]),
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, function, closure_data]), ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, function, closure_data]),
ListMap4 => arena.alloc_slice_copy(&[owned, owned, owned, owned, function, closure_data]), ListMap4 => arena.alloc_slice_copy(&[owned, owned, owned, owned, function, closure_data]),
ListKeepIf | ListKeepOks | ListKeepErrs | ListAny => { ListKeepIf | ListKeepOks | ListKeepErrs | ListAny | ListAll => {
arena.alloc_slice_copy(&[owned, function, closure_data]) arena.alloc_slice_copy(&[owned, function, closure_data])
} }
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]), ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),

View file

@ -536,6 +536,7 @@ impl<'a> Context<'a> {
| ListKeepOks { xs } | ListKeepOks { xs }
| ListKeepErrs { xs } | ListKeepErrs { xs }
| ListAny { xs } | ListAny { xs }
| ListAll { xs }
| ListFindUnsafe { xs } => { | ListFindUnsafe { xs } => {
let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA]; let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA];

File diff suppressed because it is too large Load diff

View file

@ -50,6 +50,9 @@ pub enum HigherOrder {
ListAny { ListAny {
xs: Symbol, xs: Symbol,
}, },
ListAll {
xs: Symbol,
},
ListFindUnsafe { ListFindUnsafe {
xs: Symbol, xs: Symbol,
}, },
@ -77,6 +80,7 @@ impl HigherOrder {
HigherOrder::ListFindUnsafe { .. } => 1, HigherOrder::ListFindUnsafe { .. } => 1,
HigherOrder::DictWalk { .. } => 2, HigherOrder::DictWalk { .. } => 2,
HigherOrder::ListAny { .. } => 1, HigherOrder::ListAny { .. } => 1,
HigherOrder::ListAll { .. } => 1,
} }
} }
} }

View file

@ -4,7 +4,7 @@ use crate::header::{AppHeader, ImportsEntry, InterfaceHeader, PlatformHeader, Ty
use crate::ident::Ident; use crate::ident::Ident;
use bumpalo::collections::{String, Vec}; use bumpalo::collections::{String, Vec};
use bumpalo::Bump; use bumpalo::Bump;
use roc_module::operator::{BinOp, CalledVia, UnaryOp}; use roc_module::called_via::{BinOp, CalledVia, UnaryOp};
use roc_region::all::{Loc, Position, Region}; use roc_region::all::{Loc, Position, Region};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]

View file

@ -13,7 +13,7 @@ use crate::pattern::loc_closure_param;
use crate::type_annotation; use crate::type_annotation;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_module::operator::{BinOp, CalledVia, UnaryOp}; use roc_module::called_via::{BinOp, CalledVia, UnaryOp};
use roc_region::all::{Located, Position, Region}; use roc_region::all::{Located, Position, Region};
use crate::parser::Progress::{self, *}; use crate::parser::Progress::{self, *};

View file

@ -1,6 +1,6 @@
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
use roc_module::called_via::BinOp;
use roc_module::ident::{Ident, Lowercase, ModuleName, TagName}; use roc_module::ident::{Ident, Lowercase, ModuleName, TagName};
use roc_module::operator::BinOp;
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_parse::ast::Base; use roc_parse::ast::Base;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
@ -139,6 +139,7 @@ pub enum RuntimeError {
module_name: ModuleName, module_name: ModuleName,
ident: Ident, ident: Ident,
region: Region, region: Region,
exposed_values: Vec<Lowercase>,
}, },
ModuleNotImported { ModuleNotImported {
module_name: ModuleName, module_name: ModuleName,

View file

@ -12,6 +12,7 @@ roc_module = { path = "../module" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
bumpalo = { version = "3.8.0", features = ["collections"] }
[dev-dependencies] [dev-dependencies]
roc_load = { path = "../load" } roc_load = { path = "../load" }

View file

@ -27,7 +27,7 @@ pub fn run_solve(
aliases, aliases,
}; };
let mut subs = Subs::new(var_store); let mut subs = Subs::new_from_varstore(var_store);
for (var, name) in rigid_variables { for (var, name) in rigid_variables {
subs.rigid_var(var, name); subs.rigid_var(var, name);

View file

@ -619,10 +619,13 @@ fn type_to_var(
subs: &mut Subs, subs: &mut Subs,
rank: Rank, rank: Rank,
pools: &mut Pools, pools: &mut Pools,
cached: &mut MutMap<Symbol, Variable>, _: &mut MutMap<Symbol, Variable>,
typ: &Type, typ: &Type,
) -> Variable { ) -> Variable {
type_to_variable(subs, rank, pools, cached, typ) // capacity based on the false hello world program
let arena = bumpalo::Bump::with_capacity(4 * 1024);
type_to_variable(subs, rank, pools, &arena, typ)
} }
/// Abusing existing functions for our purposes /// Abusing existing functions for our purposes
@ -630,25 +633,29 @@ fn type_to_var(
pub fn insert_type_into_subs(subs: &mut Subs, typ: &Type) -> Variable { pub fn insert_type_into_subs(subs: &mut Subs, typ: &Type) -> Variable {
let rank = Rank::NONE; let rank = Rank::NONE;
let mut pools = Pools::default(); let mut pools = Pools::default();
let mut cached = MutMap::default();
type_to_variable(subs, rank, &mut pools, &mut cached, typ) // capacity based on the false hello world program
let arena = bumpalo::Bump::with_capacity(4 * 1024);
type_to_variable(subs, rank, &mut pools, &arena, typ)
} }
fn type_to_variable( fn type_to_variable<'a>(
subs: &mut Subs, subs: &mut Subs,
rank: Rank, rank: Rank,
pools: &mut Pools, pools: &mut Pools,
cached: &mut MutMap<Symbol, Variable>, arena: &'a bumpalo::Bump,
typ: &Type, typ: &Type,
) -> Variable { ) -> Variable {
use bumpalo::collections::Vec;
match typ { match typ {
Variable(var) => *var, Variable(var) => *var,
Apply(symbol, args) => { Apply(symbol, args) => {
let mut new_arg_vars = Vec::with_capacity(args.len()); let mut new_arg_vars = Vec::with_capacity_in(args.len(), arena);
for arg in args { for arg in args {
let var = type_to_variable(subs, rank, pools, cached, arg); let var = type_to_variable(subs, rank, pools, arena, arg);
new_arg_vars.push(var); new_arg_vars.push(var);
} }
@ -663,32 +670,32 @@ fn type_to_variable(
// This case is important for the rank of boolean variables // This case is important for the rank of boolean variables
Function(arg_vars, closure_type, ret_type) => { Function(arg_vars, closure_type, ret_type) => {
let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); let mut new_arg_vars = Vec::with_capacity_in(arg_vars.len(), arena);
for arg in arg_vars { for arg in arg_vars {
let var = type_to_variable(subs, rank, pools, cached, arg); let var = type_to_variable(subs, rank, pools, arena, arg);
new_arg_vars.push(var); new_arg_vars.push(var);
} }
let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars);
let ret_var = type_to_variable(subs, rank, pools, cached, ret_type); let ret_var = type_to_variable(subs, rank, pools, arena, ret_type);
let closure_var = type_to_variable(subs, rank, pools, cached, closure_type); let closure_var = type_to_variable(subs, rank, pools, arena, closure_type);
let content = Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)); let content = Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var));
register(subs, rank, pools, content) register(subs, rank, pools, content)
} }
Record(fields, ext) => { Record(fields, ext) => {
let mut field_vars = Vec::with_capacity(fields.len()); let mut field_vars = Vec::with_capacity_in(fields.len(), arena);
for (field, field_type) in fields { for (field, field_type) in fields {
let field_var = let field_var =
field_type.map(|typ| type_to_variable(subs, rank, pools, cached, typ)); field_type.map(|typ| type_to_variable(subs, rank, pools, arena, typ));
field_vars.push((field.clone(), field_var)); field_vars.push((field.clone(), field_var));
} }
let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext);
let (it, new_ext_var) = let (it, new_ext_var) =
gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var); gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var);
@ -707,14 +714,14 @@ fn type_to_variable(
register(subs, rank, pools, content) register(subs, rank, pools, content)
} }
TagUnion(tags, ext) => { TagUnion(tags, ext) => {
let (union_tags, ext) = type_to_union_tags(subs, rank, pools, cached, tags, ext); let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext);
let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); let content = Content::Structure(FlatType::TagUnion(union_tags, ext));
register(subs, rank, pools, content) register(subs, rank, pools, content)
} }
FunctionOrTagUnion(tag_name, symbol, ext) => { FunctionOrTagUnion(tag_name, symbol, ext) => {
let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext);
let mut ext_tag_vec = Vec::new(); let mut ext_tag_vec = std::vec::Vec::new();
let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union(
subs, subs,
temp_ext_var, temp_ext_var,
@ -735,7 +742,7 @@ fn type_to_variable(
register(subs, rank, pools, content) register(subs, rank, pools, content)
} }
RecursiveTagUnion(rec_var, tags, ext) => { RecursiveTagUnion(rec_var, tags, ext) => {
let (union_tags, ext) = type_to_union_tags(subs, rank, pools, cached, tags, ext); let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext);
let content = let content =
Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext));
@ -780,22 +787,22 @@ fn type_to_variable(
} }
} }
let mut arg_vars = Vec::with_capacity(args.len()); let mut arg_vars = Vec::with_capacity_in(args.len(), arena);
for (arg, arg_type) in args { for (arg, arg_type) in args {
let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); let arg_var = type_to_variable(subs, rank, pools, arena, arg_type);
arg_vars.push((arg.clone(), arg_var)); arg_vars.push((arg.clone(), arg_var));
} }
let lambda_set_variables: Vec<_> = lambda_set_variables let lambda_set_variables_it = lambda_set_variables
.iter() .iter()
.map(|ls| type_to_variable(subs, rank, pools, cached, &ls.0)) .map(|ls| type_to_variable(subs, rank, pools, arena, &ls.0));
.collect(); let lambda_set_variables = Vec::from_iter_in(lambda_set_variables_it, arena);
let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, lambda_set_variables); let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, lambda_set_variables);
let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); let alias_var = type_to_variable(subs, rank, pools, arena, alias_type);
let content = Content::Alias(*symbol, arg_vars, alias_var); let content = Content::Alias(*symbol, arg_vars, alias_var);
register(subs, rank, pools, content) register(subs, rank, pools, content)
@ -808,22 +815,22 @@ fn type_to_variable(
lambda_set_variables, lambda_set_variables,
.. ..
} => { } => {
let mut arg_vars = Vec::with_capacity(args.len()); let mut arg_vars = Vec::with_capacity_in(args.len(), arena);
for (arg, arg_type) in args { for (arg, arg_type) in args {
let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); let arg_var = type_to_variable(subs, rank, pools, arena, arg_type);
arg_vars.push((arg.clone(), arg_var)); arg_vars.push((arg.clone(), arg_var));
} }
let lambda_set_variables: Vec<_> = lambda_set_variables let lambda_set_variables_it = lambda_set_variables
.iter() .iter()
.map(|ls| type_to_variable(subs, rank, pools, cached, &ls.0)) .map(|ls| type_to_variable(subs, rank, pools, arena, &ls.0));
.collect(); let lambda_set_variables = Vec::from_iter_in(lambda_set_variables_it, arena);
let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, lambda_set_variables); let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, lambda_set_variables);
let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); let alias_var = type_to_variable(subs, rank, pools, arena, alias_type);
// unify the actual_var with the result var // unify the actual_var with the result var
// this can be used to access the type of the actual_var // this can be used to access the type of the actual_var
@ -853,20 +860,22 @@ fn type_to_variable(
} }
} }
fn type_to_union_tags( fn type_to_union_tags<'a>(
subs: &mut Subs, subs: &mut Subs,
rank: Rank, rank: Rank,
pools: &mut Pools, pools: &mut Pools,
cached: &mut MutMap<Symbol, Variable>, arena: &'a bumpalo::Bump,
tags: &[(TagName, Vec<Type>)], tags: &[(TagName, Vec<Type>)],
ext: &Type, ext: &Type,
) -> (UnionTags, Variable) { ) -> (UnionTags, Variable) {
let mut tag_vars = Vec::with_capacity(tags.len()); use bumpalo::collections::Vec;
let mut tag_argument_vars = Vec::new(); let mut tag_vars = Vec::with_capacity_in(tags.len(), arena);
let mut tag_argument_vars = Vec::with_capacity_in(tags.len(), arena);
for (tag, tag_argument_types) in tags { for (tag, tag_argument_types) in tags {
for arg_type in tag_argument_types { for arg_type in tag_argument_types {
let new_var = type_to_variable(subs, rank, pools, cached, arg_type); let new_var = type_to_variable(subs, rank, pools, arena, arg_type);
tag_argument_vars.push(new_var); tag_argument_vars.push(new_var);
} }
@ -875,7 +884,7 @@ fn type_to_union_tags(
tag_vars.push((tag.clone(), new_slice)); tag_vars.push((tag.clone(), new_slice));
} }
let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext);
let ext = { let ext = {
let (it, ext) = let (it, ext) =
@ -1031,8 +1040,7 @@ fn pool_to_rank_table(
// Sort the variables into buckets by rank. // Sort the variables into buckets by rank.
for &var in young_vars.iter() { for &var in young_vars.iter() {
let rank = subs.get_rank(var); let rank = subs.get_rank_set_mark(var, young_mark);
subs.set_mark(var, young_mark);
debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); debug_assert!(rank.into_usize() < young_rank.into_usize() + 1);
pools.get_mut(rank).push(var); pools.get_mut(rank).push(var);
@ -1245,140 +1253,136 @@ fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable])
/// this is used during the monomorphization process /// this is used during the monomorphization process
pub fn instantiate_rigids(subs: &mut Subs, var: Variable) { pub fn instantiate_rigids(subs: &mut Subs, var: Variable) {
let rank = Rank::NONE; let rank = Rank::NONE;
let mut pools = Pools::default();
instantiate_rigids_help(subs, rank, &mut pools, var); instantiate_rigids_help(subs, rank, var);
// NOTE subs.restore(var) is done at the end of instantiate_rigids_help
} }
fn instantiate_rigids_help( fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
subs: &mut Subs, let mut visited = vec![];
max_rank: Rank, let mut stack = vec![initial];
pools: &mut Pools,
var: Variable,
) -> Variable {
use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*;
let desc = subs.get_without_compacting(var); macro_rules! var_slice {
($variable_subs_slice:expr) => {{
if let Some(copy) = desc.copy.into_variable() { let slice = $variable_subs_slice;
return copy; &subs.variables[slice.slice.start as usize..][..slice.slice.length as usize]
}};
} }
let make_descriptor = |content| Descriptor { while let Some(var) = stack.pop() {
content, visited.push(var);
rank: max_rank,
mark: Mark::NONE,
copy: OptVariable::NONE,
};
let content = desc.content; let desc = subs.get_ref_mut(var);
let copy = var; if desc.copy.is_some() {
continue;
}
pools.get_mut(max_rank).push(copy); desc.rank = Rank::NONE;
desc.mark = Mark::NONE;
desc.copy = OptVariable::from(var);
// Link the original variable to the new variable. This lets us use Content::*;
// avoid making multiple copies of the variable we are instantiating. use FlatType::*;
//
// Need to do this before recursively copying to avoid looping.
subs.set(
var,
Descriptor {
content: content.clone(),
rank: desc.rank,
mark: Mark::NONE,
copy: copy.into(),
},
);
// Now we recursively copy the content of the variable. match &desc.content {
// We have already marked the variable as copied, so we RigidVar(name) => {
// will not repeat this work or crawl this variable again. // what it's all about: convert the rigid var into a flex var
match content { let name = name.clone();
Structure(flat_type) => {
match flat_type { // NOTE: we must write to the mutually borrowed `desc` value here
// using `subs.set` does not work (unclear why, really)
// but get_ref_mut approach saves a lookup, so the weirdness is worth it
desc.content = FlexVar(Some(name));
desc.rank = max_rank;
desc.mark = Mark::NONE;
desc.copy = OptVariable::NONE;
}
FlexVar(_) | Error => (),
RecursionVar { structure, .. } => {
stack.push(*structure);
}
Structure(flat_type) => match flat_type {
Apply(_, args) => { Apply(_, args) => {
for var_index in args.into_iter() { stack.extend(var_slice!(*args));
let var = subs[var_index];
instantiate_rigids_help(subs, max_rank, pools, var);
}
} }
Func(arg_vars, closure_var, ret_var) => { Func(arg_vars, closure_var, ret_var) => {
instantiate_rigids_help(subs, max_rank, pools, ret_var); let arg_vars = *arg_vars;
instantiate_rigids_help(subs, max_rank, pools, closure_var); let ret_var = *ret_var;
let closure_var = *closure_var;
for index in arg_vars.into_iter() { stack.extend(var_slice!(arg_vars));
let var = subs[index];
instantiate_rigids_help(subs, max_rank, pools, var); stack.push(ret_var);
} stack.push(closure_var);
} }
EmptyRecord | EmptyTagUnion | Erroneous(_) => {} EmptyRecord => (),
EmptyTagUnion => (),
Record(fields, ext_var) => { Record(fields, ext_var) => {
for index in fields.iter_variables() { let fields = *fields;
let var = subs[index]; let ext_var = *ext_var;
instantiate_rigids_help(subs, max_rank, pools, var); stack.extend(var_slice!(fields.variables()));
}
instantiate_rigids_help(subs, max_rank, pools, ext_var); stack.push(ext_var);
} }
TagUnion(tags, ext_var) => { TagUnion(tags, ext_var) => {
for (_, index) in tags.iter_all() { let tags = *tags;
let slice = subs[index]; let ext_var = *ext_var;
for var_index in slice {
let var = subs[var_index]; for slice_index in tags.variables() {
instantiate_rigids_help(subs, max_rank, pools, var); let slice = subs.variable_slices[slice_index.start as usize];
} stack.extend(var_slice!(slice));
} }
instantiate_rigids_help(subs, max_rank, pools, ext_var); stack.push(ext_var);
} }
FunctionOrTagUnion(_, _, ext_var) => {
FunctionOrTagUnion(_tag_name, _symbol, ext_var) => { stack.push(*ext_var);
instantiate_rigids_help(subs, max_rank, pools, ext_var);
} }
RecursiveTagUnion(rec_var, tags, ext_var) => { RecursiveTagUnion(rec_var, tags, ext_var) => {
instantiate_rigids_help(subs, max_rank, pools, rec_var); let tags = *tags;
let ext_var = *ext_var;
let rec_var = *rec_var;
for (_, index) in tags.iter_all() { for slice_index in tags.variables() {
let slice = subs[index]; let slice = subs.variable_slices[slice_index.start as usize];
for var_index in slice { stack.extend(var_slice!(slice));
let var = subs[var_index];
instantiate_rigids_help(subs, max_rank, pools, var);
}
} }
instantiate_rigids_help(subs, max_rank, pools, ext_var); stack.push(ext_var);
stack.push(rec_var);
} }
};
}
FlexVar(_) | Error => {} Erroneous(_) => (),
},
Alias(_, args, var) => {
let var = *var;
let args = *args;
RecursionVar { structure, .. } => { stack.extend(var_slice!(args.variables()));
instantiate_rigids_help(subs, max_rank, pools, structure);
}
RigidVar(name) => { stack.push(var);
// what it's all about: convert the rigid var into a flex var
subs.set(copy, make_descriptor(FlexVar(Some(name))));
}
Alias(_symbol, args, real_type_var) => {
for var_index in args.variables().into_iter() {
let var = subs[var_index];
instantiate_rigids_help(subs, max_rank, pools, var);
} }
instantiate_rigids_help(subs, max_rank, pools, real_type_var);
} }
} }
var // we have tracked all visited variables, and can now traverse them
// in one go (without looking at the UnificationTable) and clear the copy field
for var in visited {
let descriptor = subs.get_ref_mut(var);
if descriptor.copy.is_some() {
descriptor.rank = Rank::NONE;
descriptor.mark = Mark::NONE;
descriptor.copy = OptVariable::NONE;
}
}
} }
fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable { fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable {

View file

@ -3813,6 +3813,17 @@ mod solve_expr {
); );
} }
#[test]
fn list_intersperse() {
infer_eq_without_problem(
indoc!(
r#"
List.intersperse
"#
),
"List a, a -> List a",
);
}
#[test] #[test]
fn function_that_captures_nothing_is_not_captured() { fn function_that_captures_nothing_is_not_captured() {
// we should make sure that a function that doesn't capture anything it not itself captured // we should make sure that a function that doesn't capture anything it not itself captured

View file

@ -318,6 +318,29 @@ fn list_drop_at() {
assert_evals_to!("List.dropAt [0] 0", RocList::from_slice(&[]), RocList<i64>); assert_evals_to!("List.dropAt [0] 0", RocList::from_slice(&[]), RocList<i64>);
} }
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn list_intersperse() {
assert_evals_to!(
indoc!(
r#"
List.intersperse [0, 0, 0] 1
"#
),
RocList::from_slice(&[0, 1, 0, 1, 0]),
RocList<i64>
);
assert_evals_to!(
indoc!(
r#"
List.intersperse [] 1
"#
),
RocList::from_slice(&[]),
RocList<i64>
);
}
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
fn list_drop_at_shared() { fn list_drop_at_shared() {
@ -2393,6 +2416,28 @@ fn list_any_empty_with_unknown_element_type() {
assert_evals_to!("List.any [] (\\_ -> True)", false, bool); assert_evals_to!("List.any [] (\\_ -> True)", false, bool);
} }
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn list_all() {
assert_evals_to!("List.all [] (\\e -> e > 3)", true, bool);
assert_evals_to!("List.all [ 1, 2, 3 ] (\\e -> e > 3)", false, bool);
assert_evals_to!("List.all [ 1, 2, 4 ] (\\e -> e > 3)", false, bool);
assert_evals_to!("List.all [ 1, 2, 3 ] (\\e -> e >= 1)", true, bool);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[should_panic(expected = r#"Roc failed with message: "UnresolvedTypeVar"#)]
fn list_all_empty_with_unknown_element_type() {
// Segfaults with invalid memory reference. Running this as a stand-alone
// Roc program, generates the following error message:
//
// Application crashed with message
// UnresolvedTypeVar compiler/mono/src/ir.rs line 3775
// Shutting down
assert_evals_to!("List.all [] (\\_ -> True)", false, bool);
}
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
#[should_panic(expected = r#"Roc failed with message: "invalid ret_layout""#)] #[should_panic(expected = r#"Roc failed with message: "invalid ret_layout""#)]

View file

@ -968,3 +968,24 @@ fn update_the_only_field() {
i64 i64
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
// https://github.com/rtfeldman/roc/issues/1513
fn both_have_unique_fields() {
assert_evals_to!(
indoc!(
r#"
a = { x: 42, y: 43 }
b = { x: 42, z: 44 }
f : { x : I64 }a, { x : I64 }b -> I64
f = \{ x: x1}, { x: x2 } -> x1 + x2
f a b
"#
),
84,
i64
);
}

View file

@ -305,129 +305,95 @@ fn small_str_concat_empty_first_arg() {
); );
} }
// #[test] #[test]
// fn small_str_concat_empty_second_arg() { fn small_str_concat_empty_second_arg() {
// assert_llvm_evals_to!( assert_evals_to!(
// r#"Str.concat "JJJJJJJJJJJJJJJ" """#, r#"Str.concat "JJJJJJJ" """#,
// [ [0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0b1000_0111],
// 0x4a, [u8; 8]
// 0x4a, );
// 0x4a, }
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0b1000_1111
// ],
// [u8; 16]
// );
// }
// #[test] #[test]
// fn small_str_concat_small_to_big() { fn small_str_concat_small_to_big() {
// assert_evals_to!( assert_evals_to!(
// r#"Str.concat "abc" " this is longer than 15 chars""#, r#"Str.concat "abc" " this is longer than 7 chars""#,
// RocStr::from_slice(b"abc this is longer than 15 chars"), RocStr::from_slice(b"abc this is longer than 7 chars"),
// RocStr RocStr
// ); );
// } }
// #[test] #[test]
// fn small_str_concat_small_to_small_staying_small() { fn small_str_concat_small_to_small_staying_small() {
// assert_llvm_evals_to!( assert_evals_to!(
// r#"Str.concat "J" "JJJJJJJJJJJJJJ""#, r#"Str.concat "J" "JJJJJJ""#,
// [ [0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0b1000_0111],
// 0x4a, [u8; 8]
// 0x4a, );
// 0x4a, }
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0x4a,
// 0b1000_1111
// ],
// [u8; 16]
// );
// }
// #[test] #[test]
// fn small_str_concat_small_to_small_overflow_to_big() { fn small_str_concat_small_to_small_overflow_to_big() {
// assert_evals_to!( assert_evals_to!(
// r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#, r#"Str.concat "abcdefg" "hijklmn""#,
// RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"), RocStr::from_slice(b"abcdefghijklmn"),
// RocStr RocStr
// ); );
// } }
// #[test] #[test]
// fn str_concat_empty() { fn str_concat_empty() {
// assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr); assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr);
// } }
// #[test] #[test]
// fn small_str_is_empty() { fn small_str_is_empty() {
// assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool); assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool);
// } }
// #[test] #[test]
// fn big_str_is_empty() { fn big_str_is_empty() {
// assert_evals_to!( assert_evals_to!(
// r#"Str.isEmpty "this is more than 15 chars long""#, r#"Str.isEmpty "this is more than 15 chars long""#,
// false, false,
// bool bool
// ); );
// } }
// #[test] #[test]
// fn empty_str_is_empty() { fn empty_str_is_empty() {
// assert_evals_to!(r#"Str.isEmpty """#, true, bool); assert_evals_to!(r#"Str.isEmpty """#, true, bool);
// } }
// #[test] #[test]
// fn str_starts_with() { fn str_starts_with() {
// assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool); assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool);
// assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool); assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool);
// assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool); assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool); assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool); assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool);
// } }
// #[test] #[test]
// fn str_starts_with_code_point() { fn str_starts_with_code_point() {
// assert_evals_to!( assert_evals_to!(
// &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32), &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32),
// true, true,
// bool bool
// ); );
// assert_evals_to!( assert_evals_to!(
// &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32), &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32),
// false, false,
// bool bool
// ); );
// } }
// #[test] #[test]
// fn str_ends_with() { fn str_ends_with() {
// assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool); assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool);
// assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool); assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool); assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool);
// } }
// #[test] // #[test]
// fn str_count_graphemes_small_str() { // fn str_count_graphemes_small_str() {
@ -448,37 +414,37 @@ fn small_str_concat_empty_first_arg() {
// ); // );
// } // }
// #[test] #[test]
// fn str_starts_with_same_big_str() { fn str_starts_with_same_big_str() {
// assert_evals_to!( assert_evals_to!(
// r#"Str.startsWith "123456789123456789" "123456789123456789""#, r#"Str.startsWith "123456789123456789" "123456789123456789""#,
// true, true,
// bool bool
// ); );
// } }
// #[test] #[test]
// fn str_starts_with_different_big_str() { fn str_starts_with_different_big_str() {
// assert_evals_to!( assert_evals_to!(
// r#"Str.startsWith "12345678912345678910" "123456789123456789""#, r#"Str.startsWith "12345678912345678910" "123456789123456789""#,
// true, true,
// bool bool
// ); );
// } }
// #[test] #[test]
// fn str_starts_with_same_small_str() { fn str_starts_with_same_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool); assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool);
// } }
// #[test] #[test]
// fn str_starts_with_different_small_str() { fn str_starts_with_different_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool); assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool);
// } }
// #[test] #[test]
// fn str_starts_with_false_small_str() { fn str_starts_with_false_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
// } }
// #[test] // #[test]
// fn str_from_int() { // fn str_from_int() {
@ -934,121 +900,239 @@ fn small_str_concat_empty_first_arg() {
// ); // );
// } // }
// #[test] #[test]
// fn str_repeat_small() { fn str_repeat_small() {
// assert_evals_to!( assert_evals_to!(
// indoc!(r#"Str.repeat "Roc" 3"#), indoc!(r#"Str.repeat "Roc" 3"#),
// RocStr::from("RocRocRoc"), RocStr::from("RocRocRoc"),
// RocStr RocStr
// ); );
// } }
// #[test] #[test]
// fn str_repeat_big() { fn str_repeat_big() {
// assert_evals_to!( assert_evals_to!(
// indoc!(r#"Str.repeat "more than 16 characters" 2"#), indoc!(r#"Str.repeat "more than 16 characters" 2"#),
// RocStr::from("more than 16 charactersmore than 16 characters"), RocStr::from("more than 16 charactersmore than 16 characters"),
// RocStr RocStr
// ); );
// } }
// #[test] #[test]
// fn str_repeat_empty_string() { fn str_repeat_empty_string() {
// assert_evals_to!(indoc!(r#"Str.repeat "" 3"#), RocStr::from(""), RocStr); assert_evals_to!(indoc!(r#"Str.repeat "" 3"#), RocStr::from(""), RocStr);
// } }
// #[test] #[test]
// fn str_repeat_zero_times() { fn str_repeat_zero_times() {
// assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr); assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr);
// } }
// #[test] #[test]
// fn str_trim_empty_string() { fn str_trim_empty_string() {
// assert_evals_to!(indoc!(r#"Str.trim """#), RocStr::from(""), RocStr); assert_evals_to!(indoc!(r#"Str.trim """#), RocStr::from(""), RocStr);
// } }
// #[test] #[test]
// fn str_trim_small_blank_string() { fn str_trim_small_blank_string() {
// assert_evals_to!(indoc!(r#"Str.trim " ""#), RocStr::from(""), RocStr); assert_evals_to!(indoc!(r#"Str.trim " ""#), RocStr::from(""), RocStr);
// } }
// #[test] #[test]
// fn str_trim_small_to_small() { fn str_trim_small_to_small() {
// assert_evals_to!( assert_evals_to!(
// indoc!(r#"Str.trim " hello world ""#), indoc!(r#"Str.trim " hello world ""#),
// RocStr::from("hello world"), RocStr::from("hello world"),
// RocStr RocStr
// ); );
// } }
// #[test] #[test]
// fn str_trim_large_to_large_unique() { fn str_trim_large_to_large_unique() {
// assert_evals_to!( assert_evals_to!(
// indoc!(r#"Str.trim (Str.concat " " "hello world from a large string ")"#), indoc!(r#"Str.trim (Str.concat " " "hello world from a large string ")"#),
// RocStr::from("hello world from a large string"), RocStr::from("hello world from a large string"),
// RocStr RocStr
// ); );
// } }
// #[test] #[test]
// fn str_trim_large_to_small_unique() { fn str_trim_large_to_small_unique() {
// assert_evals_to!( assert_evals_to!(
// indoc!(r#"Str.trim (Str.concat " " "hello world ")"#), indoc!(r#"Str.trim (Str.concat " " "hello world ")"#),
// RocStr::from("hello world"), RocStr::from("hello world"),
// RocStr RocStr
// ); );
// } }
// #[test] #[test]
// fn str_trim_large_to_large_shared() { fn str_trim_large_to_large_shared() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// original : Str original : Str
// original = " hello world world " original = " hello world world "
// { trimmed: Str.trim original, original: original } { trimmed: Str.trim original, original: original }
// "# "#
// ), ),
// ( (
// RocStr::from(" hello world world "), RocStr::from(" hello world world "),
// RocStr::from("hello world world"), RocStr::from("hello world world"),
// ), ),
// (RocStr, RocStr) (RocStr, RocStr)
// ); );
// } }
// #[test] #[test]
// fn str_trim_large_to_small_shared() { fn str_trim_large_to_small_shared() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// original : Str original : Str
// original = " hello world " original = " hello world "
// { trimmed: Str.trim original, original: original } { trimmed: Str.trim original, original: original }
// "# "#
// ), ),
// ( (
// RocStr::from(" hello world "), RocStr::from(" hello world "),
// RocStr::from("hello world"), RocStr::from("hello world"),
// ), ),
// (RocStr, RocStr) (RocStr, RocStr)
// ); );
// } }
// #[test] #[test]
// fn str_trim_small_to_small_shared() { fn str_trim_small_to_small_shared() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// original : Str original : Str
// original = " hello world " original = " hello world "
// { trimmed: Str.trim original, original: original } { trimmed: Str.trim original, original: original }
// "# "#
// ), ),
// (RocStr::from(" hello world "), RocStr::from("hello world"),), (RocStr::from(" hello world "), RocStr::from("hello world"),),
// (RocStr, RocStr) (RocStr, RocStr)
// ); );
// } }
#[test]
fn str_trim_left_small_blank_string() {
assert_evals_to!(indoc!(r#"Str.trimLeft " ""#), RocStr::from(""), RocStr);
}
#[test]
fn str_trim_left_small_to_small() {
assert_evals_to!(
indoc!(r#"Str.trimLeft " hello world ""#),
RocStr::from("hello world "),
RocStr
);
}
#[test]
fn str_trim_left_large_to_large_unique() {
assert_evals_to!(
indoc!(r#"Str.trimLeft (Str.concat " " "hello world from a large string ")"#),
RocStr::from("hello world from a large string "),
RocStr
);
}
#[test]
fn str_trim_left_large_to_small_unique() {
assert_evals_to!(
indoc!(r#"Str.trimLeft (Str.concat " " "hello world ")"#),
RocStr::from("hello world "),
RocStr
);
}
#[test]
fn str_trim_right_small_blank_string() {
assert_evals_to!(indoc!(r#"Str.trimRight " ""#), RocStr::from(""), RocStr);
}
#[test]
fn str_trim_right_small_to_small() {
assert_evals_to!(
indoc!(r#"Str.trimRight " hello world ""#),
RocStr::from(" hello world"),
RocStr
);
}
#[test]
fn str_trim_right_large_to_large_unique() {
assert_evals_to!(
indoc!(r#"Str.trimRight (Str.concat " hello world from a large string" " ")"#),
RocStr::from(" hello world from a large string"),
RocStr
);
}
#[test]
fn str_trim_right_large_to_small_unique() {
assert_evals_to!(
indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#),
RocStr::from(" hello world"),
RocStr
);
}
#[test]
fn str_trim_right_large_to_large_shared() {
assert_evals_to!(
indoc!(
r#"
original : Str
original = " hello world world "
{ trimmed: Str.trimRight original, original: original }
"#
),
(
RocStr::from(" hello world world "),
RocStr::from(" hello world world"),
),
(RocStr, RocStr)
);
}
#[test]
fn str_trim_right_large_to_small_shared() {
assert_evals_to!(
indoc!(
r#"
original : Str
original = " hello world "
{ trimmed: Str.trimRight original, original: original }
"#
),
(
RocStr::from(" hello world "),
RocStr::from(" hello world"),
),
(RocStr, RocStr)
);
}
#[test]
fn str_trim_right_small_to_small_shared() {
assert_evals_to!(
indoc!(
r#"
original : Str
original = " hello world "
{ trimmed: Str.trimRight original, original: original }
"#
),
(RocStr::from(" hello world "), RocStr::from(" hello world"),),
(RocStr, RocStr)
);
}

View file

@ -1,4 +1,4 @@
procedure Bool.5 (#Attr.2, #Attr.3): procedure Bool.7 (#Attr.2, #Attr.3):
let Test.11 = lowlevel Eq #Attr.2 #Attr.3; let Test.11 = lowlevel Eq #Attr.2 #Attr.3;
ret Test.11; ret Test.11;
@ -13,7 +13,7 @@ procedure Test.1 (Test.3):
ret Test.12; ret Test.12;
in in
let Test.10 = 5i64; let Test.10 = 5i64;
let Test.9 = CallByName Bool.5 Test.6 Test.10; let Test.9 = CallByName Bool.7 Test.6 Test.10;
jump Test.8 Test.9; jump Test.8 Test.9;
procedure Test.0 (): procedure Test.0 ():

View file

@ -10,4 +10,5 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
ven_ena = { path = "../../vendor/ena" } ven_ena = { path = "../../vendor/ena" }
bumpalo = { version = "3.8.0", features = ["collections"] }
static_assertions = "1.1.0" static_assertions = "1.1.0"

View file

@ -9,10 +9,12 @@ use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey};
// if your changes cause this number to go down, great! // if your changes cause this number to go down, great!
// please change it to the lower number. // please change it to the lower number.
// if it went up, maybe check that the change is really required // if it went up, maybe check that the change is really required
static_assertions::assert_eq_size!([u8; 48], Descriptor); static_assertions::assert_eq_size!([u8; 6 * 8], Descriptor);
static_assertions::assert_eq_size!([u8; 32], Content); static_assertions::assert_eq_size!([u8; 4 * 8], Content);
static_assertions::assert_eq_size!([u8; 24], FlatType); static_assertions::assert_eq_size!([u8; 3 * 8], FlatType);
static_assertions::assert_eq_size!([u8; 48], Problem); static_assertions::assert_eq_size!([u8; 6 * 8], Problem);
static_assertions::assert_eq_size!([u8; 12], UnionTags);
static_assertions::assert_eq_size!([u8; 2 * 8], RecordFields);
#[derive(Clone, Copy, Hash, PartialEq, Eq)] #[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub struct Mark(i32); pub struct Mark(i32);
@ -61,16 +63,7 @@ pub struct Subs {
impl Default for Subs { impl Default for Subs {
fn default() -> Self { fn default() -> Self {
Subs { Subs::new()
utable: Default::default(),
variables: Default::default(),
tag_names: Default::default(),
field_names: Default::default(),
record_fields: Default::default(),
// store an empty slice at the first position
// used for "TagOrFunction"
variable_slices: vec![VariableSubsSlice::default()],
}
} }
} }
@ -79,14 +72,14 @@ impl Default for Subs {
/// The starting position is a u32 which should be plenty /// The starting position is a u32 which should be plenty
/// We limit slices to u16::MAX = 65535 elements /// We limit slices to u16::MAX = 65535 elements
pub struct SubsSlice<T> { pub struct SubsSlice<T> {
start: u32, pub start: u32,
length: u16, pub length: u16,
_marker: std::marker::PhantomData<T>, _marker: std::marker::PhantomData<T>,
} }
/// An index into the Vec<T> of subs /// An index into the Vec<T> of subs
pub struct SubsIndex<T> { pub struct SubsIndex<T> {
start: u32, pub start: u32,
_marker: std::marker::PhantomData<T>, _marker: std::marker::PhantomData<T>,
} }
@ -324,7 +317,8 @@ fn subs_fmt_desc(this: &Descriptor, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
subs_fmt_content(&this.content, subs, f)?; subs_fmt_content(&this.content, subs, f)?;
write!(f, " r: {:?}", &this.rank)?; write!(f, " r: {:?}", &this.rank)?;
write!(f, " m: {:?}", &this.mark) write!(f, " m: {:?}", &this.mark)?;
write!(f, " c: {:?}", &this.copy)
} }
pub struct SubsFmtContent<'a>(pub &'a Content, pub &'a Subs); pub struct SubsFmtContent<'a>(pub &'a Content, pub &'a Subs);
@ -679,11 +673,11 @@ impl Variable {
/// ///
/// This should only ever be called from tests! /// This should only ever be called from tests!
pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self { pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self {
debug_assert!(v <= Self::NUM_RESERVED_VARS as u32); debug_assert!(v >= Self::NUM_RESERVED_VARS as u32);
Variable(v) Variable(v)
} }
pub fn index(&self) -> u32 { pub const fn index(&self) -> u32 {
self.0 self.0
} }
} }
@ -985,21 +979,31 @@ fn define_integer_types(subs: &mut Subs) {
} }
impl Subs { impl Subs {
pub fn new(var_store: VarStore) -> Self { pub fn new() -> Self {
let entries = var_store.next; Self::with_capacity(0)
}
pub fn with_capacity(capacity: usize) -> Self {
let capacity = capacity.max(Variable::NUM_RESERVED_VARS);
let mut subs = Subs { let mut subs = Subs {
utable: UnificationTable::default(), utable: UnificationTable::default(),
..Default::default() variables: Default::default(),
tag_names: Default::default(),
field_names: Default::default(),
record_fields: Default::default(),
// store an empty slice at the first position
// used for "TagOrFunction"
variable_slices: vec![VariableSubsSlice::default()],
}; };
// NOTE the utable does not (currently) have a with_capacity; using this as the next-best thing // NOTE the utable does not (currently) have a with_capacity; using this as the next-best thing
subs.utable.reserve(entries as usize); subs.utable.reserve(capacity);
// TODO There are at least these opportunities for performance optimization here: // TODO There are at least these opportunities for performance optimization here:
// * Making the default flex_var_descriptor be all 0s, so no init step is needed. // * Making the default flex_var_descriptor be all 0s, so no init step is needed.
for _ in 0..entries { for _ in 0..capacity {
subs.utable.new_key(flex_var_descriptor()); subs.utable.new_key(flex_var_descriptor());
} }
@ -1040,7 +1044,14 @@ impl Subs {
subs subs
} }
pub fn new_from_varstore(var_store: VarStore) -> Self {
let entries = var_store.next;
Self::with_capacity(entries as usize)
}
pub fn extend_by(&mut self, entries: usize) { pub fn extend_by(&mut self, entries: usize) {
self.utable.reserve(entries);
for _ in 0..entries { for _ in 0..entries {
self.utable.new_key(flex_var_descriptor()); self.utable.new_key(flex_var_descriptor());
} }
@ -1080,6 +1091,10 @@ impl Subs {
&self.utable.probe_value_ref(key).value &self.utable.probe_value_ref(key).value
} }
pub fn get_ref_mut(&mut self, key: Variable) -> &mut Descriptor {
&mut self.utable.probe_value_ref_mut(key).value
}
pub fn get_rank(&self, key: Variable) -> Rank { pub fn get_rank(&self, key: Variable) -> Rank {
self.utable.probe_value_ref(key).value.rank self.utable.probe_value_ref(key).value.rank
} }
@ -1149,6 +1164,26 @@ impl Subs {
}); });
} }
pub fn modify<F>(&mut self, key: Variable, mapper: F)
where
F: Fn(&mut Descriptor),
{
mapper(self.get_ref_mut(key));
}
pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank {
let l_key = self.utable.get_root_key(key);
let mut rank = Rank::NONE;
self.utable.update_value(l_key, |node| {
node.value.mark = mark;
rank = node.value.rank;
});
rank
}
pub fn equivalent(&mut self, left: Variable, right: Variable) -> bool { pub fn equivalent(&mut self, left: Variable, right: Variable) -> bool {
self.utable.unioned(left, right) self.utable.unioned(left, right)
} }
@ -1192,22 +1227,7 @@ impl Subs {
} }
pub fn restore(&mut self, var: Variable) { pub fn restore(&mut self, var: Variable) {
let desc = self.get(var); restore_help(self, var)
if desc.copy.is_some() {
let content = desc.content;
let desc = Descriptor {
content: content.clone(),
rank: Rank::NONE,
mark: Mark::NONE,
copy: OptVariable::NONE,
};
self.set(var, desc);
restore_content(self, &content);
}
} }
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
@ -1218,6 +1238,10 @@ impl Subs {
self.utable.is_empty() self.utable.is_empty()
} }
pub fn contains(&self, var: Variable) -> bool {
(var.index() as usize) < self.len()
}
pub fn snapshot(&mut self) -> Snapshot<InPlace<Variable>> { pub fn snapshot(&mut self) -> Snapshot<InPlace<Variable>> {
self.utable.snapshot() self.utable.snapshot()
} }
@ -1326,6 +1350,12 @@ impl From<Content> for Descriptor {
} }
} }
static_assertions::assert_eq_size!([u8; 4 * 8], Content);
static_assertions::assert_eq_size!([u8; 4 * 8], (Variable, Option<Lowercase>));
static_assertions::assert_eq_size!([u8; 3 * 8], (Symbol, AliasVariables, Variable));
static_assertions::assert_eq_size!([u8; 12], AliasVariables);
static_assertions::assert_eq_size!([u8; 3 * 8], FlatType);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Content { pub enum Content {
/// A type variable which the user did not name in an annotation, /// A type variable which the user did not name in an annotation,
@ -1347,10 +1377,10 @@ pub enum Content {
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
pub struct AliasVariables { pub struct AliasVariables {
lowercases_start: u32, pub lowercases_start: u32,
variables_start: u32, pub variables_start: u32,
lowercases_len: u16, pub lowercases_len: u16,
variables_len: u16, pub variables_len: u16,
} }
impl AliasVariables { impl AliasVariables {
@ -1466,6 +1496,8 @@ impl Content {
} }
} }
static_assertions::assert_eq_size!([u8; 3 * 8], FlatType);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum FlatType { pub enum FlatType {
Apply(Symbol, VariableSubsSlice), Apply(Symbol, VariableSubsSlice),
@ -1489,7 +1521,7 @@ pub enum Builtin {
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
pub struct VariableSubsSlice { pub struct VariableSubsSlice {
slice: SubsSlice<Variable>, pub slice: SubsSlice<Variable>,
} }
impl VariableSubsSlice { impl VariableSubsSlice {
@ -1813,6 +1845,12 @@ impl RecordFields {
} }
} }
pub const fn variables(&self) -> VariableSubsSlice {
let slice = SubsSlice::new(self.variables_start, self.length);
VariableSubsSlice { slice }
}
pub fn iter_variables(&self) -> impl Iterator<Item = SubsIndex<Variable>> { pub fn iter_variables(&self) -> impl Iterator<Item = SubsIndex<Variable>> {
let slice = SubsSlice::new(self.variables_start, self.length); let slice = SubsSlice::new(self.variables_start, self.length);
slice.into_iter() slice.into_iter()
@ -2701,79 +2739,643 @@ fn get_fresh_var_name(state: &mut ErrorTypeState) -> Lowercase {
name name
} }
fn restore_content(subs: &mut Subs, content: &Content) { fn restore_help(subs: &mut Subs, initial: Variable) {
use Content::*; let mut stack = vec![initial];
use FlatType::*;
match content { let variable_slices = &subs.variable_slices;
FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => (),
Structure(flat_type) => match flat_type { let variables = &subs.variables;
Apply(_, args) => { let var_slice = |variable_subs_slice: VariableSubsSlice| {
for index in args.into_iter() { &variables[variable_subs_slice.slice.start as usize..]
let var = subs[index]; [..variable_subs_slice.slice.length as usize]
subs.restore(var); };
}
}
Func(arg_vars, closure_var, ret_var) => { while let Some(var) = stack.pop() {
for index in arg_vars.into_iter() { let desc = &mut subs.utable.probe_value_ref_mut(var).value;
let var = subs[index];
subs.restore(var); if desc.copy.is_some() {
desc.rank = Rank::NONE;
desc.mark = Mark::NONE;
desc.copy = OptVariable::NONE;
use Content::*;
use FlatType::*;
match &desc.content {
FlexVar(_) | RigidVar(_) | Error => (),
RecursionVar { structure, .. } => {
stack.push(*structure);
} }
subs.restore(*ret_var); Structure(flat_type) => match flat_type {
subs.restore(*closure_var); Apply(_, args) => {
} stack.extend(var_slice(*args));
EmptyRecord => (),
EmptyTagUnion => (),
Record(fields, ext_var) => {
for index in fields.iter_variables() {
let var = subs[index];
subs.restore(var);
}
subs.restore(*ext_var);
}
TagUnion(tags, ext_var) => {
for slice_index in tags.variables() {
let slice = subs[slice_index];
for var_index in slice {
let var = subs[var_index];
subs.restore(var);
} }
}
subs.restore(*ext_var); Func(arg_vars, closure_var, ret_var) => {
} stack.extend(var_slice(*arg_vars));
FunctionOrTagUnion(_, _, ext_var) => {
subs.restore(*ext_var);
}
RecursiveTagUnion(rec_var, tags, ext_var) => { stack.push(*ret_var);
for slice_index in tags.variables() { stack.push(*closure_var);
let slice = subs[slice_index];
for var_index in slice {
let var = subs[var_index];
subs.restore(var);
} }
EmptyRecord => (),
EmptyTagUnion => (),
Record(fields, ext_var) => {
stack.extend(var_slice(fields.variables()));
stack.push(*ext_var);
}
TagUnion(tags, ext_var) => {
for slice_index in tags.variables() {
let slice = variable_slices[slice_index.start as usize];
stack.extend(var_slice(slice));
}
stack.push(*ext_var);
}
FunctionOrTagUnion(_, _, ext_var) => {
stack.push(*ext_var);
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
for slice_index in tags.variables() {
let slice = variable_slices[slice_index.start as usize];
stack.extend(var_slice(slice));
}
stack.push(*ext_var);
stack.push(*rec_var);
}
Erroneous(_) => (),
},
Alias(_, args, var) => {
stack.extend(var_slice(args.variables()));
stack.push(*var);
} }
subs.restore(*ext_var);
subs.restore(*rec_var);
} }
}
Erroneous(_) => (), }
}, }
Alias(_, args, var) => {
for var_index in args.variables().into_iter() { #[derive(Clone, Debug)]
let var = subs[var_index]; pub struct StorageSubs {
subs.restore(var); subs: Subs,
} }
subs.restore(*var); #[derive(Copy, Clone, Debug)]
struct StorageSubsOffsets {
utable: u32,
variables: u32,
tag_names: u32,
field_names: u32,
record_fields: u32,
variable_slices: u32,
}
impl StorageSubs {
pub fn new(subs: Subs) -> Self {
Self { subs }
}
pub fn extend_with_variable(&mut self, source: &mut Subs, variable: Variable) -> Variable {
deep_copy_var_to(source, &mut self.subs, variable)
}
pub fn merge_into(self, target: &mut Subs) -> impl Fn(Variable) -> Variable {
let self_offsets = StorageSubsOffsets {
utable: self.subs.utable.len() as u32,
variables: self.subs.variables.len() as u32,
tag_names: self.subs.tag_names.len() as u32,
field_names: self.subs.field_names.len() as u32,
record_fields: self.subs.record_fields.len() as u32,
variable_slices: self.subs.variable_slices.len() as u32,
};
let offsets = StorageSubsOffsets {
utable: (target.utable.len() - Variable::NUM_RESERVED_VARS) as u32,
variables: target.variables.len() as u32,
tag_names: target.tag_names.len() as u32,
field_names: target.field_names.len() as u32,
record_fields: target.record_fields.len() as u32,
variable_slices: target.variable_slices.len() as u32,
};
// The first Variable::NUM_RESERVED_VARS are the same in every subs,
// so we can skip copying them!
let range = Variable::NUM_RESERVED_VARS..self.subs.utable.len();
// fill new slots with empty values
target.extend_by(range.len());
for i in range {
let variable = Variable(i as u32);
let descriptor = self.subs.get_ref(variable);
debug_assert!(descriptor.copy.is_none());
let new_content = Self::offset_content(&offsets, &descriptor.content);
let new_descriptor = Descriptor {
rank: descriptor.rank,
mark: descriptor.mark,
copy: OptVariable::NONE,
content: new_content,
};
let new_variable = Self::offset_variable(&offsets, variable);
target.set(new_variable, new_descriptor);
}
target.variables.extend(
self.subs
.variables
.iter()
.map(|v| Self::offset_variable(&offsets, *v)),
);
target.variable_slices.extend(
self.subs
.variable_slices
.into_iter()
.map(|v| Self::offset_variable_slice(&offsets, v)),
);
target.tag_names.extend(self.subs.tag_names);
target.field_names.extend(self.subs.field_names);
target.record_fields.extend(self.subs.record_fields);
debug_assert_eq!(
target.utable.len(),
(self_offsets.utable + offsets.utable) as usize
);
debug_assert_eq!(
target.tag_names.len(),
(self_offsets.tag_names + offsets.tag_names) as usize
);
move |v| {
let offsets = offsets;
Self::offset_variable(&offsets, v)
}
}
fn offset_flat_type(offsets: &StorageSubsOffsets, flat_type: &FlatType) -> FlatType {
match flat_type {
FlatType::Apply(symbol, arguments) => {
FlatType::Apply(*symbol, Self::offset_variable_slice(offsets, *arguments))
}
FlatType::Func(arguments, lambda_set, result) => FlatType::Func(
Self::offset_variable_slice(offsets, *arguments),
Self::offset_variable(offsets, *lambda_set),
Self::offset_variable(offsets, *result),
),
FlatType::Record(record_fields, ext) => FlatType::Record(
Self::offset_record_fields(offsets, *record_fields),
Self::offset_variable(offsets, *ext),
),
FlatType::TagUnion(union_tags, ext) => FlatType::TagUnion(
Self::offset_union_tags(offsets, *union_tags),
Self::offset_variable(offsets, *ext),
),
FlatType::FunctionOrTagUnion(tag_name, symbol, ext) => FlatType::FunctionOrTagUnion(
Self::offset_tag_name_index(offsets, *tag_name),
*symbol,
Self::offset_variable(offsets, *ext),
),
FlatType::RecursiveTagUnion(rec, union_tags, ext) => FlatType::RecursiveTagUnion(
Self::offset_variable(offsets, *rec),
Self::offset_union_tags(offsets, *union_tags),
Self::offset_variable(offsets, *ext),
),
FlatType::Erroneous(problem) => FlatType::Erroneous(problem.clone()),
FlatType::EmptyRecord => FlatType::EmptyRecord,
FlatType::EmptyTagUnion => FlatType::EmptyTagUnion,
}
}
fn offset_content(offsets: &StorageSubsOffsets, content: &Content) -> Content {
use Content::*;
match content {
FlexVar(opt_name) => FlexVar(opt_name.clone()),
RigidVar(name) => RigidVar(name.clone()),
RecursionVar {
structure,
opt_name,
} => RecursionVar {
structure: Self::offset_variable(offsets, *structure),
opt_name: opt_name.clone(),
},
Structure(flat_type) => Structure(Self::offset_flat_type(offsets, flat_type)),
Alias(symbol, alias_variables, actual) => Alias(
*symbol,
Self::offset_alias_variables(offsets, *alias_variables),
Self::offset_variable(offsets, *actual),
),
Error => Content::Error,
}
}
fn offset_alias_variables(
offsets: &StorageSubsOffsets,
mut alias_variables: AliasVariables,
) -> AliasVariables {
alias_variables.lowercases_start += offsets.field_names;
alias_variables.variables_start += offsets.variables;
alias_variables
}
fn offset_union_tags(offsets: &StorageSubsOffsets, mut union_tags: UnionTags) -> UnionTags {
union_tags.tag_names_start += offsets.tag_names;
union_tags.variables_start += offsets.variable_slices;
union_tags
}
fn offset_record_fields(
offsets: &StorageSubsOffsets,
mut record_fields: RecordFields,
) -> RecordFields {
record_fields.field_names_start += offsets.field_names;
record_fields.variables_start += offsets.variables;
record_fields.field_types_start += offsets.record_fields;
record_fields
}
fn offset_tag_name_index(
offsets: &StorageSubsOffsets,
mut tag_name: SubsIndex<TagName>,
) -> SubsIndex<TagName> {
tag_name.start += offsets.tag_names;
tag_name
}
fn offset_variable(offsets: &StorageSubsOffsets, variable: Variable) -> Variable {
if variable.index() < Variable::FIRST_USER_SPACE_VAR.index() {
variable
} else {
let new_index = variable.0 + offsets.utable;
Variable(new_index)
}
}
fn offset_variable_slice(
offsets: &StorageSubsOffsets,
mut slice: VariableSubsSlice,
) -> VariableSubsSlice {
slice.slice.start += offsets.variables;
slice
}
}
pub fn deep_copy_var_to(
source: &mut Subs, // mut to set the copy
target: &mut Subs,
var: Variable,
) -> Variable {
let rank = Rank::toplevel();
// capacity based on the false hello world program
let arena = bumpalo::Bump::with_capacity(4 * 1024);
let mut visited = bumpalo::collections::Vec::with_capacity_in(256, &arena);
let copy = deep_copy_var_to_help(&arena, &mut visited, source, target, rank, var);
// we have tracked all visited variables, and can now traverse them
// in one go (without looking at the UnificationTable) and clear the copy field
for var in visited {
let descriptor = source.get_ref_mut(var);
if descriptor.copy.is_some() {
descriptor.rank = Rank::NONE;
descriptor.mark = Mark::NONE;
descriptor.copy = OptVariable::NONE;
}
}
copy
}
fn deep_copy_var_to_help<'a>(
arena: &'a bumpalo::Bump,
visited: &mut bumpalo::collections::Vec<'a, Variable>,
source: &mut Subs,
target: &mut Subs,
max_rank: Rank,
var: Variable,
) -> Variable {
use bumpalo::collections::Vec;
use Content::*;
use FlatType::*;
let desc = source.get_without_compacting(var);
if let Some(copy) = desc.copy.into_variable() {
debug_assert!(target.contains(copy));
return copy;
} else if desc.rank != Rank::NONE {
// DO NOTHING, Fall through
//
// The original deep_copy_var can do
// return var;
//
// but we cannot, because this `var` is in the source, not the target, and we
// should only return variables in the target
}
visited.push(var);
let make_descriptor = |content| Descriptor {
content,
rank: max_rank,
mark: Mark::NONE,
copy: OptVariable::NONE,
};
let copy = target.fresh_unnamed_flex_var();
// Link the original variable to the new variable. This lets us
// avoid making multiple copies of the variable we are instantiating.
//
// Need to do this before recursively copying to avoid looping.
source.modify(var, |descriptor| {
descriptor.mark = Mark::NONE;
descriptor.copy = copy.into();
});
// Now we recursively copy the content of the variable.
// We have already marked the variable as copied, so we
// will not repeat this work or crawl this variable again.
match desc.content {
Structure(flat_type) => {
let new_flat_type = match flat_type {
Apply(symbol, args) => {
let mut new_args = Vec::with_capacity_in(args.len(), arena);
for index in args.into_iter() {
let var = source[index];
new_args.push(deep_copy_var_to_help(
arena, visited, source, target, max_rank, var,
));
}
let arg_vars = VariableSubsSlice::insert_into_subs(target, new_args);
Apply(symbol, arg_vars)
}
Func(arg_vars, closure_var, ret_var) => {
let new_ret_var =
deep_copy_var_to_help(arena, visited, source, target, max_rank, ret_var);
let new_closure_var = deep_copy_var_to_help(
arena,
visited,
source,
target,
max_rank,
closure_var,
);
let mut new_arg_vars = Vec::with_capacity_in(arg_vars.len(), arena);
for index in arg_vars.into_iter() {
let var = source[index];
let copy_var =
deep_copy_var_to_help(arena, visited, source, target, max_rank, var);
new_arg_vars.push(copy_var);
}
let arg_vars = VariableSubsSlice::insert_into_subs(target, new_arg_vars);
Func(arg_vars, new_closure_var, new_ret_var)
}
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
Record(fields, ext_var) => {
let record_fields = {
let mut new_vars = Vec::with_capacity_in(fields.len(), arena);
for index in fields.iter_variables() {
let var = source[index];
let copy_var = deep_copy_var_to_help(
arena, visited, source, target, max_rank, var,
);
new_vars.push(copy_var);
}
let field_names_start = target.field_names.len() as u32;
let variables_start = target.variables.len() as u32;
let field_types_start = target.record_fields.len() as u32;
let mut length = 0;
for ((i1, _, i3), var) in fields.iter_all().zip(new_vars) {
let record_field = source[i3].map(|_| var);
target.field_names.push(source[i1].clone());
target.record_fields.push(record_field.map(|_| ()));
target.variables.push(*record_field.as_inner());
length += 1;
}
RecordFields {
length,
field_names_start,
variables_start,
field_types_start,
}
};
Record(
record_fields,
deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var),
)
}
TagUnion(tags, ext_var) => {
let new_ext =
deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var);
let mut new_variable_slices = Vec::with_capacity_in(tags.len(), arena);
let mut new_variables = Vec::with_capacity_in(tags.len(), arena);
for index in tags.variables() {
let slice = source[index];
for var_index in slice {
let var = source[var_index];
let new_var = deep_copy_var_to_help(
arena, visited, source, target, max_rank, var,
);
new_variables.push(new_var);
}
let new_slice =
VariableSubsSlice::insert_into_subs(target, new_variables.drain(..));
new_variable_slices.push(new_slice);
}
let new_variables = {
let start = target.variable_slices.len() as u32;
let length = new_variable_slices.len() as u16;
target.variable_slices.extend(new_variable_slices);
SubsSlice::new(start, length)
};
let new_tag_names = {
let tag_names = tags.tag_names();
let slice = &source.tag_names[tag_names.start as usize..]
[..tag_names.length as usize];
let start = target.tag_names.len() as u32;
let length = tag_names.len() as u16;
target.tag_names.extend(slice.iter().cloned());
SubsSlice::new(start, length)
};
let union_tags = UnionTags::from_slices(new_tag_names, new_variables);
TagUnion(union_tags, new_ext)
}
FunctionOrTagUnion(tag_name, symbol, ext_var) => {
let new_tag_name = SubsIndex::new(target.tag_names.len() as u32);
target.tag_names.push(source[tag_name].clone());
FunctionOrTagUnion(
new_tag_name,
symbol,
deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var),
)
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
let mut new_variable_slices = Vec::with_capacity_in(tags.len(), arena);
let mut new_variables = Vec::with_capacity_in(tags.len(), arena);
for index in tags.variables() {
let slice = source[index];
for var_index in slice {
let var = source[var_index];
let new_var = deep_copy_var_to_help(
arena, visited, source, target, max_rank, var,
);
new_variables.push(new_var);
}
let new_slice =
VariableSubsSlice::insert_into_subs(target, new_variables.drain(..));
new_variable_slices.push(new_slice);
}
let new_variables = {
let start = target.variable_slices.len() as u32;
let length = new_variable_slices.len() as u16;
target.variable_slices.extend(new_variable_slices);
SubsSlice::new(start, length)
};
let new_tag_names = {
let tag_names = tags.tag_names();
let slice = &source.tag_names[tag_names.start as usize..]
[..tag_names.length as usize];
let start = target.tag_names.len() as u32;
let length = tag_names.len() as u16;
target.tag_names.extend(slice.iter().cloned());
SubsSlice::new(start, length)
};
let union_tags = UnionTags::from_slices(new_tag_names, new_variables);
let new_ext =
deep_copy_var_to_help(arena, visited, source, target, max_rank, ext_var);
let new_rec_var =
deep_copy_var_to_help(arena, visited, source, target, max_rank, rec_var);
RecursiveTagUnion(new_rec_var, union_tags, new_ext)
}
};
target.set(copy, make_descriptor(Structure(new_flat_type)));
copy
}
FlexVar(_) | Error => copy,
RecursionVar {
opt_name,
structure,
} => {
let new_structure =
deep_copy_var_to_help(arena, visited, source, target, max_rank, structure);
debug_assert!((new_structure.index() as usize) < target.len());
target.set(
copy,
make_descriptor(RecursionVar {
opt_name,
structure: new_structure,
}),
);
copy
}
RigidVar(name) => {
target.set(copy, make_descriptor(FlexVar(Some(name))));
copy
}
Alias(symbol, mut args, real_type_var) => {
let mut new_vars = Vec::with_capacity_in(args.variables().len(), arena);
for var_index in args.variables() {
let var = source[var_index];
let new_var = deep_copy_var_to_help(arena, visited, source, target, max_rank, var);
new_vars.push(new_var);
}
args.replace_variables(target, new_vars);
let lowercases = &source.field_names[args.lowercases_start as usize..]
[..args.lowercases_len as usize];
args.lowercases_start = target.field_names.len() as u32;
target.field_names.extend(lowercases.iter().cloned());
let new_real_type_var =
deep_copy_var_to_help(arena, visited, source, target, max_rank, real_type_var);
let new_content = Alias(symbol, args, new_real_type_var);
target.set(copy, make_descriptor(new_content));
copy
} }
} }
} }

View file

@ -3,6 +3,7 @@ use crate::subs::{
GetSubsSlice, RecordFields, Subs, UnionTags, VarStore, Variable, VariableSubsSlice, GetSubsSlice, RecordFields, Subs, UnionTags, VarStore, Variable, VariableSubsSlice,
}; };
use roc_collections::all::{ImMap, ImSet, Index, MutSet, SendMap}; use roc_collections::all::{ImMap, ImSet, Index, MutSet, SendMap};
use roc_module::called_via::CalledVia;
use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -1134,7 +1135,7 @@ pub enum Reason {
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone)]
pub enum Category { pub enum Category {
Lookup(Symbol), Lookup(Symbol),
CallResult(Option<Symbol>), CallResult(Option<Symbol>, CalledVia),
LowLevelOpResult(LowLevel), LowLevelOpResult(LowLevel),
ForeignCall, ForeignCall,
TagApply { TagApply {

View file

@ -133,12 +133,14 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
// println!("\n --- \n"); // println!("\n --- \n");
// dbg!(ctx.second, type2); // dbg!(ctx.second, type2);
// println!("\n --------------- \n"); // println!("\n --------------- \n");
let content_1 = subs.get(ctx.first).content;
let content_2 = subs.get(ctx.second).content;
println!( println!(
"{:?} {:?} ~ {:?} {:?}", "{:?} {:?} ~ {:?} {:?}",
ctx.first, ctx.first,
subs.get(ctx.first).content, roc_types::subs::SubsFmtContent(&content_1, subs),
ctx.second, ctx.second,
subs.get(ctx.second).content roc_types::subs::SubsFmtContent(&content_2, subs),
); );
} }
match &ctx.first_desc.content { match &ctx.first_desc.content {

View file

@ -5,7 +5,7 @@ Unlike most editors, we use projectional or structural editing to edit the [Abst
## Getting started ## Getting started
- Install the compiler, see [here](../BUILDING_FROM_SOURCE). - Install the compiler, see [here](../BUILDING_FROM_SOURCE.md).
- Run the following from the roc folder: - Run the following from the roc folder:
``` ```

View file

@ -501,7 +501,7 @@ impl<'a> EdModel<'a> {
}; };
let arena = Bump::new(); let arena = Bump::new();
let mut subs = Subs::new(var_store); let mut subs = Subs::new_from_varstore(var_store);
for (var, name) in rigid_variables { for (var, name) in rigid_variables {
subs.rigid_var(var, name); subs.rigid_var(var, name);

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -6,6 +6,7 @@ use roc_problem::can::{BadPattern, FloatErrorKind, IntErrorKind, Problem, Runtim
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use std::path::PathBuf; use std::path::PathBuf;
use crate::error::r#type::suggest;
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
use ven_pretty::DocAllocator; use ven_pretty::DocAllocator;
@ -874,16 +875,36 @@ fn pretty_runtime_error<'b>(
module_name, module_name,
ident, ident,
region, region,
exposed_values,
} => { } => {
let mut suggestions = suggest::sort(ident.as_ref(), exposed_values);
suggestions.truncate(4);
let did_you_mean = if suggestions.is_empty() {
alloc.concat(vec![
alloc.reflow("In fact, it looks like "),
alloc.module_name(module_name.clone()),
alloc.reflow(" doesn't expose any values!"),
])
} else {
let qualified_suggestions = suggestions
.into_iter()
.map(|v| alloc.string(module_name.to_string() + "." + v.as_str()));
alloc.stack(vec![
alloc.reflow("Did you mean one of these?"),
alloc.vcat(qualified_suggestions).indent(4),
])
};
doc = alloc.stack(vec![ doc = alloc.stack(vec![
alloc.concat(vec![ alloc.concat(vec![
alloc.reflow("The "), alloc.reflow("The "),
alloc.module_name(module_name), alloc.module_name(module_name),
alloc.reflow(" module does not expose a "), alloc.reflow(" module does not expose `"),
alloc.string(ident.to_string()), alloc.string(ident.to_string()),
alloc.reflow(" value:"), alloc.reflow("`:"),
]), ]),
alloc.region(region), alloc.region(region),
did_you_mean,
]); ]);
title = VALUE_NOT_EXPOSED; title = VALUE_NOT_EXPOSED;
@ -1176,8 +1197,6 @@ fn not_found<'b>(
thing: &'b str, thing: &'b str,
options: MutSet<Box<str>>, options: MutSet<Box<str>>,
) -> RocDocBuilder<'b> { ) -> RocDocBuilder<'b> {
use crate::error::r#type::suggest;
let mut suggestions = suggest::sort( let mut suggestions = suggest::sort(
name.as_inline_str().as_str(), name.as_inline_str().as_str(),
options.iter().map(|v| v.as_ref()).collect(), options.iter().map(|v| v.as_ref()).collect(),
@ -1225,8 +1244,6 @@ fn module_not_found<'b>(
name: &ModuleName, name: &ModuleName,
options: MutSet<Box<str>>, options: MutSet<Box<str>>,
) -> RocDocBuilder<'b> { ) -> RocDocBuilder<'b> {
use crate::error::r#type::suggest;
let mut suggestions = let mut suggestions =
suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect()); suggest::sort(name.as_str(), options.iter().map(|v| v.as_ref()).collect());
suggestions.truncate(4); suggestions.truncate(4);

View file

@ -1,5 +1,6 @@
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{Index, MutSet, SendMap}; use roc_collections::all::{Index, MutSet, SendMap};
use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -1043,13 +1044,26 @@ fn add_category<'b>(
alloc.record_field(field.to_owned()), alloc.record_field(field.to_owned()),
alloc.text(" is a:"), alloc.text(" is a:"),
]), ]),
CallResult(
CallResult(Some(symbol)) => alloc.concat(vec![ Some(_),
CalledVia::BinOp(
BinOp::Equals
| BinOp::NotEquals
| BinOp::LessThan
| BinOp::GreaterThan
| BinOp::LessThanOrEq
| BinOp::GreaterThanOrEq,
),
) => alloc.concat(vec![alloc.text("This comparison produces:")]),
CallResult(Some(_), CalledVia::StringInterpolation) => {
alloc.concat(vec![this_is, alloc.text(" a string of type:")])
}
CallResult(Some(symbol), _) => alloc.concat(vec![
alloc.text("This "), alloc.text("This "),
alloc.symbol_foreign_qualified(*symbol), alloc.symbol_foreign_qualified(*symbol),
alloc.text(" call produces:"), alloc.text(" call produces:"),
]), ]),
CallResult(None) => alloc.concat(vec![this_is, alloc.text(":")]), CallResult(None, _) => alloc.concat(vec![this_is, alloc.text(":")]),
LowLevelOpResult(op) => { LowLevelOpResult(op) => {
panic!( panic!(
"Compiler bug: invalid return type from low-level op {:?}", "Compiler bug: invalid return type from low-level op {:?}",

View file

@ -4,3 +4,35 @@
pub mod error; pub mod error;
pub mod report; pub mod report;
/// `internal_error!` should be used whenever a compiler invariant is broken.
/// It is a wrapper around panic that tells the user to file a bug.
/// This should only be used in cases where there would be a compiler bug and the user can't fix it.
/// If there is simply an unimplemented feature, please use `unimplemented!`
/// If there is a user error, please use roc_reporting to print a nice error message.
#[macro_export]
macro_rules! internal_error {
($($arg:tt)*) => ({
eprintln!("An internal compiler expectation was broken.");
eprintln!("This is definitely a compiler bug.");
// TODO: update this to the new bug template.
eprintln!("Please file an issue here: https://github.com/rtfeldman/roc/issues/new/choose");
#[allow(clippy::panic)] {
panic!($($arg)*);
}
})
}
/// `user_error!` should only ever be used temporarily.
/// It is a way to document locations where we do not yet have nice error reporting.
/// All cases of `user_error!` should eventually be replaced with pretty error printing using roc_reporting.
#[macro_export]
macro_rules! user_error {
($($arg:tt)*) => ({
eprintln!("We ran into an issue while compiling your code.");
eprintln!("Sadly, we don't havs a pretty error message for this case yet.");
eprintln!("If you can't figure out the problem from the context below, please reach out at: https://roc.zulipchat.com/");
eprintln!($($arg)*);
std::process::exit(1);
})
}

View file

@ -353,7 +353,7 @@ impl<'a> RocDocAllocator<'a> {
pub fn binop( pub fn binop(
&'a self, &'a self,
content: roc_module::operator::BinOp, content: roc_module::called_via::BinOp,
) -> DocBuilder<'a, Self, Annotation> { ) -> DocBuilder<'a, Self, Annotation> {
self.text(content.to_string()).annotate(Annotation::BinOp) self.text(content.to_string()).annotate(Annotation::BinOp)
} }

View file

@ -66,7 +66,7 @@ mod test_reporting {
problems: can_problems, problems: can_problems,
.. ..
} = can_expr(arena, expr_src)?; } = can_expr(arena, expr_src)?;
let mut subs = Subs::new(var_store); let mut subs = Subs::new_from_varstore(var_store);
for (var, name) in output.introduced_variables.name_by_var { for (var, name) in output.introduced_variables.name_by_var {
subs.rigid_var(var, name); subs.rigid_var(var, name);
@ -298,17 +298,24 @@ mod test_reporting {
report_problem_as( report_problem_as(
indoc!( indoc!(
r#" r#"
List.foobar 1 2 List.isempty 1 2
"# "#
), ),
indoc!( indoc!(
r#" r#"
NOT EXPOSED NOT EXPOSED
The List module does not expose a foobar value: The List module does not expose `isempty`:
1 List.foobar 1 2 1 List.isempty 1 2
^^^^^^^^^^^ ^^^^^^^^^^^^
Did you mean one of these?
List.isEmpty
List.set
List.get
List.keepIf
"# "#
), ),
) )
@ -547,7 +554,35 @@ mod test_reporting {
baz baz
Nat Nat
Str Str
U8 Err
"#
),
)
}
#[test]
fn lowercase_primitive_tag_bool() {
report_problem_as(
indoc!(
r#"
if true then 1 else 2
"#
),
indoc!(
r#"
UNRECOGNIZED NAME
I cannot find a `true` value
1 if true then 1 else 2
^^^^
Did you mean one of these?
True
Str
Num
Err
"# "#
), ),
) )
@ -1950,10 +1985,10 @@ mod test_reporting {
Did you mean one of these? Did you mean one of these?
Ok
U8 U8
f f
I8 I8
F64
"# "#
), ),
) )
@ -5551,6 +5586,82 @@ mod test_reporting {
) )
} }
#[test]
// https://github.com/rtfeldman/roc/issues/1714
fn interpolate_concat_is_transparent_1714() {
report_problem_as(
indoc!(
r#"
greeting = "Privet"
if True then 1 else "\(greeting), World!"
"#,
),
indoc!(
r#"
TYPE MISMATCH
This `if` has an `else` branch with a different type from its `then` branch:
3 if True then 1 else "\(greeting), World!"
^^^^^^^^^^^^^^^^^^^^^
The `else` branch is a string of type:
Str
but the `then` branch has the type:
Num a
I need all branches in an `if` to have the same type!
"#
),
)
}
macro_rules! comparison_binop_transparency_tests {
($($op:expr, $name:ident),* $(,)?) => {
$(
#[test]
fn $name() {
report_problem_as(
&format!(r#"if True then "abc" else 1 {} 2"#, $op),
&format!(
r#"── TYPE MISMATCH ───────────────────────────────────────────────────────────────
This `if` has an `else` branch with a different type from its `then` branch:
1 if True then "abc" else 1 {} 2
^^{}^^
This comparison produces:
Bool
but the `then` branch has the type:
Str
I need all branches in an `if` to have the same type!
"#,
$op, "^".repeat($op.len())
),
)
}
)*
}
}
comparison_binop_transparency_tests! {
"<", lt_binop_is_transparent,
">", gt_binop_is_transparent,
"==", eq_binop_is_transparent,
"!=", neq_binop_is_transparent,
"<=", leq_binop_is_transparent,
">=", geq_binop_is_transparent,
}
#[test] #[test]
fn keyword_record_field_access() { fn keyword_record_field_access() {
report_problem_as( report_problem_as(
@ -5596,10 +5707,17 @@ mod test_reporting {
r#" r#"
NOT EXPOSED NOT EXPOSED
The Num module does not expose a if value: The Num module does not expose `if`:
1 Num.if 1 Num.if
^^^^^^ ^^^^^^
Did you mean one of these?
Num.sin
Num.div
Num.abs
Num.neg
"# "#
), ),
) )
@ -5802,8 +5920,8 @@ mod test_reporting {
Nat Nat
Str Str
Err
U8 U8
F64
"# "#
), ),
) )

View file

@ -6,8 +6,6 @@ use core::{fmt, mem, ptr, slice};
// A list of C functions that are being imported // A list of C functions that are being imported
extern "C" { extern "C" {
pub fn printf(format: *const u8, ...) -> i32;
pub fn roc_alloc(size: usize, alignment: u32) -> *mut c_void; pub fn roc_alloc(size: usize, alignment: u32) -> *mut c_void;
pub fn roc_realloc( pub fn roc_realloc(
ptr: *mut c_void, ptr: *mut c_void,

View file

@ -15,7 +15,10 @@ type Key<S: UnificationStore> = <S as UnificationStore>::Key;
/// backing store types. The most common such type is `InPlace`, /// backing store types. The most common such type is `InPlace`,
/// which indicates a standard, mutable unification table. /// which indicates a standard, mutable unification table.
pub trait UnificationStore: pub trait UnificationStore:
ops::Index<usize, Output = VarValue<Key<Self>>> + Clone + Default ops::Index<usize, Output = VarValue<Key<Self>>>
+ ops::IndexMut<usize, Output = VarValue<Key<Self>>>
+ Clone
+ Default
{ {
type Key: UnifyKey<Value = Self::Value>; type Key: UnifyKey<Value = Self::Value>;
type Value: Clone + Debug; type Value: Clone + Debug;
@ -141,6 +144,15 @@ where
} }
} }
impl<K> ops::IndexMut<usize> for InPlace<K>
where
K: UnifyKey,
{
fn index_mut(&mut self, index: usize) -> &mut VarValue<K> {
&mut self.values[index]
}
}
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
struct Delegate<K>(PhantomData<K>); struct Delegate<K>(PhantomData<K>);

View file

@ -284,6 +284,12 @@ impl<S: UnificationStore> UnificationTable<S> {
&self.values[key.index() as usize] &self.values[key.index() as usize]
} }
/// Obtains the current value for a particular key.
/// Not for end-users; they can use `probe_value`.
pub fn value_mut(&mut self, key: S::Key) -> &mut VarValue<S::Key> {
&mut self.values[key.index() as usize]
}
/// Find the root node for `vid`. This uses the standard /// Find the root node for `vid`. This uses the standard
/// union-find algorithm with path compression: /// union-find algorithm with path compression:
/// <http://en.wikipedia.org/wiki/Disjoint-set_data_structure>. /// <http://en.wikipedia.org/wiki/Disjoint-set_data_structure>.
@ -451,6 +457,17 @@ where
self.value(id) self.value(id)
} }
/// Returns the current value for the given key. If the key has
/// been union'd, this will give the value from the current root.
pub fn probe_value_ref_mut<K1>(&mut self, id: K1) -> &mut VarValue<K>
where
K1: Into<K>,
{
let id = id.into();
let id = self.get_root_key_without_compacting(id);
self.value_mut(id)
}
/// This is for a debug_assert! in solve() only. Do not use it elsewhere! /// This is for a debug_assert! in solve() only. Do not use it elsewhere!
pub fn probe_value_without_compacting<K1>(&self, id: K1) -> V pub fn probe_value_without_compacting<K1>(&self, id: K1) -> V
where where