mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-22 00:09:33 +03:00
commit
b517528a09
@ -158,6 +158,9 @@ pub enum Expr {
|
||||
arguments: Vec<(Variable, Located<Expr>)>,
|
||||
},
|
||||
|
||||
// Test
|
||||
Expect(Box<Located<Expr>>, Box<Located<Expr>>),
|
||||
|
||||
// Compiles, but will crash if reached
|
||||
RuntimeError(RuntimeError),
|
||||
}
|
||||
@ -649,9 +652,30 @@ pub fn canonicalize_expr<'a>(
|
||||
Output::default(),
|
||||
)
|
||||
}
|
||||
ast::Expr::Expect(_condition, _continuation) => todo!(),
|
||||
ast::Expr::Expect(condition, continuation) => {
|
||||
let mut output = Output::default();
|
||||
|
||||
let (loc_condition, output1) =
|
||||
canonicalize_expr(env, var_store, scope, condition.region, &condition.value);
|
||||
|
||||
let (loc_continuation, output2) = canonicalize_expr(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
continuation.region,
|
||||
&continuation.value,
|
||||
);
|
||||
|
||||
output.union(output1);
|
||||
output.union(output2);
|
||||
|
||||
(
|
||||
Expect(Box::new(loc_condition), Box::new(loc_continuation)),
|
||||
output,
|
||||
)
|
||||
}
|
||||
ast::Expr::If(if_thens, final_else_branch) => {
|
||||
let mut branches = Vec::with_capacity(1);
|
||||
let mut branches = Vec::with_capacity(if_thens.len());
|
||||
let mut output = Output::default();
|
||||
|
||||
for (condition, then_branch) in if_thens.iter() {
|
||||
@ -1282,6 +1306,20 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||
}
|
||||
}
|
||||
|
||||
Expect(loc_condition, loc_expr) => {
|
||||
let loc_condition = Located {
|
||||
region: loc_condition.region,
|
||||
value: inline_calls(var_store, scope, loc_condition.value),
|
||||
};
|
||||
|
||||
let loc_expr = Located {
|
||||
region: loc_expr.region,
|
||||
value: inline_calls(var_store, scope, loc_expr.value),
|
||||
};
|
||||
|
||||
Expect(Box::new(loc_condition), Box::new(loc_expr))
|
||||
}
|
||||
|
||||
LetRec(defs, loc_expr, var) => {
|
||||
let mut new_defs = Vec::with_capacity(defs.len());
|
||||
|
||||
|
@ -405,6 +405,11 @@ fn fix_values_captured_in_closure_expr(
|
||||
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
|
||||
}
|
||||
|
||||
Expect(condition, loc_expr) => {
|
||||
fix_values_captured_in_closure_expr(&mut condition.value, no_capture_symbols);
|
||||
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
|
||||
}
|
||||
|
||||
Closure {
|
||||
captured_symbols,
|
||||
name,
|
||||
|
@ -406,6 +406,25 @@ pub fn constrain_expr(
|
||||
)
|
||||
}
|
||||
|
||||
Expect(loc_cond, continuation) => {
|
||||
let expect_bool = |region| {
|
||||
let bool_type = Type::Variable(Variable::BOOL);
|
||||
Expected::ForReason(Reason::ExpectCondition, bool_type, region)
|
||||
};
|
||||
|
||||
let cond_con = constrain_expr(
|
||||
env,
|
||||
loc_cond.region,
|
||||
&loc_cond.value,
|
||||
expect_bool(loc_cond.region),
|
||||
);
|
||||
|
||||
let continuation_con =
|
||||
constrain_expr(env, continuation.region, &continuation.value, expected);
|
||||
|
||||
exists(vec![], And(vec![cond_con, continuation_con]))
|
||||
}
|
||||
|
||||
If {
|
||||
cond_var,
|
||||
branch_var,
|
||||
@ -759,9 +778,22 @@ pub fn constrain_expr(
|
||||
)
|
||||
}
|
||||
LetNonRec(def, loc_ret, var) => {
|
||||
let body_con = constrain_expr(env, loc_ret.region, &loc_ret.value, expected.clone());
|
||||
let mut stack = Vec::with_capacity(1);
|
||||
|
||||
exists(
|
||||
let mut loc_ret = loc_ret;
|
||||
|
||||
stack.push((def, var, loc_ret.region));
|
||||
|
||||
while let LetNonRec(def, new_loc_ret, var) = &loc_ret.value {
|
||||
stack.push((def, var, new_loc_ret.region));
|
||||
loc_ret = new_loc_ret;
|
||||
}
|
||||
|
||||
let mut body_con =
|
||||
constrain_expr(env, loc_ret.region, &loc_ret.value, expected.clone());
|
||||
|
||||
while let Some((def, var, ret_region)) = stack.pop() {
|
||||
body_con = exists(
|
||||
vec![*var],
|
||||
And(vec![
|
||||
constrain_def(env, def, body_con),
|
||||
@ -769,13 +801,16 @@ pub fn constrain_expr(
|
||||
// Code gen will need that later!
|
||||
Eq(
|
||||
Type::Variable(*var),
|
||||
expected,
|
||||
expected.clone(),
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
loc_ret.region,
|
||||
ret_region,
|
||||
),
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
body_con
|
||||
}
|
||||
Tag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
|
@ -4430,6 +4430,34 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||
_ => unreachable!("invalid dict layout"),
|
||||
}
|
||||
}
|
||||
ExpectTrue => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let context = env.context;
|
||||
let bd = env.builder;
|
||||
|
||||
let (cond, _cond_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
let condition = bd.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
cond.into_int_value(),
|
||||
context.bool_type().const_int(1, false),
|
||||
"is_true",
|
||||
);
|
||||
|
||||
let then_block = context.append_basic_block(parent, "then_block");
|
||||
let throw_block = context.append_basic_block(parent, "throw_block");
|
||||
|
||||
bd.build_conditional_branch(condition, then_block, throw_block);
|
||||
|
||||
bd.position_at_end(throw_block);
|
||||
|
||||
throw_exception(env, "assert failed!");
|
||||
|
||||
bd.position_at_end(then_block);
|
||||
|
||||
cond
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,4 +97,5 @@ pub enum LowLevel {
|
||||
Or,
|
||||
Not,
|
||||
Hash,
|
||||
ExpectTrue,
|
||||
}
|
||||
|
@ -694,5 +694,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||
DictWalk => arena.alloc_slice_copy(&[owned, borrowed, owned]),
|
||||
|
||||
SetFromList => arena.alloc_slice_copy(&[owned]),
|
||||
|
||||
ExpectTrue => arena.alloc_slice_copy(&[irrelevant]),
|
||||
}
|
||||
}
|
||||
|
@ -728,7 +728,13 @@ impl<'a> Context<'a> {
|
||||
|
||||
// the result of an invoke should not be touched in the fail branch
|
||||
// but it should be present in the pass branch (otherwise it would be dead)
|
||||
debug_assert!(case_live_vars.contains(symbol));
|
||||
// NOTE: we cheat a bit here to allow `invoke` when generating code for `expect`
|
||||
let is_dead = !case_live_vars.contains(symbol);
|
||||
|
||||
if is_dead && layout.is_refcounted() {
|
||||
panic!("A variable of a reference-counted layout is dead; that's a bug!");
|
||||
}
|
||||
|
||||
case_live_vars.remove(symbol);
|
||||
|
||||
let fail = {
|
||||
@ -738,7 +744,9 @@ impl<'a> Context<'a> {
|
||||
ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b)
|
||||
};
|
||||
|
||||
if !is_dead {
|
||||
case_live_vars.insert(*symbol);
|
||||
}
|
||||
|
||||
let pass = {
|
||||
// TODO should we use ctor info like Lean?
|
||||
|
@ -3260,6 +3260,8 @@ pub fn with_hole<'a>(
|
||||
|
||||
EmptyRecord => Stmt::Let(assigned, Expr::Struct(&[]), Layout::Struct(&[]), hole),
|
||||
|
||||
Expect(_, _) => unreachable!("I think this is unreachable"),
|
||||
|
||||
If {
|
||||
cond_var,
|
||||
branch_var,
|
||||
@ -4290,6 +4292,41 @@ pub fn from_can<'a>(
|
||||
|
||||
stmt
|
||||
}
|
||||
|
||||
Expect(condition, rest) => {
|
||||
let rest = from_can(env, variable, rest.value, procs, layout_cache);
|
||||
|
||||
let bool_layout = Layout::Builtin(Builtin::Int1);
|
||||
let cond_symbol = env.unique_symbol();
|
||||
|
||||
let call_type = CallType::LowLevel {
|
||||
op: LowLevel::ExpectTrue,
|
||||
};
|
||||
let arguments = env.arena.alloc([cond_symbol]);
|
||||
let call = self::Call {
|
||||
call_type,
|
||||
arguments,
|
||||
};
|
||||
|
||||
let rest = Stmt::Invoke {
|
||||
symbol: env.unique_symbol(),
|
||||
call,
|
||||
layout: bool_layout,
|
||||
pass: env.arena.alloc(rest),
|
||||
fail: env.arena.alloc(Stmt::Rethrow),
|
||||
};
|
||||
|
||||
with_hole(
|
||||
env,
|
||||
condition.value,
|
||||
variable,
|
||||
procs,
|
||||
layout_cache,
|
||||
cond_symbol,
|
||||
env.arena.alloc(rest),
|
||||
)
|
||||
}
|
||||
|
||||
LetRec(defs, cont, _) => {
|
||||
// because Roc is strict, only functions can be recursive!
|
||||
for def in defs.into_iter() {
|
||||
|
@ -354,6 +354,44 @@ fn to_expr_report<'b>(
|
||||
}
|
||||
}
|
||||
Expected::ForReason(reason, expected_type, region) => match reason {
|
||||
Reason::ExpectCondition => {
|
||||
let problem = alloc.concat(vec![
|
||||
alloc.text("This "),
|
||||
alloc.keyword("expect"),
|
||||
alloc.text(" condition needs to be a "),
|
||||
alloc.type_str("Bool"),
|
||||
alloc.text(":"),
|
||||
]);
|
||||
|
||||
report_bad_type(
|
||||
alloc,
|
||||
filename,
|
||||
&category,
|
||||
found,
|
||||
expected_type,
|
||||
region,
|
||||
Some(expr_region),
|
||||
problem,
|
||||
alloc.text("Right now it’s"),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("But I need every "),
|
||||
alloc.keyword("expect"),
|
||||
alloc.reflow(" condition to evaluate to a "),
|
||||
alloc.type_str("Bool"),
|
||||
alloc.reflow("—either "),
|
||||
alloc.global_tag_name("True".into()),
|
||||
alloc.reflow(" or "),
|
||||
alloc.global_tag_name("False".into()),
|
||||
alloc.reflow("."),
|
||||
]),
|
||||
// Note: Elm has a hint here about truthiness. I think that
|
||||
// makes sense for Elm, since most Elm users will come from
|
||||
// JS, where truthiness is a thing. I don't really know
|
||||
// what the background of Roc programmers will be, and I'd
|
||||
// rather not create a distraction by introducing a term
|
||||
// they don't know. ("Wait, what's truthiness?")
|
||||
)
|
||||
}
|
||||
Reason::IfCondition => {
|
||||
let problem = alloc.concat(vec![
|
||||
alloc.text("This "),
|
||||
|
@ -6357,4 +6357,34 @@ mod test_reporting {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expect_expr_type_error() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
expect "foobar"
|
||||
|
||||
4
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
This `expect` condition needs to be a Bool:
|
||||
|
||||
1│ expect "foobar"
|
||||
^^^^^^^^
|
||||
|
||||
Right now it’s a string of type:
|
||||
|
||||
Str
|
||||
|
||||
But I need every `expect` condition to evaluate to a Bool—either `True`
|
||||
or `False`.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2296,3 +2296,19 @@ fn call_invalid_layout() {
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "assert failed!")]
|
||||
fn expect_fail() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
expect 1 == 2
|
||||
|
||||
3
|
||||
"#
|
||||
),
|
||||
3,
|
||||
i64
|
||||
);
|
||||
}
|
||||
|
@ -891,6 +891,7 @@ pub enum Reason {
|
||||
index: Index,
|
||||
},
|
||||
WhenGuard,
|
||||
ExpectCondition,
|
||||
IfCondition,
|
||||
IfBranch {
|
||||
index: Index,
|
||||
|
Loading…
Reference in New Issue
Block a user