Constrain + solve crash

This commit is contained in:
Ayaz Hafiz 2022-11-02 16:05:13 -05:00
parent 9dc489c2b0
commit e2b30e5301
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
13 changed files with 194 additions and 60 deletions

View file

@ -376,7 +376,10 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
*called_via, *called_via,
) )
} }
Crash => Crash, Crash { msg, ret_var } => Crash {
msg: Box::new(msg.map(|m| go_help!(m))),
ret_var: sub!(*ret_var),
},
RunLowLevel { op, args, ret_var } => RunLowLevel { RunLowLevel { op, args, ret_var } => RunLowLevel {
op: *op, op: *op,
args: args args: args

View file

@ -167,7 +167,10 @@ pub enum Expr {
EmptyRecord, EmptyRecord,
/// The "crash" keyword /// The "crash" keyword
Crash, Crash {
msg: Box<Loc<Expr>>,
ret_var: Variable,
},
/// Look up exactly one field on a record, e.g. (expr).foo. /// Look up exactly one field on a record, e.g. (expr).foo.
Access { Access {
@ -312,7 +315,7 @@ impl Expr {
} }
Self::Expect { .. } => Category::Expect, Self::Expect { .. } => Category::Expect,
Self::ExpectFx { .. } => Category::Expect, Self::ExpectFx { .. } => Category::Expect,
Self::Crash => Category::Crash, Self::Crash { .. } => Category::Crash,
Self::Dbg { .. } => Category::Expect, Self::Dbg { .. } => Category::Expect,
@ -788,6 +791,47 @@ pub fn canonicalize_expr<'a>(
} }
} }
} }
} else if let ast::Expr::Crash = loc_fn.value {
// We treat crash specially, since crashing must be applied with one argument.
debug_assert!(!args.is_empty());
let mut args = Vec::new();
let mut output = Output::default();
for loc_arg in loc_args.iter() {
let (arg_expr, arg_out) =
canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value);
args.push(arg_expr);
output.references.union_mut(&arg_out.references);
}
let crash = if args.len() > 1 {
let args_region = Region::span_across(
&loc_args.first().unwrap().region,
&loc_args.last().unwrap().region,
);
env.problem(Problem::OverAppliedCrash {
region: args_region,
});
// Still crash, just with our own message, and drop the references.
Crash {
msg: Box::new(Loc::at(
region,
Expr::Str(String::from("hit a crash!").into_boxed_str()),
)),
ret_var: var_store.fresh(),
}
} else {
let msg = args.pop().unwrap();
Crash {
msg: Box::new(msg),
ret_var: var_store.fresh(),
}
};
(crash, output)
} else { } else {
// Canonicalize the function expression and its arguments // Canonicalize the function expression and its arguments
let (fn_expr, fn_expr_output) = let (fn_expr, fn_expr_output) =
@ -878,7 +922,22 @@ pub fn canonicalize_expr<'a>(
(RuntimeError(problem), Output::default()) (RuntimeError(problem), Output::default())
} }
ast::Expr::Crash => (Crash, Output::default()), ast::Expr::Crash => {
// Naked crashes aren't allowed; we'll admit this with our own message, but yield an
// error.
env.problem(Problem::UnappliedCrash { region });
(
Crash {
msg: Box::new(Loc::at(
region,
Expr::Str(String::from("hit a crash!").into_boxed_str()),
)),
ret_var: var_store.fresh(),
},
Output::default(),
)
}
ast::Expr::Defs(loc_defs, loc_ret) => { ast::Expr::Defs(loc_defs, loc_ret) => {
// The body expression gets a new scope for canonicalization, // The body expression gets a new scope for canonicalization,
scope.inner_scope(|inner_scope| { scope.inner_scope(|inner_scope| {
@ -1726,7 +1785,7 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
| other @ TypedHole { .. } | other @ TypedHole { .. }
| other @ ForeignCall { .. } | other @ ForeignCall { .. }
| other @ OpaqueWrapFunction(_) | other @ OpaqueWrapFunction(_)
| other @ Crash => other, | other @ Crash { .. } => other,
List { List {
elem_var, elem_var,
@ -2834,8 +2893,7 @@ fn get_lookup_symbols(expr: &Expr) -> Vec<ExpectLookup> {
| Expr::EmptyRecord | Expr::EmptyRecord
| Expr::TypedHole(_) | Expr::TypedHole(_)
| Expr::RuntimeError(_) | Expr::RuntimeError(_)
| Expr::OpaqueWrapFunction(_) | Expr::OpaqueWrapFunction(_) => {}
| Expr::Crash => {}
} }
} }

View file

@ -981,6 +981,14 @@ fn fix_values_captured_in_closure_expr(
); );
} }
Crash { msg, ret_var: _ } => {
fix_values_captured_in_closure_expr(
&mut msg.value,
no_capture_symbols,
closure_captures,
);
}
Closure(ClosureData { Closure(ClosureData {
captured_symbols, captured_symbols,
name, name,
@ -1056,8 +1064,7 @@ fn fix_values_captured_in_closure_expr(
| TypedHole { .. } | TypedHole { .. }
| RuntimeError(_) | RuntimeError(_)
| ZeroArgumentTag { .. } | ZeroArgumentTag { .. }
| Accessor { .. } | Accessor { .. } => {}
| Crash => {}
List { loc_elems, .. } => { List { loc_elems, .. } => {
for elem in loc_elems.iter_mut() { for elem in loc_elems.iter_mut() {

View file

@ -185,7 +185,6 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
} }
Expr::Var(..) => { /* terminal */ } Expr::Var(..) => { /* terminal */ }
Expr::AbilityMember(..) => { /* terminal */ } Expr::AbilityMember(..) => { /* terminal */ }
Expr::Crash => { /* terminal */ }
Expr::If { Expr::If {
cond_var, cond_var,
branches, branches,
@ -204,6 +203,9 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
let (fn_var, loc_fn, _closure_var, _ret_var) = &**f; let (fn_var, loc_fn, _closure_var, _ret_var) = &**f;
walk_call(visitor, *fn_var, loc_fn, args); walk_call(visitor, *fn_var, loc_fn, args);
} }
Expr::Crash { msg, .. } => {
visitor.visit_expr(&msg.value, msg.region, Variable::STR);
}
Expr::RunLowLevel { Expr::RunLowLevel {
op: _, op: _,
args, args,

View file

@ -482,11 +482,17 @@ pub fn constrain_expr(
let and_constraint = constraints.and_constraint(and_cons); let and_constraint = constraints.and_constraint(and_cons);
constraints.exists(vars, and_constraint) constraints.exists(vars, and_constraint)
} }
Expr::Crash => { Expr::Crash { msg, ret_var } => {
let crash_type_index = constraints.push_type(Type::Crash); let expected_msg = Expected::ForReason(Reason::CrashArg, str_type(), msg.region);
let expected_index = constraints.push_expected_type(expected); let expected_ret = constraints.push_expected_type(expected);
constraints.equal_types(crash_type_index, expected_index, Category::Crash, region) let msg_is_str = constrain_expr(constraints, env, msg.region, &msg.value, expected_msg);
let magic =
constraints.equal_types_var(*ret_var, expected_ret, Category::Crash, region);
let and = constraints.and_constraint([msg_is_str, magic]);
constraints.exists([*ret_var], and)
} }
Var(symbol, variable) => { Var(symbol, variable) => {
// Save the expectation in the variable, then lookup the symbol's type in the environment // Save the expectation in the variable, then lookup the symbol's type in the environment

View file

@ -5562,7 +5562,7 @@ pub fn with_hole<'a>(
} }
TypedHole(_) => Stmt::RuntimeError("Hit a blank"), TypedHole(_) => Stmt::RuntimeError("Hit a blank"),
RuntimeError(e) => Stmt::RuntimeError(env.arena.alloc(e.runtime_message())), RuntimeError(e) => Stmt::RuntimeError(env.arena.alloc(e.runtime_message())),
Crash => todo!(), Crash { .. } => todo!(),
} }
} }

View file

@ -195,6 +195,12 @@ pub enum Problem {
type_got: u8, type_got: u8,
alias_kind: AliasKind, alias_kind: AliasKind,
}, },
UnappliedCrash {
region: Region,
},
OverAppliedCrash {
region: Region,
},
} }
impl Problem { impl Problem {
@ -325,7 +331,9 @@ impl Problem {
} }
| Problem::MultipleListRestPattern { region } | Problem::MultipleListRestPattern { region }
| Problem::BadTypeArguments { region, .. } | Problem::BadTypeArguments { region, .. }
| Problem::UnnecessaryOutputWildcard { region } => Some(*region), | Problem::UnnecessaryOutputWildcard { region }
| Problem::OverAppliedCrash { region }
| Problem::UnappliedCrash { region } => Some(*region),
Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries)) Problem::RuntimeError(RuntimeError::CircularDef(cycle_entries))
| Problem::BadRecursion(cycle_entries) => { | Problem::BadRecursion(cycle_entries) => {
cycle_entries.first().map(|entry| entry.expr_region) cycle_entries.first().map(|entry| entry.expr_region)

View file

@ -2961,27 +2961,6 @@ fn type_to_variable<'a>(
register_with_known_var(subs, destination, rank, pools, content) register_with_known_var(subs, destination, rank, pools, content)
} }
Crash => {
let magic_return = subs.fresh(Descriptor {
content: Content::FlexVar(None),
rank,
mark: Mark::NONE,
copy: OptVariable::NONE,
});
let magic_lambda_set = subs.fresh(Descriptor {
content: Content::FlexVar(None),
rank,
mark: Mark::NONE,
copy: OptVariable::NONE,
});
let magic_crash = Content::Structure(FlatType::Func(
Subs::STR_SLICE,
magic_lambda_set,
magic_return,
));
register_with_known_var(subs, destination, rank, pools, magic_crash)
}
}; };
} }

