Merge remote-tracking branch 'origin/trunk' into red-black-tree

This commit is contained in:
Folkert 2020-11-10 14:09:08 +01:00
commit 3f658c4b98
5 changed files with 849 additions and 407 deletions

View file

@ -70,7 +70,7 @@ pub fn run_cmd(cmd_name: &str, args: &[&str]) -> Out {
let output = cmd let output = cmd
.output() .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 { Out {
stdout: String::from_utf8(output.stdout).unwrap(), stdout: String::from_utf8(output.stdout).unwrap(),
@ -263,12 +263,12 @@ pub fn repl_eval(input: &str) -> Out {
// Evaluate the expression // Evaluate the expression
stdin stdin
.write_all("\n".as_bytes()) .write_all(b"\n")
.expect("Failed to write newline to stdin"); .expect("Failed to write newline to stdin");
// Gracefully exit the repl // Gracefully exit the repl
stdin stdin
.write_all(":exit\n".as_bytes()) .write_all(b":exit\n")
.expect("Failed to write :exit to stdin"); .expect("Failed to write :exit to stdin");
} }

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![],
}
}

View file

@ -8,7 +8,7 @@ use roc_builtins::std::{Mode, StdLib};
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
use roc_can::def::Declaration; use roc_can::def::Declaration;
use roc_can::module::{canonicalize_module_defs, Module}; use roc_can::module::{canonicalize_module_defs, Module};
use roc_collections::all::{default_hasher, MutMap, MutSet, SendMap}; use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_constrain::module::{ use roc_constrain::module::{
constrain_imports, pre_constrain_imports, ConstrainableImports, Import, constrain_imports, pre_constrain_imports, ConstrainableImports, Import,
}; };
@ -2331,399 +2331,6 @@ fn run_solve<'a>(
} }
} }
fn fabricate_effect_after(
env: &mut roc_can::env::Env,
scope: &mut roc_can::scope::Scope,
effect_symbol: Symbol,
effect_tag_name: TagName,
var_store: &mut VarStore,
) -> (Symbol, roc_can::def::Def) {
use roc_can::expr::Expr;
use roc_can::expr::Recursive;
use roc_module::operator::CalledVia;
let thunk_symbol = {
scope
.introduce(
"thunk".into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap()
};
let to_effect_symbol = {
scope
.introduce(
"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)
};
use roc_can::pattern::Pattern;
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 = roc_can::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)
}
fn fabricate_host_exposed_def(
env: &mut roc_can::env::Env,
scope: &mut roc_can::scope::Scope,
symbol: Symbol,
ident: &str,
effect_tag_name: TagName,
var_store: &mut VarStore,
annotation: roc_can::annotation::Annotation,
) -> roc_can::def::Def {
use roc_can::pattern::Pattern;
let expr_var = var_store.fresh();
let pattern = Pattern::Identifier(symbol);
let mut pattern_vars = SendMap::default();
pattern_vars.insert(symbol, expr_var);
use roc_can::expr::Expr;
use roc_can::expr::Recursive;
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 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)),
};
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(),
};
roc_can::def::Def {
loc_pattern: Located::at_zero(pattern),
loc_expr: Located::at_zero(def_body),
expr_var,
pattern_vars,
annotation: Some(def_annotation),
}
}
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),
)
}
fn unpack_exposes_entries<'a>(
arena: &'a Bump,
entries: &'a [Located<TypedIdent<'a>>],
) -> bumpalo::collections::Vec<'a, (&'a Located<&'a str>, &'a Located<TypeAnnotation<'a>>)> {
use bumpalo::collections::Vec;
let mut stack: Vec<&TypedIdent> = Vec::with_capacity_in(entries.len(), arena);
let mut output = Vec::with_capacity_in(entries.len(), arena);
for entry in entries.iter() {
stack.push(&entry.value);
}
while let Some(effects_entry) = stack.pop() {
match effects_entry {
TypedIdent::Entry {
ident,
spaces_before_colon: _,
ann,
} => {
output.push((ident, ann));
}
TypedIdent::SpaceAfter(nested, _) | TypedIdent::SpaceBefore(nested, _) => {
stack.push(nested);
}
}
}
output
}
fn fabricate_effects_module<'a>( fn fabricate_effects_module<'a>(
arena: &'a Bump, arena: &'a Bump,
module_ids: Arc<Mutex<ModuleIds>>, module_ids: Arc<Mutex<ModuleIds>>,
@ -2742,7 +2349,15 @@ fn fabricate_effects_module<'a>(
let name = effects.type_name; let name = effects.type_name;
let declared_name: ModuleName = name.into(); let declared_name: ModuleName = name.into();
let hardcoded_exposed_functions = vec![name, "after"]; let hardcoded_effect_symbols = {
let mut functions: Vec<_> = crate::effect_module::BUILTIN_EFFECT_FUNCTIONS
.iter()
.map(|x| x.0)
.collect();
functions.push(name);
functions
};
let exposed_ident_ids = { let exposed_ident_ids = {
// Lock just long enough to perform the minimal operations necessary. // Lock just long enough to perform the minimal operations necessary.
@ -2778,7 +2393,7 @@ fn fabricate_effects_module<'a>(
exposed.push(symbol); exposed.push(symbol);
} }
for hardcoded in hardcoded_exposed_functions { for hardcoded in hardcoded_effect_symbols {
// Use get_or_insert here because the ident_ids may already // Use get_or_insert here because the ident_ids may already
// created an IdentId for this, when it was imported exposed // created an IdentId for this, when it was imported exposed
// in a dependent module. // in a dependent module.
@ -2824,8 +2439,8 @@ fn fabricate_effects_module<'a>(
let alias = { let alias = {
let a_var = var_store.fresh(); let a_var = var_store.fresh();
let actual = build_effect_actual( let actual = crate::effect_module::build_effect_actual(
effect_tag_name.clone(), effect_tag_name,
Type::Variable(a_var), Type::Variable(a_var),
&mut var_store, &mut var_store,
); );
@ -2868,7 +2483,7 @@ fn fabricate_effects_module<'a>(
&mut var_store, &mut var_store,
); );
let def = fabricate_host_exposed_def( let def = crate::effect_module::build_host_exposed_def(
&mut can_env, &mut can_env,
&mut scope, &mut scope,
symbol, symbol,
@ -2884,15 +2499,15 @@ fn fabricate_effects_module<'a>(
} }
} }
let (effect_after_symbol, def) = fabricate_effect_after( // define Effect.after, Effect.map etc.
crate::effect_module::build_effect_builtins(
&mut can_env, &mut can_env,
&mut scope, &mut scope,
effect_symbol, effect_symbol,
effect_tag_name,
&mut var_store, &mut var_store,
&mut exposed_vars_by_symbol,
&mut declarations,
); );
exposed_vars_by_symbol.push((effect_after_symbol, def.expr_var));
declarations.push(Declaration::Declare(def));
exposed_vars_by_symbol exposed_vars_by_symbol
}; };
@ -2950,6 +2565,37 @@ fn fabricate_effects_module<'a>(
)) ))
} }
fn unpack_exposes_entries<'a>(
arena: &'a Bump,
entries: &'a [Located<TypedIdent<'a>>],
) -> bumpalo::collections::Vec<'a, (&'a Located<&'a str>, &'a Located<TypeAnnotation<'a>>)> {
use bumpalo::collections::Vec;
let mut stack: Vec<&TypedIdent> = Vec::with_capacity_in(entries.len(), arena);
let mut output = Vec::with_capacity_in(entries.len(), arena);
for entry in entries.iter() {
stack.push(&entry.value);
}
while let Some(effects_entry) = stack.pop() {
match effects_entry {
TypedIdent::Entry {
ident,
spaces_before_colon: _,
ann,
} => {
output.push((ident, ann));
}
TypedIdent::SpaceAfter(nested, _) | TypedIdent::SpaceBefore(nested, _) => {
stack.push(nested);
}
}
}
output
}
fn canonicalize_and_constrain<'a>( fn canonicalize_and_constrain<'a>(
arena: &'a Bump, arena: &'a Bump,
module_ids: &ModuleIds, module_ids: &ModuleIds,

View file

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

View file

@ -2,6 +2,8 @@ app Main provides [ main ] imports [ Effect ]
main : Effect.Effect {} as Fx main : Effect.Effect {} as Fx
main = main =
Effect.putLine "Write a thing!" Effect.always "Write a thing"
|> Effect.map (\line -> Str.concat line "!")
|> Effect.after (\line -> Effect.putLine line)
|> Effect.after (\{} -> Effect.getLine) |> Effect.after (\{} -> Effect.getLine)
|> Effect.after (\line -> Effect.putLine line) |> Effect.after (\line -> Effect.putLine line)