Merge branch 'trunk' into record-bool-tests

This commit is contained in:
lrosa007 2020-11-11 00:38:37 -05:00
commit 2cc4f1767e
41 changed files with 2254 additions and 902 deletions

View File

@ -80,7 +80,7 @@ If MacOS and using a version >= 10.15:
You may prefer to setup up the volume manually by following nix documentation.
> You my need to restart your terminal
> You may need to restart your terminal
### Usage
@ -96,6 +96,32 @@ You should be in a shell with everything needed to build already installed. Next
You should be in a repl now. Have fun!
### Editor
When you want to run the editor from Ubuntu inside nix you need to install [nixGL](https://github.com/guibou/nixGL) as well:
```bash
nix-shell
git clone https://github.com/guibou/nixGL
cd nixGL
```
If you have an Nvidia graphics card, run:
```
nix-env -f ./ -iA nixVulkanNvidia
```
If you have integrated Intel graphics, run:
```
nix-env -f ./ -iA nixVulkanIntel
```
Check the [nixGL repo](https://github.com/guibou/nixGL) for other configurations.
Now you should be able to run the editor:
```bash
cd roc
nixVulkanNvidia cargo run edit `# replace Nvidia with the config you chose in the previous step`
```
## Troubleshooting
Create an issue if you run into problems not listed here.

1
Cargo.lock generated
View File

@ -2524,6 +2524,7 @@ dependencies = [
"roc_solve",
"roc_types",
"roc_unify",
"tempfile",
]
[[package]]

View File

@ -47,7 +47,8 @@ pub fn build_file(
let app_o_file = roc_file_path.with_file_name("roc_app.o");
let buf = &mut String::with_capacity(1024);
for (module_id, module_timing) in loaded.timings.iter() {
let mut it = loaded.timings.iter().peekable();
while let Some((module_id, module_timing)) = it.next() {
let module_name = loaded.interns.module_name(*module_id);
buf.push_str(" ");
@ -60,9 +61,23 @@ pub fn build_file(
report_timing(buf, "Canonicalize", module_timing.canonicalize);
report_timing(buf, "Constrain", module_timing.constrain);
report_timing(buf, "Solve", module_timing.solve);
report_timing(
buf,
"Find Specializations",
module_timing.find_specializations,
);
report_timing(
buf,
"Make Specializations",
module_timing.make_specializations,
);
report_timing(buf, "Other", module_timing.other());
buf.push('\n');
report_timing(buf, "Total", module_timing.total());
if it.peek().is_some() {
buf.push('\n');
}
}
println!(

View File

@ -3,7 +3,7 @@ use roc_std::RocStr;
use std::str;
extern "C" {
#[link_name = "main_1_exposed"]
#[link_name = "Main_main_1_exposed"]
fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
}

View File

@ -3,7 +3,7 @@ use roc_std::RocStr;
use std::str;
extern "C" {
#[link_name = "main_1_exposed"]
#[link_name = "Main_main_1_exposed"]
fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
}

View File

@ -70,7 +70,7 @@ pub fn run_cmd(cmd_name: &str, args: &[&str]) -> Out {
let output = cmd
.output()
.expect(&format!("failed to execute cmd `{}` in CLI test", cmd_name));
.unwrap_or_else(|_| panic!("failed to execute cmd `{}` in CLI test", cmd_name));
Out {
stdout: String::from_utf8(output.stdout).unwrap(),
@ -263,12 +263,12 @@ pub fn repl_eval(input: &str) -> Out {
// Evaluate the expression
stdin
.write_all("\n".as_bytes())
.write_all(b"\n")
.expect("Failed to write newline to stdin");
// Gracefully exit the repl
stdin
.write_all(":exit\n".as_bytes())
.write_all(b":exit\n")
.expect("Failed to write :exit to stdin");
}

View File

@ -227,15 +227,7 @@ fn can_annotation_help(
// instantiate variables
actual.substitute(&substitutions);
// Type::Alias(symbol, vars, Box::new(actual))
let actual_var = var_store.fresh();
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
Type::HostExposedAlias {
name: symbol,
arguments: vars,
actual: Box::new(actual),
actual_var,
}
Type::Alias(symbol, vars, Box::new(actual))
}
None => {
let mut args = Vec::new();
@ -369,13 +361,17 @@ fn can_annotation_help(
// Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
let actual_var = var_store.fresh();
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
Type::HostExposedAlias {
name: symbol,
arguments: vars,
actual: Box::new(alias.typ.clone()),
actual_var,
if vars.is_empty() && env.home == symbol.module_id() {
let actual_var = var_store.fresh();
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
Type::HostExposedAlias {
name: symbol,
arguments: vars,
actual: Box::new(alias.typ.clone()),
actual_var,
}
} else {
Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
}
}
_ => {

View File

@ -251,8 +251,8 @@ fn num_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
)
}
/// Num a, Num a -> Bool
fn num_bool_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
/// Num a, Num a -> b
fn num_num_other_binop(symbol: Symbol, var_store: &mut VarStore, op: LowLevel) -> Def {
let num_var = var_store.fresh();
let bool_var = var_store.fresh();
let body = RunLowLevel {
@ -381,27 +381,27 @@ fn num_mul(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.isGt : Num a, Num a -> Bool
fn num_gt(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_bool_binop(symbol, var_store, LowLevel::NumGt)
num_num_other_binop(symbol, var_store, LowLevel::NumGt)
}
/// Num.isGte : Num a, Num a -> Bool
fn num_gte(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_bool_binop(symbol, var_store, LowLevel::NumGte)
num_num_other_binop(symbol, var_store, LowLevel::NumGte)
}
/// Num.isLt : Num a, Num a -> Bool
fn num_lt(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_bool_binop(symbol, var_store, LowLevel::NumLt)
num_num_other_binop(symbol, var_store, LowLevel::NumLt)
}
/// Num.isLte : Num a, Num a -> Num a
/// Num.isLte : Num a, Num a -> Bool
fn num_lte(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_bool_binop(symbol, var_store, LowLevel::NumLte)
num_num_other_binop(symbol, var_store, LowLevel::NumLte)
}
/// Num.compare : Num a, Num a -> [ LT, EQ, GT ]
fn num_compare(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_bool_binop(symbol, var_store, LowLevel::NumCompare)
num_num_other_binop(symbol, var_store, LowLevel::NumCompare)
}
/// Num.sin : Float -> Float

View File

@ -184,17 +184,11 @@ pub fn canonicalize_defs<'a>(
let mut can_ann =
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
// all referenced symbols in an alias must be symbols
output
.references
.referenced_aliases
.extend(can_ann.aliases.keys().copied());
// if an alias definition uses an alias, the used alias is referenced
output
.references
.lookups
.extend(can_ann.aliases.keys().copied());
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
}
let mut can_vars: Vec<Located<(Lowercase, Variable)>> =
Vec::with_capacity(vars.len());

View File

@ -304,6 +304,13 @@ fn fix_values_captured_in_closure_defs(
defs: &mut Vec<crate::def::Def>,
no_capture_symbols: &mut MutSet<Symbol>,
) {
// recursive defs cannot capture each other
for def in defs.iter() {
no_capture_symbols.extend(crate::pattern::symbols_from_pattern(&def.loc_pattern.value));
}
// TODO mutually recursive functions should both capture the union of both their capture sets
for def in defs.iter_mut() {
fix_values_captured_in_closure_def(def, no_capture_symbols);
}

View File

@ -112,7 +112,7 @@ pub fn constrain_expr(
let stored_con = Eq(
Type::Variable(*record_var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -391,7 +391,7 @@ pub fn constrain_expr(
Eq(
Type::Variable(*fn_var),
NoExpectation(fn_type),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
),
closure_constraint,
@ -469,7 +469,7 @@ pub fn constrain_expr(
let ast_con = Eq(
Type::Variable(*branch_var),
NoExpectation(tipe),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -521,7 +521,7 @@ pub fn constrain_expr(
branch_cons.push(Eq(
Type::Variable(*branch_var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
));
branch_cons.push(else_con);
@ -556,7 +556,7 @@ pub fn constrain_expr(
let ast_con = Eq(
Type::Variable(*expr_var),
expected.clone(),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
constraints.push(ast_con);
@ -743,7 +743,7 @@ pub fn constrain_expr(
Eq(
Type::Variable(*var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
loc_ret.region,
),
]),
@ -761,7 +761,7 @@ pub fn constrain_expr(
Eq(
Type::Variable(*var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
loc_ret.region,
),
]),
@ -805,7 +805,7 @@ pub fn constrain_expr(
let ast_con = Eq(
Type::Variable(*variant_var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -1006,10 +1006,10 @@ pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint {
match decl {
Declaration::Declare(def) | Declaration::Builtin(def) => {
constraint = exists(Vec::new(), constrain_def(&env, def, constraint));
constraint = constrain_def(&env, def, constraint);
}
Declaration::DeclareRec(defs) => {
constraint = exists(Vec::new(), constrain_recursive_defs(&env, defs, constraint));
constraint = constrain_recursive_defs(&env, defs, constraint);
}
Declaration::InvalidCycle(_, _) => {
// invalid cycles give a canonicalization error. we skip them here.
@ -1090,7 +1090,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
pattern_state.constraints.push(Eq(
expr_type,
annotation_expected.clone(),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
Region::span_across(&annotation.region, &def.loc_expr.region),
));
@ -1173,7 +1173,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
state.constraints.push(Constraint::Eq(
pattern_type.clone(),
Expected::NoExpectation(loc_ann.clone()),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
loc_pattern.region,
));
@ -1218,7 +1218,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
Eq(
Type::Variable(*fn_var),
NoExpectation(fn_type),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
),
closure_constraint,
@ -1440,32 +1440,31 @@ pub fn rec_defs_help(
},
def.loc_expr.region,
&def.loc_expr.value,
NoExpectation(expr_type.clone()),
annotation_expected.clone(),
);
// ensure expected type unifies with annotated type
rigid_info.constraints.push(Eq(
let storage_con = Eq(
expr_type,
annotation_expected.clone(),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
def.loc_expr.region,
));
);
// TODO investigate if this let can be safely removed
let def_con = Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: Vec::new(), // empty because Roc function defs have no args
def_types: SendMap::default(), // empty because Roc function defs have no args
defs_constraint: True, // I think this is correct, once again because there are no args
defs_constraint: storage_con,
ret_constraint: expr_con,
}));
rigid_info.vars.extend(&new_rigids);
// because of how in Roc headers point to variables, we must include the pattern var here
rigid_info.vars.extend(pattern_state.vars);
rigid_info.constraints.push(Let(Box::new(LetConstraint {
rigid_vars: new_rigids,
flex_vars: Vec::new(), // no flex vars introduced
flex_vars: pattern_state.vars,
def_types: SendMap::default(), // no headers introduced (at this level)
defs_constraint: def_con,
ret_constraint: True,

View File

@ -271,7 +271,7 @@ pub fn constrain_pattern(
let whole_con = Constraint::Eq(
Type::Variable(*whole_var),
Expected::NoExpectation(record_type),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -315,7 +315,7 @@ pub fn constrain_pattern(
vec![(tag_name.clone(), argument_types)],
Box::new(Type::Variable(*ext_var)),
)),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);

View File

@ -317,7 +317,7 @@ fn constrain_pattern(
let whole_con = Constraint::Eq(
Type::Variable(*whole_var),
Expected::NoExpectation(record_type),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -381,7 +381,7 @@ fn constrain_pattern(
let whole_con = Constraint::Eq(
Type::Variable(*whole_var),
Expected::NoExpectation(union_type),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -686,7 +686,12 @@ pub fn constrain_expr(
constraints.push(Eq(inferred, expected.clone(), Category::List, region));
let stored = Type::Variable(*list_var);
constraints.push(Eq(stored, expected, Category::Storage, region));
constraints.push(Eq(
stored,
expected,
Category::Storage(std::file!(), std::line!()),
region,
));
exists(vec![*elem_var, *list_var, uniq_var], And(constraints))
}
@ -1044,7 +1049,7 @@ pub fn constrain_expr(
Eq(
Type::Variable(*var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
loc_ret.region,
),
]),
@ -1079,7 +1084,7 @@ pub fn constrain_expr(
Eq(
Type::Variable(*var),
expected,
Category::Storage,
Category::Storage(std::file!(), std::line!()),
loc_ret.region,
),
]),
@ -1175,7 +1180,7 @@ pub fn constrain_expr(
let ast_con = Eq(
Type::Variable(*branch_var),
Expected::NoExpectation(tipe),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
region,
);
@ -2278,7 +2283,7 @@ fn constrain_def(
pattern_state.constraints.push(Eq(
expr_type,
annotation_expected.clone(),
Category::Storage,
Category::Storage(std::file!(), std::line!()),
Region::zero(),
));
@ -2545,32 +2550,30 @@ pub fn rec_defs_help(
applied_usage_constraint,
def.loc_expr.region,
&def.loc_expr.value,
Expected::NoExpectation(expr_type.clone()),
annotation_expected.clone(),
);
// ensure expected type unifies with annotated type
rigid_info.constraints.push(Eq(
let storage_con = Eq(
expr_type,
annotation_expected.clone(),
Category::Storage,
annotation_expected,
Category::Storage(std::file!(), std::line!()),
def.loc_expr.region,
));
);
// TODO investigate if this let can be safely removed
let def_con = Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: Vec::new(), // empty because Roc function defs have no args
def_types: SendMap::default(), // empty because Roc function defs have no args
defs_constraint: True, // I think this is correct, once again because there are no args
defs_constraint: storage_con,
ret_constraint: expr_con,
}));
rigid_info.vars.extend(&new_rigids);
// because of how in Roc headers point to variables, we must include the pattern var here
rigid_info.vars.extend(pattern_state.vars);
rigid_info.constraints.push(Let(Box::new(LetConstraint {
rigid_vars: new_rigids,
flex_vars: Vec::new(), // no flex vars introduced
flex_vars: pattern_state.vars,
def_types: SendMap::default(), // no headers introduced (at this level)
defs_constraint: def_con,
ret_constraint: True,

View File

@ -10,7 +10,9 @@ impl LayoutId {
// Returns something like "foo#1" when given a symbol that interns to "foo"
// and a LayoutId of 1.
pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
format!("{}_{}", symbol.ident_string(interns), self.0)
let ident_string = symbol.ident_string(interns);
let module_string = interns.module_ids.get_name(symbol.module_id()).unwrap();
format!("{}_{}_{}", module_string, ident_string, self.0)
}
}

View File

@ -13,15 +13,7 @@ mod helpers;
#[cfg(test)]
mod gen_num {
/*
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::layout::Layout;
use roc_types::subs::Subs;
*/
use roc_std::RocOrder;
#[test]
fn f64_sqrt() {
@ -584,86 +576,16 @@ mod gen_num {
#[test]
fn int_compare() {
assert_evals_to!(
indoc!(
r#"
when Num.compare 0 1 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
0,
i64
);
assert_evals_to!(
indoc!(
r#"
when Num.compare 1 1 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
1,
i64
);
assert_evals_to!(
indoc!(
r#"
when Num.compare 1 0 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
2,
i64
);
assert_evals_to!("Num.compare 0 1", RocOrder::Lt, RocOrder);
assert_evals_to!("Num.compare 1 1", RocOrder::Eq, RocOrder);
assert_evals_to!("Num.compare 1 0", RocOrder::Gt, RocOrder);
}
#[test]
fn float_compare() {
assert_evals_to!(
indoc!(
r#"
when Num.compare 0 3.14 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
0,
i64
);
assert_evals_to!(
indoc!(
r#"
when Num.compare 3.14 3.14 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
1,
i64
);
assert_evals_to!(
indoc!(
r#"
when Num.compare 3.14 0 is
LT -> 0
EQ -> 1
GT -> 2
"#
),
2,
i64
);
assert_evals_to!("Num.compare 0.01 3.14", RocOrder::Lt, RocOrder);
assert_evals_to!("Num.compare 3.14 3.14", RocOrder::Eq, RocOrder);
assert_evals_to!("Num.compare 3.14 0.01", RocOrder::Gt, RocOrder);
}
#[test]

View File

@ -549,28 +549,25 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn linked_list_len_0() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app LinkedListLen0 provides [ main ] imports []
app Test provides [ main ] imports []
LinkedList a : [ Nil, Cons a (LinkedList a) ]
# nil : LinkedList Int
nil = Cons 0x1 Nil
# length : [ Nil, Cons a (LinkedList a) ] as LinkedList a -> Int
length : LinkedList a -> Int
length = \list ->
len : LinkedList a -> Int
len = \list ->
when list is
Nil -> 0
Cons _ rest -> 1 + length rest
Cons _ rest -> 1 + len rest
main =
length nil
nil : LinkedList Int
nil = Nil
len nil
"#
),
0,
@ -579,9 +576,8 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn linked_list_len_twice_0() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app LinkedListLenTwice0 provides [ main ] imports []
@ -607,9 +603,8 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn linked_list_len_1() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
@ -635,9 +630,8 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn linked_list_len_twice_1() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
@ -663,9 +657,8 @@ mod gen_primitives {
}
#[test]
#[ignore]
fn linked_list_len_3() {
assert_evals_to!(
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
@ -1111,4 +1104,95 @@ mod gen_primitives {
|_| 1
);
}
#[test]
fn linked_list_is_empty_1() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
ConsList a : [ Cons a (ConsList a), Nil ]
empty : ConsList a
empty = Nil
isEmpty : ConsList a -> Bool
isEmpty = \list ->
when list is
Cons _ _ ->
False
Nil ->
True
main : Bool
main =
myList : ConsList Int
myList = empty
isEmpty myList
"#
),
true,
bool
);
}
#[test]
fn linked_list_is_empty_2() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
ConsList a : [ Cons a (ConsList a), Nil ]
# isEmpty : ConsList a -> Bool
isEmpty = \list ->
when list is
Cons _ _ ->
False
Nil ->
True
main : Bool
main =
myList : ConsList Int
myList = Cons 0x1 Nil
isEmpty myList
"#
),
false,
bool
);
}
#[test]
fn recursive_functon_with_rigid() {
assert_non_opt_evals_to!(
indoc!(
r#"
app Test provides [ main ] imports []
State a : { count : Int, x : a }
foo : State a -> Int
foo = \state ->
if state.count == 0 then
0
else
1 + foo { count: state.count - 1, x: state.x }
main : Int
main =
foo { count: 3, x: {} }
"#
),
3,
i64
);
}
}

View File

@ -88,7 +88,7 @@ mod gen_tags {
}
#[test]
fn applied_tag_just_unit() {
fn applied_tag_just_enum() {
assert_evals_to!(
indoc!(
r#"
@ -105,7 +105,7 @@ mod gen_tags {
"#
),
(0, 2),
(i64, i64)
(i64, u8)
);
}
@ -236,7 +236,6 @@ mod gen_tags {
// }
#[test]
#[ignore]
fn even_odd() {
assert_evals_to!(
indoc!(

View File

@ -25,6 +25,7 @@ crossbeam = "0.7"
num_cpus = "1"
[dev-dependencies]
tempfile = "3.1.0"
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"

View File

@ -0,0 +1,793 @@
use roc_can::def::{Declaration, Def};
use roc_can::env::Env;
use roc_can::expr::{Expr, Recursive};
use roc_can::pattern::Pattern;
use roc_can::scope::Scope;
use roc_collections::all::SendMap;
use roc_module::ident::TagName;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::Type;
/// Functions that are always implemented for Effect
type Builder = for<'r, 's, 't0, 't1> fn(
&'r mut Env<'s>,
&'t0 mut Scope,
Symbol,
TagName,
&'t1 mut VarStore,
) -> (Symbol, Def);
pub const BUILTIN_EFFECT_FUNCTIONS: [(&str, Builder); 3] = [
// Effect.after : Effect a, (a -> Effect b) -> Effect b
("after", build_effect_after),
// Effect.map : Effect a, (a -> b) -> Effect b
("map", build_effect_map),
// Effect.always : a -> Effect a
("always", build_effect_always),
];
// the Effects alias & associated functions
//
// A platform can define an Effect type in its header. It can have an arbitrary name
// (e.g. Task, IO), but we'll call it an Effect in general.
//
// From that name, we generate an effect module, an effect alias, and some functions.
//
// The effect alias is implemented as
//
// Effect a : [ @Effect ({} -> a) ]
//
// For this alias we implement the functions defined in BUILTIN_EFFECT_FUNCTIONS with the
// standard implementation.
pub fn build_effect_builtins(
env: &mut Env,
scope: &mut Scope,
effect_symbol: Symbol,
var_store: &mut VarStore,
exposed_vars_by_symbol: &mut Vec<(Symbol, Variable)>,
declarations: &mut Vec<Declaration>,
) {
for (_, f) in BUILTIN_EFFECT_FUNCTIONS.iter() {
let (symbol, def) = f(
env,
scope,
effect_symbol,
TagName::Private(effect_symbol),
var_store,
);
exposed_vars_by_symbol.push((symbol, def.expr_var));
declarations.push(Declaration::Declare(def));
}
}
fn build_effect_always(
env: &mut Env,
scope: &mut Scope,
effect_symbol: Symbol,
effect_tag_name: TagName,
var_store: &mut VarStore,
) -> (Symbol, Def) {
// Effect.always = \value -> @Effect \{} -> value
let value_symbol = {
scope
.introduce(
"effect_always_value".into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
let inner_closure_symbol = {
scope
.introduce(
"effect_always_inner".into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
let always_symbol = {
scope
.introduce(
"always".into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
// \{} -> value
let const_closure = {
let arguments = vec![(
var_store.fresh(),
Located::at_zero(empty_record_pattern(var_store)),
)];
let body = Expr::Var(value_symbol);
Expr::Closure {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: var_store.fresh(),
name: inner_closure_symbol,
captured_symbols: vec![(value_symbol, var_store.fresh())],
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
}
};
// \value -> @Effect \{} -> value
let (function_var, always_closure) = {
// `@Effect \{} -> value`
let body = Expr::Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: effect_tag_name.clone(),
arguments: vec![(var_store.fresh(), Located::at_zero(const_closure))],
};
let arguments = vec![(
var_store.fresh(),
Located::at_zero(Pattern::Identifier(value_symbol)),
)];
let function_var = var_store.fresh();
let closure = Expr::Closure {
function_type: function_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: var_store.fresh(),
name: always_symbol,
captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
};
(function_var, closure)
};
use roc_can::annotation::IntroducedVariables;
let mut introduced_variables = IntroducedVariables::default();
let signature = {
// Effect.always : a -> Effect a
let var_a = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a);
let effect_a = {
let actual = build_effect_actual(effect_tag_name, Type::Variable(var_a), var_store);
Type::Alias(
effect_symbol,
vec![("a".into(), Type::Variable(var_a))],
Box::new(actual),
)
};
let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var);
Type::Function(
vec![Type::Variable(var_a)],
Box::new(Type::Variable(closure_var)),
Box::new(effect_a),
)
};
let def_annotation = roc_can::def::Annotation {
signature,
introduced_variables,
aliases: SendMap::default(),
region: Region::zero(),
};
let pattern = Pattern::Identifier(always_symbol);
let mut pattern_vars = SendMap::default();
pattern_vars.insert(always_symbol, function_var);
let def = Def {
loc_pattern: Located::at_zero(pattern),
loc_expr: Located::at_zero(always_closure),
expr_var: function_var,
pattern_vars,
annotation: Some(def_annotation),
};
(always_symbol, def)
}
fn build_effect_map(
env: &mut Env,
scope: &mut Scope,
effect_symbol: Symbol,
effect_tag_name: TagName,
var_store: &mut VarStore,
) -> (Symbol, Def) {
// Effect.map = \@Effect thunk, mapper -> @Effect \{} -> mapper (thunk {})
let thunk_symbol = {
scope
.introduce(
"effect_map_thunk".into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
let mapper_symbol = {
scope
.introduce(
"effect_map_mapper".into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
let map_symbol = {
scope
.introduce(
"map".into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
// `thunk {}`
let force_thunk_call = {
let boxed = (
var_store.fresh(),
Located::at_zero(Expr::Var(thunk_symbol)),
var_store.fresh(),
var_store.fresh(),
);
let arguments = vec![(var_store.fresh(), Located::at_zero(Expr::EmptyRecord))];
Expr::Call(Box::new(boxed), arguments, CalledVia::Space)
};
// `toEffect (thunk {})`
let mapper_call = {
let boxed = (
var_store.fresh(),
Located::at_zero(Expr::Var(mapper_symbol)),
var_store.fresh(),
var_store.fresh(),
);
let arguments = vec![(var_store.fresh(), Located::at_zero(force_thunk_call))];
Expr::Call(Box::new(boxed), arguments, CalledVia::Space)
};
let inner_closure_symbol = {
scope
.introduce(
"effect_map_inner".into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
// \{} -> mapper (thunk {})
let inner_closure = {
let arguments = vec![(
var_store.fresh(),
Located::at_zero(empty_record_pattern(var_store)),
)];
Expr::Closure {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: var_store.fresh(),
name: inner_closure_symbol,
captured_symbols: vec![
(thunk_symbol, var_store.fresh()),
(mapper_symbol, var_store.fresh()),
],
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(mapper_call)),
}
};
let arguments = vec![
(
var_store.fresh(),
Located::at_zero(Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name: effect_tag_name.clone(),
arguments: vec![(
var_store.fresh(),
Located::at_zero(Pattern::Identifier(thunk_symbol)),
)],
}),
),
(
var_store.fresh(),
Located::at_zero(Pattern::Identifier(mapper_symbol)),
),
];
// `@Effect \{} -> (mapper (thunk {}))`
let body = Expr::Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: effect_tag_name.clone(),
arguments: vec![(var_store.fresh(), Located::at_zero(inner_closure))],
};
let function_var = var_store.fresh();
let map_closure = Expr::Closure {
function_type: function_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: var_store.fresh(),
name: map_symbol,
captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
};
use roc_can::annotation::IntroducedVariables;
let mut introduced_variables = IntroducedVariables::default();
let signature = {
// Effect.map : Effect a, (a -> b) -> Effect b
let var_a = var_store.fresh();
let var_b = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a);
introduced_variables.insert_named("b".into(), var_b);
let effect_a = {
let actual =
build_effect_actual(effect_tag_name.clone(), Type::Variable(var_a), var_store);
Type::Alias(
effect_symbol,
vec![("a".into(), Type::Variable(var_a))],
Box::new(actual),
)
};
let effect_b = {
let actual = build_effect_actual(effect_tag_name, Type::Variable(var_b), var_store);
Type::Alias(
effect_symbol,
vec![("b".into(), Type::Variable(var_b))],
Box::new(actual),
)
};
let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var);
let a_to_b = {
Type::Function(
vec![Type::Variable(var_a)],
Box::new(Type::Variable(closure_var)),
Box::new(Type::Variable(var_b)),
)
};
let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var);
Type::Function(
vec![effect_a, a_to_b],
Box::new(Type::Variable(closure_var)),
Box::new(effect_b),
)
};
let def_annotation = roc_can::def::Annotation {
signature,
introduced_variables,
aliases: SendMap::default(),
region: Region::zero(),
};
let pattern = Pattern::Identifier(map_symbol);
let mut pattern_vars = SendMap::default();
pattern_vars.insert(map_symbol, function_var);
let def = Def {
loc_pattern: Located::at_zero(pattern),
loc_expr: Located::at_zero(map_closure),
expr_var: function_var,
pattern_vars,
annotation: Some(def_annotation),
};
(map_symbol, def)
}
fn build_effect_after(
env: &mut Env,
scope: &mut Scope,
effect_symbol: Symbol,
effect_tag_name: TagName,
var_store: &mut VarStore,
) -> (Symbol, Def) {
// Effect.after = \@Effect effect, toEffect -> toEffect (effect {})
let thunk_symbol = {
scope
.introduce(
"effect_after_thunk".into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
let to_effect_symbol = {
scope
.introduce(
"effect_after_toEffect".into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
let after_symbol = {
scope
.introduce(
"after".into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
// `thunk {}`
let force_thunk_call = {
let boxed = (
var_store.fresh(),
Located::at_zero(Expr::Var(thunk_symbol)),
var_store.fresh(),
var_store.fresh(),
);
let arguments = vec![(var_store.fresh(), Located::at_zero(Expr::EmptyRecord))];
Expr::Call(Box::new(boxed), arguments, CalledVia::Space)
};
// `toEffect (thunk {})`
let to_effect_call = {
let boxed = (
var_store.fresh(),
Located::at_zero(Expr::Var(to_effect_symbol)),
var_store.fresh(),
var_store.fresh(),
);
let arguments = vec![(var_store.fresh(), Located::at_zero(force_thunk_call))];
Expr::Call(Box::new(boxed), arguments, CalledVia::Space)
};
let arguments = vec![
(
var_store.fresh(),
Located::at_zero(Pattern::AppliedTag {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
tag_name: effect_tag_name.clone(),
arguments: vec![(
var_store.fresh(),
Located::at_zero(Pattern::Identifier(thunk_symbol)),
)],
}),
),
(
var_store.fresh(),
Located::at_zero(Pattern::Identifier(to_effect_symbol)),
),
];
let function_var = var_store.fresh();
let after_closure = Expr::Closure {
function_type: function_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: var_store.fresh(),
name: after_symbol,
captured_symbols: Vec::new(),
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(to_effect_call)),
};
use roc_can::annotation::IntroducedVariables;
let mut introduced_variables = IntroducedVariables::default();
let signature = {
let var_a = var_store.fresh();
let var_b = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a);
introduced_variables.insert_named("b".into(), var_b);
let effect_a = {
let actual =
build_effect_actual(effect_tag_name.clone(), Type::Variable(var_a), var_store);
Type::Alias(
effect_symbol,
vec![("a".into(), Type::Variable(var_a))],
Box::new(actual),
)
};
let effect_b = {
let actual = build_effect_actual(effect_tag_name, Type::Variable(var_b), var_store);
Type::Alias(
effect_symbol,
vec![("b".into(), Type::Variable(var_b))],
Box::new(actual),
)
};
let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var);
let a_to_effect_b = Type::Function(
vec![Type::Variable(var_a)],
Box::new(Type::Variable(closure_var)),
Box::new(effect_b.clone()),
);
let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var);
Type::Function(
vec![effect_a, a_to_effect_b],
Box::new(Type::Variable(closure_var)),
Box::new(effect_b),
)
};
let def_annotation = roc_can::def::Annotation {
signature,
introduced_variables,
aliases: SendMap::default(),
region: Region::zero(),
};
let pattern = Pattern::Identifier(after_symbol);
let mut pattern_vars = SendMap::default();
pattern_vars.insert(after_symbol, function_var);
let def = Def {
loc_pattern: Located::at_zero(pattern),
loc_expr: Located::at_zero(after_closure),
expr_var: function_var,
pattern_vars,
annotation: Some(def_annotation),
};
(after_symbol, def)
}
pub fn build_host_exposed_def(
env: &mut Env,
scope: &mut Scope,
symbol: Symbol,
ident: &str,
effect_tag_name: TagName,
var_store: &mut VarStore,
annotation: roc_can::annotation::Annotation,
) -> Def {
let expr_var = var_store.fresh();
let pattern = Pattern::Identifier(symbol);
let mut pattern_vars = SendMap::default();
pattern_vars.insert(symbol, expr_var);
let mut arguments: Vec<(Variable, Located<Pattern>)> = Vec::new();
let mut linked_symbol_arguments: Vec<(Variable, Expr)> = Vec::new();
let mut captured_symbols: Vec<(Symbol, Variable)> = Vec::new();
let def_body = {
match annotation.typ.shallow_dealias() {
Type::Function(args, _, _) => {
for i in 0..args.len() {
let name = format!("closure_arg_{}_{}", ident, i);
let arg_symbol = {
let ident = name.clone().into();
scope
.introduce(
ident,
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
let arg_var = var_store.fresh();
arguments.push((arg_var, Located::at_zero(Pattern::Identifier(arg_symbol))));
captured_symbols.push((arg_symbol, arg_var));
linked_symbol_arguments.push((arg_var, Expr::Var(arg_symbol)));
}
let foreign_symbol_name = format!("roc_fx_{}", ident);
let low_level_call = Expr::ForeignCall {
foreign_symbol: foreign_symbol_name.into(),
args: linked_symbol_arguments,
ret_var: var_store.fresh(),
};
let effect_closure_symbol = {
let name = format!("effect_closure_{}", ident);
let ident = name.into();
scope
.introduce(
ident,
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
let effect_closure = Expr::Closure {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: var_store.fresh(),
name: effect_closure_symbol,
captured_symbols,
recursive: Recursive::NotRecursive,
arguments: vec![(
var_store.fresh(),
Located::at_zero(empty_record_pattern(var_store)),
)],
loc_body: Box::new(Located::at_zero(low_level_call)),
};
let body = Expr::Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: effect_tag_name,
arguments: vec![(var_store.fresh(), Located::at_zero(effect_closure))],
};
Expr::Closure {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: var_store.fresh(),
name: symbol,
captured_symbols: std::vec::Vec::new(),
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
}
}
_ => {
// not a function
let foreign_symbol_name = format!("roc_fx_{}", ident);
let low_level_call = Expr::ForeignCall {
foreign_symbol: foreign_symbol_name.into(),
args: linked_symbol_arguments,
ret_var: var_store.fresh(),
};
let effect_closure_symbol = {
let name = format!("effect_closure_{}", ident);
let ident = name.into();
scope
.introduce(
ident,
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
let empty_record_pattern = Pattern::RecordDestructure {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
destructs: vec![],
};
let effect_closure = Expr::Closure {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: var_store.fresh(),
name: effect_closure_symbol,
captured_symbols,
recursive: Recursive::NotRecursive,
arguments: vec![(var_store.fresh(), Located::at_zero(empty_record_pattern))],
loc_body: Box::new(Located::at_zero(low_level_call)),
};
Expr::Tag {
variant_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: effect_tag_name,
arguments: vec![(var_store.fresh(), Located::at_zero(effect_closure))],
}
}
}
};
let def_annotation = roc_can::def::Annotation {
signature: annotation.typ,
introduced_variables: annotation.introduced_variables,
aliases: annotation.aliases,
region: Region::zero(),
};
Def {
loc_pattern: Located::at_zero(pattern),
loc_expr: Located::at_zero(def_body),
expr_var,
pattern_vars,
annotation: Some(def_annotation),
}
}
pub fn build_effect_actual(
effect_tag_name: TagName,
a_type: Type,
var_store: &mut VarStore,
) -> Type {
let closure_var = var_store.fresh();
Type::TagUnion(
vec![(
effect_tag_name,
vec![Type::Function(
vec![Type::EmptyRec],
Box::new(Type::Variable(closure_var)),
Box::new(a_type),
)],
)],
Box::new(Type::EmptyTagUnion),
)
}
#[inline(always)]
fn empty_record_pattern(var_store: &mut VarStore) -> Pattern {
Pattern::RecordDestructure {
whole_var: var_store.fresh(),
ext_var: var_store.fresh(),
destructs: vec![],
}
}

File diff suppressed because it is too large Load Diff

View File

@ -11,4 +11,5 @@
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
pub mod docs;
pub mod effect_module;
pub mod file;

View File

@ -1,4 +1,6 @@
#[macro_use]
extern crate indoc;
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate maplit;
@ -28,6 +30,87 @@ mod test_load {
// HELPERS
fn multiple_modules(files: Vec<(&str, &str)>) -> LoadedModule {
multiple_modules_help(files).unwrap()
}
fn multiple_modules_help(mut files: Vec<(&str, &str)>) -> Result<LoadedModule, std::io::Error> {
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use tempfile::tempdir;
let arena = Bump::new();
let arena = &arena;
let stdlib = roc_builtins::std::standard_stdlib();
let mut file_handles: Vec<_> = Vec::new();
let exposed_types = MutMap::default();
let loaded = {
// create a temporary directory
let dir = tempdir()?;
let app_module = files.pop().unwrap();
let interfaces = files;
debug_assert!(
app_module.1.starts_with("app"),
"The final module should be the application module"
);
for (name, source) in interfaces {
let mut filename = PathBuf::from(name);
filename.set_extension("roc");
let file_path = dir.path().join(filename.clone());
let mut file = File::create(file_path)?;
writeln!(file, "{}", source)?;
file_handles.push(file);
}
let result = {
let (name, source) = app_module;
let filename = PathBuf::from(name);
let file_path = dir.path().join(filename.clone());
let full_file_path = PathBuf::from(file_path.clone());
let mut file = File::create(file_path)?;
writeln!(file, "{}", source)?;
file_handles.push(file);
roc_load::file::load_and_typecheck(
arena,
full_file_path,
stdlib,
dir.path(),
exposed_types,
)
};
dir.close()?;
result
};
let mut loaded_module = loaded.expect("failed to load module");
let home = loaded_module.module_id;
assert_eq!(
loaded_module.can_problems.remove(&home).unwrap_or_default(),
Vec::new()
);
assert_eq!(
loaded_module
.type_problems
.remove(&home)
.unwrap_or_default(),
Vec::new()
);
Ok(loaded_module)
}
fn load_fixture(
dir_name: &str,
module_name: &str,
@ -146,6 +229,46 @@ mod test_load {
// TESTS
#[test]
fn import_transitive_alias() {
// this had a bug where NodeColor was HostExposed, and it's `actual_var` conflicted
// with variables in the importee
let modules = vec![
(
"RBTree",
indoc!(
r#"
interface RBTree exposes [ Dict, empty ] imports []
# The color of a node. Leaves are considered Black.
NodeColor : [ Red, Black ]
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
# Create an empty dictionary.
empty : Dict k v
empty =
Empty
"#
),
),
(
"Main",
indoc!(
r#"
app Test provides [ main ] imports [ RBTree ]
empty : RBTree.Dict Int Int
empty = RBTree.empty
main = empty
"#
),
),
];
multiple_modules(modules);
}
#[test]
fn interface_with_deps() {
let subs_by_module = MutMap::default();

View File

@ -2173,6 +2173,17 @@ pub fn with_hole<'a>(
if let roc_can::expr::Expr::Var(original) = def.loc_expr.value {
substitute_in_exprs(env.arena, &mut stmt, symbol, original);
// if the substituted variable is a function, make sure we specialize it
stmt = reuse_function_symbol(
env,
procs,
layout_cache,
Some(def.expr_var),
original,
stmt,
original,
);
stmt
} else {
with_hole(
@ -3576,6 +3587,17 @@ pub fn from_can<'a>(
// a variable is aliased
substitute_in_exprs(env.arena, &mut rest, *symbol, original);
// if the substituted variable is a function, make sure we specialize it
rest = reuse_function_symbol(
env,
procs,
layout_cache,
Some(def.expr_var),
original,
rest,
original,
);
return rest;
}
roc_can::expr::Expr::LetNonRec(nested_def, nested_cont, nested_annotation) => {

View File

@ -1002,7 +1002,7 @@ fn parse_closure_param<'a>(
) -> ParseResult<'a, Located<Pattern<'a>>> {
one_of!(
// An ident is the most common param, e.g. \foo -> ...
loc_ident_pattern(min_indent),
loc_ident_pattern(min_indent, true),
// Underscore is also common, e.g. \_ -> ...
loc!(underscore_pattern()),
// You can destructure records in params, e.g. \{ x, y } -> ...
@ -1026,7 +1026,7 @@ fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
one_of!(
loc_parenthetical_pattern(min_indent),
loc!(underscore_pattern()),
loc_ident_pattern(min_indent),
loc_ident_pattern(min_indent, true),
loc!(record_destructure(min_indent)),
loc!(string_pattern()),
loc!(number_pattern())
@ -1034,6 +1034,38 @@ fn loc_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
)
}
fn loc_tag_pattern_args<'a>(min_indent: u16) -> impl Parser<'a, Vec<'a, Located<Pattern<'a>>>> {
zero_or_more!(space1_before(loc_tag_pattern_arg(min_indent), min_indent))
}
fn loc_tag_pattern_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
skip_first!(
// If this is a reserved keyword ("if", "then", "case, "when"), then
// it is not a function argument!
not(reserved_keyword()),
// Don't parse operators, because they have a higher precedence than function application.
// If we encounter one, we're done parsing function args!
move |arena, state| loc_parse_tag_pattern_arg(min_indent, arena, state)
)
}
fn loc_parse_tag_pattern_arg<'a>(
min_indent: u16,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Located<Pattern<'a>>> {
one_of!(
loc_parenthetical_pattern(min_indent),
loc!(underscore_pattern()),
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
loc_ident_pattern(min_indent, false),
loc!(record_destructure(min_indent)),
loc!(string_pattern()),
loc!(number_pattern())
)
.parse(arena, state)
}
fn loc_parenthetical_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
between!(
ascii_char(b'('),
@ -1133,51 +1165,64 @@ fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> {
)
}
fn loc_ident_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>>> {
fn loc_ident_pattern<'a>(
min_indent: u16,
can_have_arguments: bool,
) -> impl Parser<'a, Located<Pattern<'a>>> {
move |arena: &'a Bump, state: State<'a>| {
let (loc_ident, state) = loc!(ident()).parse(arena, state)?;
match loc_ident.value {
Ident::GlobalTag(tag) => {
let (loc_args, state) =
zero_or_more!(space1_before(loc_pattern(min_indent), min_indent))
.parse(arena, state)?;
let loc_tag = Located {
region: loc_ident.region,
value: Pattern::GlobalTag(tag),
};
if loc_args.is_empty() {
Ok((loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value = Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (loc_args, state) = loc_tag_pattern_args(min_indent).parse(arena, state)?;
Ok((Located { region, value }, state))
if loc_args.is_empty() {
Ok((loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((Located { region, value }, state))
}
} else {
Ok((loc_tag, state))
}
}
Ident::PrivateTag(tag) => {
let (loc_args, state) =
zero_or_more!(space1_before(loc_pattern(min_indent), min_indent))
.parse(arena, state)?;
let loc_tag = Located {
region: loc_ident.region,
value: Pattern::PrivateTag(tag),
};
if loc_args.is_empty() {
Ok((loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value = Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments {
let (loc_args, state) = loc_tag_pattern_args(min_indent).parse(arena, state)?;
Ok((Located { region, value }, state))
if loc_args.is_empty() {
Ok((loc_tag, state))
} else {
let region = Region::across_all(
std::iter::once(&loc_ident.region)
.chain(loc_args.iter().map(|loc_arg| &loc_arg.region)),
);
let value =
Pattern::Apply(&*arena.alloc(loc_tag), loc_args.into_bump_slice());
Ok((Located { region, value }, state))
}
} else {
Ok((loc_tag, state))
}
}
Ident::Access { module_name, parts } => {

View File

@ -40,21 +40,25 @@ pub fn can_problem<'b>(
Problem::UnusedArgument(closure_symbol, argument_symbol, region) => {
let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used.";
alloc.concat(vec![
alloc.symbol_unqualified(closure_symbol),
alloc.reflow(" doesn't use "),
alloc.symbol_unqualified(argument_symbol),
alloc.reflow("."),
alloc.stack(vec![
alloc.concat(vec![
alloc.symbol_unqualified(closure_symbol),
alloc.reflow(" doesn't use "),
alloc.symbol_unqualified(argument_symbol),
alloc.text("."),
]),
alloc.region(region),
alloc.reflow("If you don't need "),
alloc.symbol_unqualified(argument_symbol),
alloc.reflow(", then you can just remove it. However, if you really do need "),
alloc.symbol_unqualified(argument_symbol),
alloc.reflow(" as an argument of "),
alloc.symbol_unqualified(closure_symbol),
alloc.reflow(", prefix it with an underscore, like this: \"_"),
alloc.symbol_unqualified(argument_symbol),
alloc.reflow(line),
alloc.concat(vec![
alloc.reflow("If you don't need "),
alloc.symbol_unqualified(argument_symbol),
alloc.reflow(", then you can just remove it. However, if you really do need "),
alloc.symbol_unqualified(argument_symbol),
alloc.reflow(" as an argument of "),
alloc.symbol_unqualified(closure_symbol),
alloc.reflow(", prefix it with an underscore, like this: \"_"),
alloc.symbol_unqualified(argument_symbol),
alloc.reflow(line),
])
])
}
Problem::PrecedenceProblem(BothNonAssociative(region, left_bin_op, right_bin_op)) => alloc

View File

@ -972,7 +972,7 @@ fn add_category<'b>(
this_is,
alloc.text(" an uniqueness attribute of type:"),
]),
Storage => alloc.concat(vec![this_is, alloc.text(" a value of type:")]),
Storage(_file, _line) => alloc.concat(vec![this_is, alloc.text(" a value of type:")]),
DefaultValue(_) => alloc.concat(vec![this_is, alloc.text(" a default field of type:")]),
}

View File

@ -3880,4 +3880,54 @@ mod test_reporting {
),
)
}
#[test]
fn alias_using_alias() {
report_problem_as(
indoc!(
r#"
# The color of a node. Leaves are considered Black.
NodeColor : [ Red, Black ]
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
# Create an empty dictionary.
empty : Dict k v
empty =
Empty
empty
"#
),
"",
)
}
#[test]
fn unused_argument() {
report_problem_as(
indoc!(
r#"
f = \foo -> 1
f
"#
),
indoc!(
r#"
SYNTAX PROBLEM
`f` doesn't use `foo`.
1 f = \foo -> 1
^^^
If you don't need `foo`, then you can just remove it. However, if you
really do need `foo` as an argument of `f`, prefix it with an underscore,
like this: "_`foo`". Adding an underscore at the start of a variable
name is a way of saying that the variable is not used.
"#
),
)
}
}

View File

@ -154,9 +154,6 @@ pub fn run(
constraint,
);
//dbg!(&subs, &state.env.vars_by_symbol);
//panic!();
(Solved(subs), state.env)
}
@ -1139,29 +1136,47 @@ fn adjust_rank(
group_rank: Rank,
var: Variable,
) -> Rank {
let mut desc = subs.get(var);
let mark = desc.mark;
let desc = subs.get(var);
if mark == young_mark {
desc.mark = visit_mark;
let content = desc.content.clone();
let mut marked_desc = desc.clone();
if desc.mark == young_mark {
let Descriptor {
content,
rank,
mark: _,
copy,
} = desc;
// Mark the variable as visited before adjusting content, as it may be cyclic.
subs.set(var, desc);
subs.set(
var,
Descriptor {
content: content.clone(),
rank,
mark: visit_mark,
copy,
},
);
let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, content);
marked_desc.rank = max_rank;
let max_rank =
adjust_rank_content(subs, young_mark, visit_mark, group_rank, content.clone());
debug_assert_eq!(marked_desc.mark, visit_mark);
subs.set(var, marked_desc);
subs.set(
var,
Descriptor {
content,
rank: max_rank,
mark: visit_mark,
copy,
},
);
max_rank
} else if mark == visit_mark {
} else if desc.mark == visit_mark {
// nothing changes
desc.rank
} else {
let mut desc = desc;
let min_rank = group_rank.min(desc.rank);
// TODO from elm-compiler: how can min_rank ever be group_rank?

View File

@ -2184,7 +2184,7 @@ mod solve_expr {
r#"
app Test provides [ main ] imports []
map =
map =
\peano ->
when peano is
Z -> Z
@ -2919,7 +2919,7 @@ mod solve_expr {
infer_eq_without_problem(
indoc!(
r#"
List.walkRight
List.walkRight
"#
),
"List a, (a, b -> b), b -> b",
@ -2932,7 +2932,7 @@ mod solve_expr {
indoc!(
r#"
empty : List Int
empty =
empty =
[]
List.walkRight empty (\a, b -> a + b) 0
@ -2980,4 +2980,130 @@ mod solve_expr {
"List x",
);
}
#[test]
fn double_tag_application() {
infer_eq_without_problem(
indoc!(
r#"
app Test provides [ main ] imports []
main =
if 1 == 1 then
Foo (Bar) 1
else
Foo Bar 1
"#
),
"[ Foo [ Bar ]* (Num *) ]*",
);
infer_eq_without_problem("Foo Bar 1", "[ Foo [ Bar ]* (Num *) ]*");
}
#[test]
fn double_tag_application_pattern_global() {
infer_eq_without_problem(
indoc!(
r#"
app Test provides [ main ] imports []
Bar : [ Bar ]
Foo : [ Foo Bar Int, Empty ]
foo : Foo
foo = Foo Bar 1
main =
when foo is
Foo Bar 1 ->
Foo Bar 2
x ->
x
"#
),
"[ Empty, Foo [ Bar ] Int ]",
);
}
#[test]
fn double_tag_application_pattern_private() {
infer_eq_without_problem(
indoc!(
r#"
app Test provides [ main ] imports []
Foo : [ @Foo [ @Bar ] Int, @Empty ]
foo : Foo
foo = @Foo @Bar 1
main =
when foo is
@Foo @Bar 1 ->
@Foo @Bar 2
x ->
x
"#
),
"[ @Empty, @Foo [ @Bar ] Int ]",
);
}
#[test]
fn recursive_functon_with_rigid() {
infer_eq_without_problem(
indoc!(
r#"
app Test provides [ main ] imports []
State a : { count : Int, x : a }
foo : State a -> Int
foo = \state ->
if state.count == 0 then
0
else
1 + foo { count: state.count - 1, x: state.x }
main : Int
main =
foo { count: 3, x: {} }
"#
),
"Int",
);
}
#[test]
fn rbtree_empty() {
infer_eq_without_problem(
indoc!(
r#"
app Test provides [ main ] imports []
# The color of a node. Leaves are considered Black.
NodeColor : [ Red, Black ]
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
# Create an empty dictionary.
empty : Dict k v
empty =
Empty
foo : Dict Int Int
foo = empty
main : Dict Int Int
main =
foo
"#
),
"Dict Int Int",
);
}
}

View File

@ -1007,7 +1007,7 @@ pub enum Category {
StrInterpolation,
// storing variables in the ast
Storage,
Storage(&'static str, u32),
// control flow
If,

View File

@ -4,16 +4,16 @@ use std::alloc::Layout;
use std::time::SystemTime;
extern "C" {
#[link_name = "makeClosure_1_exposed"]
#[link_name = "Main_makeClosure_1_exposed"]
fn make_closure(output: *mut u8) -> ();
#[link_name = "makeClosure_1_size"]
#[link_name = "Main_makeClosure_1_size"]
fn closure_size() -> i64;
#[link_name = "makeClosure_1_MyClosure_caller"]
#[link_name = "Main_makeClosure_1_MyClosure_caller"]
fn call_MyClosure(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> ();
#[link_name = "makeClosure_1_MyClosure_size"]
#[link_name = "Main_makeClosure_1_MyClosure_size"]
fn size_MyClosure() -> i64;
}

View File

@ -0,0 +1,34 @@
interface ConsList exposes [ConsList, empty, isEmpty, map, len] imports []
ConsList a : [ Cons a (ConsList a), Nil ]
empty : ConsList a
empty = Nil
len : ConsList a -> Int
len = \list ->
when list is
Cons _ rest -> 1 + len rest
Nil ->
0
map : ConsList a, (a -> b) -> ConsList b
map = \list, f ->
when list is
Cons x xs ->
Cons (f x) (map xs f)
Nil ->
Nil
isEmpty : ConsList a -> Bool
isEmpty = \list ->
when list is
Cons _ _ ->
False
Nil ->
True

View File

@ -1,7 +1,21 @@
app Main provides [ main ] imports [ Effect ]
app Main provides [ main ] imports [ Effect, RBTree ]
foo : RBTree.Dict Int Int
foo = Empty # RBTree.empty
main : Effect.Effect {} as Fx
main =
Effect.putLine "Write a thing!"
|> Effect.after (\{} -> Effect.getLine)
|> Effect.after (\line -> Effect.putLine line)
# if RBTree.isEmpty empty then
if RBTree.size foo == 0 then
Effect.putLine "Yay"
|> Effect.after (\{} -> Effect.getLine)
|> Effect.after (\line -> Effect.putLine line)
else
Effect.putLine "Nay"
# Effect.always "Write a thing"
# |> Effect.map (\line -> Str.concat line "!")
# |> Effect.after (\line -> Effect.putLine line)
# |> Effect.after (\{} -> Effect.getLine)
# |> Effect.after (\line -> Effect.putLine line)

307
examples/effect/RBTree.roc Normal file
View File

@ -0,0 +1,307 @@
interface RBTree exposes [ Dict, empty, size, singleton ] imports []
# The color of a node. Leaves are considered Black.
NodeColor : [ Red, Black ]
Dict k v : [ Node NodeColor k v (Dict k v) (Dict k v), Empty ]
Key k : Num k
Maybe a : [ Just a, Nothing ]
# Create an empty dictionary.
empty : Dict k v
empty =
Empty
# Create a dictionary with one key-value pair.
singleton : Key k, v -> Dict (Key k) v
singleton = \key, value ->
# Root node is always Black
Node Black key value Empty Empty
# {-| Determine the number of key-value pairs in the dictionary. -}
size : Dict k v -> Int
size = \dict ->
sizeHelp 0 dict
sizeHelp : Int, Dict k v -> Int
sizeHelp = \n, dict ->
when dict is
Empty ->
n
Node _ _ _ left right ->
sizeHelp (sizeHelp (n+1) right) left
isEmpty : Dict k v -> Bool
isEmpty = \dict ->
when dict is
Empty ->
True
Node _ _ _ _ _ ->
False
insert : Key k, v, Dict (Key k) v -> Dict (Key k) v
insert = \key, value, dict ->
when insertHelp key value dict is
Node Red k v l r ->
Node Black k v l r
x ->
x
insertHelp : (Key k), v, Dict (Key k) v -> Dict (Key k) v
insertHelp = \key, value, dict ->
when dict is
Empty ->
# New nodes are always red. If it violates the rules, it will be fixed
# when balancing.
Node Red key value Empty Empty
Node nColor nKey nValue nLeft nRight ->
when Num.compare key nKey is
LT ->
balance nColor nKey nValue (insertHelp key value nLeft) nRight
EQ ->
Node nColor nKey value nLeft nRight
GT ->
balance nColor nKey nValue nLeft (insertHelp key value nRight)
balance : NodeColor, k, v, Dict k v, Dict k v -> Dict k v
balance = \color, key, value, left, right ->
when right is
Node Red rK rV rLeft rRight ->
when left is
Node Red lK lV lLeft lRight ->
Node
Red
key
value
(Node Black lK lV lLeft lRight)
(Node Black rK rV rLeft rRight)
_ ->
Node color rK rV (Node Red key value left rLeft) rRight
_ ->
when left is
Node Red lK lV (Node Red llK llV llLeft llRight) lRight ->
Node
Red
lK
lV
(Node Black llK llV llLeft llRight)
(Node Black key value lRight right)
_ ->
Node color key value left right
remove : Key k, Dict (Key k) v -> Dict (Key k) v
remove = \key, dict ->
# Root node is always Black
when removeHelp key dict is
Node Red k v l r ->
Node Black k v l r
x ->
x
# The easiest thing to remove from the tree, is a red node. However, when searching for the
# node to remove, we have no way of knowing if it will be red or not. This remove implementation
# makes sure that the bottom node is red by moving red colors down the tree through rotation
# and color flips. Any violations this will cause, can easily be fixed by balancing on the way
# up again.
removeHelp : Key k, Dict (Key k) v -> Dict (Key k) v
removeHelp = \targetKey, dict ->
when dict is
Empty ->
Empty
Node color key value left right ->
if targetKey < key then
when left is
Node Black _ _ lLeft _ ->
when lLeft is
Node Red _ _ _ _ ->
Node color key value (removeHelp targetKey left) right
_ ->
when moveRedLeft dict is
Node nColor nKey nValue nLeft nRight ->
balance nColor nKey nValue (removeHelp targetKey nLeft) nRight
Empty ->
Empty
_ ->
Node color key value (removeHelp targetKey left) right
else
removeHelpEQGT targetKey (removeHelpPrepEQGT targetKey dict color key value left right)
removeHelpPrepEQGT : Key k, Dict (Key k) v, NodeColor, (Key k), v, Dict (Key k) v, Dict (Key k) v -> Dict (Key k) v
removeHelpPrepEQGT = \_, dict, color, key, value, left, right ->
when left is
Node Red lK lV lLeft lRight ->
Node
color
lK
lV
lLeft
(Node Red key value lRight right)
_ ->
when right is
Node Black _ _ (Node Black _ _ _ _) _ ->
moveRedRight dict
Node Black _ _ Empty _ ->
moveRedRight dict
_ ->
dict
# When we find the node we are looking for, we can remove by replacing the key-value
# pair with the key-value pair of the left-most node on the right side (the closest pair).
removeHelpEQGT : Key k, Dict (Key k) v -> Dict (Key k) v
removeHelpEQGT = \targetKey, dict ->
when dict is
Node color key value left right ->
if targetKey == key then
when getMin right is
Node _ minKey minValue _ _ ->
balance color minKey minValue left (removeMin right)
Empty ->
Empty
else
balance color key value left (removeHelp targetKey right)
Empty ->
Empty
getMin : Dict k v -> Dict k v
getMin = \dict ->
when dict is
# Node _ _ _ ((Node _ _ _ _ _) as left) _ ->
Node _ _ _ left _ ->
when left is
Node _ _ _ _ _ -> getMin left
_ -> dict
_ ->
dict
moveRedLeft : Dict k v -> Dict k v
moveRedLeft = \dict ->
when dict is
# Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV ((Node Red rlK rlV rlL rlR) as rLeft) rRight) ->
Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) ->
when rList is
Node Red rlK rlV rlL rlR ->
Node
Red
rlK
rlV
(Node Black k v (Node Red lK lV lLeft lRight) rlL)
(Node Black rK rV rlR rRight)
_ ->
when clr is
Black ->
Node
Black
k
v
(Node Red lK lV lLeft lRight)
(Node Red rK rV rLeft rRight)
Red ->
Node
Black
k
v
(Node Red lK lV lLeft lRight)
(Node Red rK rV rLeft rRight)
_ ->
dict
moveRedRight : Dict k v -> Dict k v
moveRedRight = \dict ->
when dict is
Node clr k v (Node lClr lK lV (Node Red llK llV llLeft llRight) lRight) (Node rClr rK rV rLeft rRight) ->
Node
Red
lK
lV
(Node Black llK llV llLeft llRight)
(Node Black k v lRight (Node Red rK rV rLeft rRight))
Node clr k v (Node lClr lK lV lLeft lRight) (Node rClr rK rV rLeft rRight) ->
when clr is
Black ->
Node
Black
k
v
(Node Red lK lV lLeft lRight)
(Node Red rK rV rLeft rRight)
Red ->
Node
Black
k
v
(Node Red lK lV lLeft lRight)
(Node Red rK rV rLeft rRight)
_ ->
dict
removeMin : Dict k v -> Dict k v
removeMin = \dict ->
when dict is
Node color key value ((Node lColor _ _ lLeft _) as left) right ->
when lColor is
Black ->
when lLeft is
Node Red _ _ _ _ ->
Node color key value (removeMin left) right
_ ->
when moveRedLeft dict is
Node nColor nKey nValue nLeft nRight ->
balance nColor nKey nValue (removeMin nLeft) nRight
Empty ->
Empty
_ ->
Node color key value (removeMin left) right
_ ->
Empty
# Update the value of a dictionary for a specific key with a given function.
update : Key k, (Maybe v, Maybe v), Dict (Key k) v -> Dict (Key k) v
update = \targetKey, alter, dictionary ->
when alter (get targetKey dictionary) is
Just value ->
insert targetKey value dictionary
Nothing ->
remove targetKey dictionary

View File

@ -7,16 +7,16 @@ use std::alloc::Layout;
use std::time::SystemTime;
extern "C" {
#[link_name = "main_1_exposed"]
#[link_name = "Main_main_1_exposed"]
fn roc_main(output: *mut u8) -> ();
#[link_name = "main_1_size"]
#[link_name = "Main_main_1_size"]
fn roc_main_size() -> i64;
#[link_name = "main_1_Fx_caller"]
#[link_name = "Main_main_1_Fx_caller"]
fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> ();
#[link_name = "main_1_Fx_size"]
#[link_name = "Main_main_1_Fx_size"]
fn size_Fx() -> i64;
}

View File

@ -3,7 +3,7 @@ use roc_std::RocStr;
use std::str;
extern "C" {
#[link_name = "main_1_exposed"]
#[link_name = "Hello_main_1_exposed"]
fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
}

View File

@ -3,7 +3,7 @@ use roc_std::RocList;
use std::time::SystemTime;
extern "C" {
#[link_name = "quicksort_1_exposed"]
#[link_name = "Quicksort_quicksort_1_exposed"]
fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
}

View File

@ -3,7 +3,7 @@ use roc_std::RocList;
use std::time::SystemTime;
extern "C" {
#[link_name = "quicksort_1_exposed"]
#[link_name = "Quicksort_quicksort_1_exposed"]
fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
}

View File

@ -3,7 +3,7 @@ use roc_std::RocList;
use std::time::SystemTime;
extern "C" {
#[link_name = "quicksort_1_exposed"]
#[link_name = "Main_quicksort_1_exposed"]
fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
}

View File

@ -11,6 +11,14 @@ extern "C" {
const REFCOUNT_1: usize = isize::MIN as usize;
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RocOrder {
Eq = 0,
Gt = 1,
Lt = 2,
}
//#[macro_export]
//macro_rules! roclist {
// () => (

View File

@ -13,6 +13,8 @@ with {
isMacOS = builtins.currentSystem == "x86_64-darwin";
};
with (pkgs);
let
darwin-frameworks =
if isMacOS then
@ -27,43 +29,63 @@ let
]
else
[ ];
llvm = pkgs.llvm_10;
lld = pkgs.lld_10; # this should match llvm's version
clang = pkgs.clang_10; # this should match llvm's version
llvmPkg = pkgs.llvm_10;
lldPkg = pkgs.lld_10; # this should match llvm's version
clangPkg = pkgs.clang_10; # this should match llvm's version
zig = import ./nix/zig.nix { inherit pkgs isMacOS; };
inputs =
[
# build libraries
pkgs.rustc
pkgs.cargo
pkgs.clippy
pkgs.rustfmt
pkgs.cmake
pkgs.git
pkgs.python3
llvm
clang
pkgs.valgrind
pkgs.pkg-config
rustc
cargo
clippy
rustfmt
cmake
git
python3
llvmPkg
clangPkg
valgrind
pkg-config
zig
# llb deps
pkgs.libffi
pkgs.libxml2
pkgs.xorg.libX11
pkgs.zlib
libffi
libxml2
xorg.libX11
zlib
vulkan-headers
vulkan-loader
vulkan-tools
vulkan-validation-layers
# faster builds - see https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#use-lld-for-the-linker
lld
lldPkg
# dev tools
pkgs.rust-analyzer
rust-analyzer
# (import ./nix/zls.nix { inherit pkgs zig; })
pkgs.ccls
ccls
];
in pkgs.mkShell {
in mkShell {
buildInputs = inputs ++ darwin-frameworks;
LLVM_SYS_100_PREFIX = "${llvm}";
LLVM_SYS_100_PREFIX = "${llvmPkg}";
APPEND_LIBRARY_PATH = stdenv.lib.makeLibraryPath [
pkgconfig
vulkan-headers
vulkan-loader
vulkan-tools
vulkan-validation-layers
xorg.libX11
xorg.libXcursor
xorg.libXrandr
xorg.libXi
libcxx
libcxxabi
libunwind
];
# Aliases don't work cross shell, so we do this
shellHook = ''
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$APPEND_LIBRARY_PATH"
export PATH="$PATH:$PWD/nix/bin"
'';
}