View file

@ -279,7 +279,7 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
) )
.group() .group()
), ),
Crash => f.text("crash"), Crash { .. } => todo!(),
ZeroArgumentTag { .. } => todo!(), ZeroArgumentTag { .. } => todo!(),
OpaqueRef { .. } => todo!(), OpaqueRef { .. } => todo!(),
Dbg { .. } => todo!(), Dbg { .. } => todo!(),

View file

@ -994,7 +994,6 @@ impl Types {
self.set_type_tag(index, TypeTag::RangedNumber(*range), Slice::default()) self.set_type_tag(index, TypeTag::RangedNumber(*range), Slice::default())
} }
Type::Error => self.set_type_tag(index, TypeTag::Error, Slice::default()), Type::Error => self.set_type_tag(index, TypeTag::Error, Slice::default()),
Type::Crash => todo!(),
} }
} }
@ -1669,7 +1668,6 @@ pub enum Type {
Apply(Symbol, Vec<Loc<Type>>, Region), Apply(Symbol, Vec<Loc<Type>>, Region),
Variable(Variable), Variable(Variable),
RangedNumber(NumericRange), RangedNumber(NumericRange),
Crash, // Str -> a
/// A type error, which will code gen to a runtime error /// A type error, which will code gen to a runtime error
Error, Error,
} }
@ -1712,7 +1710,6 @@ impl Clone for Type {
match self { match self {
Self::EmptyRec => Self::EmptyRec, Self::EmptyRec => Self::EmptyRec,
Self::EmptyTagUnion => Self::EmptyTagUnion, Self::EmptyTagUnion => Self::EmptyTagUnion,
Self::Crash => Self::Crash,
Self::Function(arg0, arg1, arg2) => { Self::Function(arg0, arg1, arg2) => {
Self::Function(arg0.clone(), arg1.clone(), arg2.clone()) Self::Function(arg0.clone(), arg1.clone(), arg2.clone())
} }
@ -2081,7 +2078,6 @@ impl fmt::Debug for Type {
Type::RangedNumber(range_vars) => { Type::RangedNumber(range_vars) => {
write!(f, "Ranged({:?})", range_vars) write!(f, "Ranged({:?})", range_vars)
} }
Type::Crash => write!(f, "Crash"),
Type::UnspecializedLambdaSet { unspecialized } => { Type::UnspecializedLambdaSet { unspecialized } => {
write!(f, "{:?}", unspecialized) write!(f, "{:?}", unspecialized)
} }
@ -2245,7 +2241,7 @@ impl Type {
); );
} }
EmptyRec | EmptyTagUnion | Crash | Error => {} EmptyRec | EmptyTagUnion | Error => {}
} }
} }
} }
@ -2375,7 +2371,7 @@ impl Type {
); );
} }
EmptyRec | EmptyTagUnion | Crash | Error => {} EmptyRec | EmptyTagUnion | Error => {}
} }
} }
} }
@ -2478,7 +2474,7 @@ impl Type {
} }
RangedNumber(_) => Ok(()), RangedNumber(_) => Ok(()),
UnspecializedLambdaSet { .. } => Ok(()), UnspecializedLambdaSet { .. } => Ok(()),
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Error | Variable(_) | Crash => Ok(()), EmptyRec | EmptyTagUnion | ClosureTag { .. } | Error | Variable(_) => Ok(()),
} }
} }
@ -2540,7 +2536,7 @@ impl Type {
UnspecializedLambdaSet { UnspecializedLambdaSet {
unspecialized: Uls(_, sym, _), unspecialized: Uls(_, sym, _),
} => *sym == rep_symbol, } => *sym == rep_symbol,
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Error | Variable(_) | Crash => false, EmptyRec | EmptyTagUnion | ClosureTag { .. } | Error | Variable(_) => false,
} }
} }
@ -2596,7 +2592,7 @@ impl Type {
.iter() .iter()
.any(|arg| arg.value.contains_variable(rep_variable)), .any(|arg| arg.value.contains_variable(rep_variable)),
RangedNumber(_) => false, RangedNumber(_) => false,
EmptyRec | EmptyTagUnion | Error | Crash => false, EmptyRec | EmptyTagUnion | Error => false,
} }
} }
@ -2970,7 +2966,7 @@ impl Type {
} }
RangedNumber(_) => {} RangedNumber(_) => {}
UnspecializedLambdaSet { .. } => {} UnspecializedLambdaSet { .. } => {}
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Error | Variable(_) | Crash => {} EmptyRec | EmptyTagUnion | ClosureTag { .. } | Error | Variable(_) => {}
} }
} }
@ -3106,7 +3102,7 @@ fn symbols_help(initial: &Type) -> Vec<Symbol> {
} => { } => {
// ignore the member symbol because unspecialized lambda sets are internal-only // ignore the member symbol because unspecialized lambda sets are internal-only
} }
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Error | Variable(_) | Crash => {} EmptyRec | EmptyTagUnion | ClosureTag { .. } | Error | Variable(_) => {}
} }
} }
@ -3222,7 +3218,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
} }
variables_help(actual, accum); variables_help(actual, accum);
} }
RangedNumber(_) | Crash => {} RangedNumber(_) => {}
Apply(_, args, _) => { Apply(_, args, _) => {
for x in args { for x in args {
variables_help(&x.value, accum); variables_help(&x.value, accum);
@ -3250,7 +3246,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
use Type::*; use Type::*;
match tipe { match tipe {
EmptyRec | EmptyTagUnion | Error | Crash => (), EmptyRec | EmptyTagUnion | Error => (),
Variable(v) => { Variable(v) => {
accum.type_variables.insert(*v); accum.type_variables.insert(*v);
@ -3490,6 +3486,7 @@ pub enum Reason {
member_name: Symbol, member_name: Symbol,
def_region: Region, def_region: Region,
}, },
CrashArg,
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
@ -4377,7 +4374,6 @@ fn instantiate_lambda_sets_as_unspecialized(
match typ { match typ {
Type::EmptyRec => {} Type::EmptyRec => {}
Type::EmptyTagUnion => {} Type::EmptyTagUnion => {}
Type::Crash => {}
Type::Function(args, lambda_set, ret) => { Type::Function(args, lambda_set, ret) => {
debug_assert!( debug_assert!(
matches!(**lambda_set, Type::Variable(..)), matches!(**lambda_set, Type::Variable(..)),

View file

@ -1086,7 +1086,36 @@ pub fn can_problem<'b>(
} else { } else {
"TOO FEW TYPE ARGUMENTS".to_string() "TOO FEW TYPE ARGUMENTS".to_string()
}; };
severity = Severity::RuntimeError;
}
Problem::UnappliedCrash { region } => {
doc = alloc.stack([
alloc.concat([
alloc.reflow("This "), alloc.keyword("crash"), alloc.reflow(" doesn't have a message given to it:")
]),
alloc.region(lines.convert_region(region)),
alloc.concat([
alloc.keyword("crash"), alloc.reflow(" must be passed a message to crash with at the exact place it's used. "),
alloc.keyword("crash"), alloc.reflow(" can't be used as a value that's passed around, like functions can be - it must be applied immediately!"),
])
]);
title = "UNAPPLIED CRASH".to_string();
severity = Severity::RuntimeError;
}
Problem::OverAppliedCrash { region } => {
doc = alloc.stack([
alloc.concat([
alloc.reflow("This "),
alloc.keyword("crash"),
alloc.reflow(" has too many values given to it:"),
]),
alloc.region(lines.convert_region(region)),
alloc.concat([
alloc.keyword("crash"),
alloc.reflow(" must be given exacly one message to crash with."),
]),
]);
title = "OVERAPPLIED CRASH".to_string();
severity = Severity::RuntimeError; severity = Severity::RuntimeError;
} }
}; };

View file

@ -1375,6 +1375,42 @@ fn to_expr_report<'b>(
} }
} }
Reason::CrashArg => {
let this_is = alloc.reflow("The value is");
let wanted = alloc.concat([
alloc.reflow("But I can only "),
alloc.keyword("crash"),
alloc.reflow(" with messages of type"),
]);
let details = None;
let lines = [
alloc
.reflow("This value passed to ")
.append(alloc.keyword("crash"))
.append(alloc.reflow(" is not a string:")),
alloc.region(lines.convert_region(region)),
type_comparison(
alloc,
found,
expected_type,
ExpectationContext::WhenCondition,
add_category(alloc, this_is, &category),
wanted,
details,
),
];
Report {
filename,
title: "TYPE MISMATCH".to_string(),
doc: alloc.stack(lines),
severity: Severity::RuntimeError,
}
}
Reason::LowLevelOpArg { op, arg_index } => { Reason::LowLevelOpArg { op, arg_index } => {
panic!( panic!(
"Compiler bug: argument #{} to low-level operation {:?} was the wrong type!", "Compiler bug: argument #{} to low-level operation {:?} was the wrong type!",

View file

@ -12431,16 +12431,16 @@ All branches in an `if` must have the same type!
@r###" @r###"
TYPE MISMATCH /code/proj/Main.roc TYPE MISMATCH /code/proj/Main.roc
This 1st argument to this function has an unexpected type: This value passed to `crash` is not a string:
4 crash {} 4 crash {}
^^ ^^
The argument is a record of type: The value is a record of type:
{} {}
But this function needs its 1st argument to be: But I can only `crash` with messages of type
Str Str
"### "###
@ -12454,6 +12454,16 @@ All branches in an `if` must have the same type!
"# "#
), ),
@r###" @r###"
UNAPPLIED CRASH /code/proj/Main.roc
This `crash` doesn't have a message given to it:
4 crash
^^^^^
`crash` must be passed a message to crash with at the exact place it's
used. `crash` can't be used as a value that's passed around, like
functions can be - it must be applied immediately!
"### "###
); );
@ -12465,14 +12475,14 @@ All branches in an `if` must have the same type!
"# "#
), ),
@r###" @r###"
TOO MANY ARGS /code/proj/Main.roc OVERAPPLIED CRASH /code/proj/Main.roc
This function expects 1 argument, but it got 2 instead: This `crash` has too many values given to it:
4 crash "" "" 4 crash "" ""
^^^^^ ^^^^^
Are there any missing commas? Or missing parentheses? `crash` must be given exacly one message to crash with.
"### "###
); );
} }