Turn invalid record field types into runtime errors

By emitting a runtime error rather than panicking when we can't layout
a record, we help programs like

```
main =
    get = \{a} -> a
    get {b: "hello world"}
```

execute as

```
Mismatch in compiler/unify/src/unify.rs Line 1071 Column 13
Trying to unify two flat types that are incompatible: EmptyRecord ~ { 'a' : Demanded(122), }<130>

🔨 Rebuilding host...
── TYPE MISMATCH ───────────────────────────────────────────────────────────────

The 1st argument to get is not what I expect:

8│      get {b: "hello world"}
            ^^^^^^^^^^^^^^^^^^

This argument is a record of type:

    { b : Str }

But get needs the 1st argument to be:

    { a : a }b

Tip: Seems like a record field typo. Maybe a should be b?

Tip: Can more type annotations be added? Type annotations always help
me give more specific messages, and I think they could help a lot in
this case

────────────────────────────────────────────────────────────────────────────────

'+fast-variable-shuffle' is not a recognized feature for this target (ignoring feature)
'+fast-variable-shuffle' is not a recognized feature for this target (ignoring feature)
Done!
Application crashed with message

    Can't create record with improper layout

Shutting down
```

rather than the hanging

```
Mismatch in compiler/unify/src/unify.rs Line 1071 Column 13
Trying to unify two flat types that are incompatible: EmptyRecord ~ { 'a' : Demanded(122), }<130>

thread '<unnamed>' panicked at 'invalid layout from var: UnresolvedTypeVar(104)', compiler/mono/s
rc/layout.rs:1510:52
```

that was previously produced.

Part of #2227
This commit is contained in:
ayazhafiz 2021-12-21 19:11:59 -06:00
parent e728e319c6
commit 576f1293fd
7 changed files with 96 additions and 51 deletions

View file

@ -5,6 +5,7 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{default_hasher, MutMap};
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{Interns, Symbol};
use roc_problem::can::RuntimeError;
use roc_types::subs::{
Content, FlatType, RecordFields, Subs, UnionTags, Variable, VariableSubsSlice,
};
@ -31,6 +32,15 @@ pub enum LayoutProblem {
Erroneous,
}
impl Into<RuntimeError> for LayoutProblem {
fn into(self) -> RuntimeError {
match self {
LayoutProblem::UnresolvedTypeVar(_) => RuntimeError::UnresolvedTypeVar,
LayoutProblem::Erroneous => RuntimeError::ErroneousType,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RawFunctionLayout<'a> {
Function(&'a [Layout<'a>], LambdaSet<'a>, &'a Layout<'a>),
@ -1495,23 +1505,17 @@ fn layout_from_flat_type<'a>(
Record(fields, ext_var) => {
// extract any values from the ext_var
let pairs_it = fields
.unsorted_iterator(subs, ext_var)
.filter_map(|(label, field)| {
// drop optional fields
let var = match field {
RecordField::Optional(_) => return None,
RecordField::Required(var) => var,
RecordField::Demanded(var) => var,
};
let mut pairs = Vec::with_capacity_in(fields.len(), arena);
for (label, field) in fields.unsorted_iterator(subs, ext_var) {
// drop optional fields
let var = match field {
RecordField::Optional(_) => continue,
RecordField::Required(var) => var,
RecordField::Demanded(var) => var,
};
Some((
label,
Layout::from_var(env, var).expect("invalid layout from var"),
))
});
let mut pairs = Vec::from_iter_in(pairs_it, arena);
pairs.push((label, Layout::from_var(env, var)?));
}
pairs.sort_by(|(label1, layout1), (label2, layout2)| {
let size1 = layout1.alignment_bytes(ptr_bytes);
@ -1635,7 +1639,7 @@ pub fn sort_record_fields<'a>(
var: Variable,
subs: &Subs,
ptr_bytes: u32,
) -> Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)> {
) -> Result<Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)>, LayoutProblem> {
let mut env = Env {
arena,
subs,
@ -1643,7 +1647,10 @@ pub fn sort_record_fields<'a>(
ptr_bytes,
};
let (it, _) = gather_fields_unsorted_iter(subs, RecordFields::empty(), var);
let (it, _) = match gather_fields_unsorted_iter(subs, RecordFields::empty(), var) {
Ok(it) => it,
Err(_) => return Err(LayoutProblem::Erroneous),
};
let it = it
.into_iter()
@ -1655,24 +1662,25 @@ pub fn sort_record_fields<'a>(
fn sort_record_fields_help<'a>(
env: &mut Env<'a, '_>,
fields_map: impl Iterator<Item = (Lowercase, RecordField<Variable>)>,
) -> Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)> {
) -> Result<Vec<'a, (Lowercase, Variable, Result<Layout<'a>, Layout<'a>>)>, LayoutProblem> {
let ptr_bytes = env.ptr_bytes;
// Sort the fields by label
let mut sorted_fields = Vec::with_capacity_in(fields_map.size_hint().0, env.arena);
for (label, field) in fields_map {
// TODO: make prettier by pulling out Ok/Err
let var = match field {
RecordField::Demanded(v) => v,
RecordField::Required(v) => v,
RecordField::Optional(v) => {
let layout = Layout::from_var(env, v).expect("invalid layout from var");
let layout = Layout::from_var(env, v)?;
sorted_fields.push((label, v, Err(layout)));
continue;
}
};
let layout = Layout::from_var(env, var).expect("invalid layout from var");
let layout = Layout::from_var(env, var)?;
sorted_fields.push((label, var, Ok(layout)));
}
@ -1690,7 +1698,7 @@ fn sort_record_fields_help<'a>(
},
);
sorted_fields
Ok(sorted_fields)
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
@ -2428,7 +2436,10 @@ fn layout_from_tag_union<'a>(
#[cfg(debug_assertions)]
pub fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool {
// the ext_var is empty
let fields = roc_types::types::gather_fields(subs, RecordFields::empty(), ext_var);
let fields = match roc_types::types::gather_fields(subs, RecordFields::empty(), ext_var) {
Ok(fields) => fields,
Err(_) => return false,
};
fields.fields.is_empty()
}