Merge pull request #1226 from rtfeldman/expr-expect

Expr expect
This commit is contained in:
Richard Feldman 2021-04-23 20:57:02 -04:00 committed by GitHub
commit b517528a09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 258 additions and 19 deletions

View File

@ -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());

View File

@ -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,

View File

@ -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,

View File

@ -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
}
}
}

View File

@ -97,4 +97,5 @@ pub enum LowLevel {
Or,
Not,
Hash,
ExpectTrue,
}

View File

@ -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]),
}
}

View File

@ -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?

View File

@ -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() {

View File

@ -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 its"),
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 "),

View File

@ -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 its a string of type:
Str
But I need every `expect` condition to evaluate to a Booleither `True`
or `False`.
"#
),
)
}
}

View File

@ -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
);
}

View File

@ -891,6 +891,7 @@ pub enum Reason {
index: Index,
},
WhenGuard,
ExpectCondition,
IfCondition,
IfBranch {
index: Index,