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

@ -765,7 +765,8 @@ fn type_to_variable<'a>(
let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext);
let (it, new_ext_var) = let (it, new_ext_var) =
gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var); gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var)
.expect("Something ended up weird in this record type");
let it = it let it = it
.into_iter() .into_iter()

View file

@ -3325,8 +3325,15 @@ pub fn with_hole<'a>(
mut fields, mut fields,
.. ..
} => { } => {
let sorted_fields = let sorted_fields = match crate::layout::sort_record_fields(
crate::layout::sort_record_fields(env.arena, record_var, env.subs, env.ptr_bytes); env.arena,
record_var,
env.subs,
env.ptr_bytes,
) {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't create record with improper layout"),
};
let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena); let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena);
let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena); let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena);
@ -3678,8 +3685,15 @@ pub fn with_hole<'a>(
loc_expr, loc_expr,
.. ..
} => { } => {
let sorted_fields = let sorted_fields = match crate::layout::sort_record_fields(
crate::layout::sort_record_fields(env.arena, record_var, env.subs, env.ptr_bytes); env.arena,
record_var,
env.subs,
env.ptr_bytes,
) {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't access record with improper layout"),
};
let mut index = None; let mut index = None;
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
@ -3821,8 +3835,15 @@ pub fn with_hole<'a>(
// This has the benefit that we don't need to do anything special for reference // This has the benefit that we don't need to do anything special for reference
// counting // counting
let sorted_fields = let sorted_fields = match crate::layout::sort_record_fields(
crate::layout::sort_record_fields(env.arena, record_var, env.subs, env.ptr_bytes); env.arena,
record_var,
env.subs,
env.ptr_bytes,
) {
Ok(fields) => fields,
Err(_) => return Stmt::RuntimeError("Can't update record with improper layout"),
};
let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
@ -4378,11 +4399,7 @@ pub fn with_hole<'a>(
} }
} }
} }
RuntimeError(e) => { RuntimeError(e) => Stmt::RuntimeError(env.arena.alloc(format!("{:?}", e))),
eprintln!("emitted runtime error {:?}", &e);
Stmt::RuntimeError(env.arena.alloc(format!("{:?}", e)))
}
} }
} }
@ -7369,7 +7386,8 @@ fn from_can_pattern_help<'a>(
use crate::layout::UnionVariant::*; use crate::layout::UnionVariant::*;
let res_variant = let res_variant =
crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs, env.ptr_bytes); crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs, env.ptr_bytes)
.map_err(Into::into);
let variant = match res_variant { let variant = match res_variant {
Ok(cached) => cached, Ok(cached) => cached,
@ -7789,7 +7807,8 @@ fn from_can_pattern_help<'a>(
} => { } => {
// sorted fields based on the type // sorted fields based on the type
let sorted_fields = let sorted_fields =
crate::layout::sort_record_fields(env.arena, *whole_var, env.subs, env.ptr_bytes); crate::layout::sort_record_fields(env.arena, *whole_var, env.subs, env.ptr_bytes)
.map_err(Into::into)?;
// sorted fields based on the destruct // sorted fields based on the destruct
let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena); let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);

View file

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

View file

@ -764,7 +764,8 @@ fn type_to_variable<'a>(
let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext);
let (it, new_ext_var) = let (it, new_ext_var) =
gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var); gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var)
.expect("Something ended up weird in this record type");
let it = it let it = it
.into_iter() .into_iter()

View file

@ -519,7 +519,8 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
let RecordStructure { let RecordStructure {
fields: sorted_fields, fields: sorted_fields,
ext, ext,
} = gather_fields(subs, *fields, *ext_var); } = gather_fields(subs, *fields, *ext_var)
.expect("Something ended up weird in this record type");
let ext_var = ext; let ext_var = ext;
if fields.is_empty() { if fields.is_empty() {

View file

@ -1968,7 +1968,8 @@ impl RecordFields {
subs: &'a Subs, subs: &'a Subs,
ext: Variable, ext: Variable,
) -> impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + 'a { ) -> impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + 'a {
let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext); let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext)
.expect("Something weird ended up in a record type");
it it
} }
@ -2003,7 +2004,8 @@ impl RecordFields {
ext, ext,
) )
} else { } else {
let record_structure = crate::types::gather_fields(subs, *self, ext); let record_structure = crate::types::gather_fields(subs, *self, ext)
.expect("Something ended up weird in this record type");
( (
Box::new(record_structure.fields.into_iter()), Box::new(record_structure.fields.into_iter()),

View file

@ -1701,14 +1701,20 @@ pub fn name_type_var(letters_used: u32, taken: &mut MutSet<Lowercase>) -> (Lower
} }
} }
#[derive(Debug, Copy, Clone)]
pub struct RecordFieldsError;
pub fn gather_fields_unsorted_iter( pub fn gather_fields_unsorted_iter(
subs: &Subs, subs: &Subs,
other_fields: RecordFields, other_fields: RecordFields,
mut var: Variable, mut var: Variable,
) -> ( ) -> Result<
impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + '_, (
Variable, impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + '_,
) { Variable,
),
RecordFieldsError,
> {
use crate::subs::Content::*; use crate::subs::Content::*;
use crate::subs::FlatType::*; use crate::subs::FlatType::*;
@ -1733,7 +1739,7 @@ pub fn gather_fields_unsorted_iter(
// TODO investigate apparently this one pops up in the reporting tests! // TODO investigate apparently this one pops up in the reporting tests!
RigidVar(_) => break, RigidVar(_) => break,
other => unreachable!("something weird ended up in a record type: {:?}", other), _ => return Err(RecordFieldsError),
} }
} }
@ -1749,11 +1755,15 @@ pub fn gather_fields_unsorted_iter(
(field_name, record_field) (field_name, record_field)
}); });
(it, var) Ok((it, var))
} }
pub fn gather_fields(subs: &Subs, other_fields: RecordFields, var: Variable) -> RecordStructure { pub fn gather_fields(
let (it, ext) = gather_fields_unsorted_iter(subs, other_fields, var); subs: &Subs,
other_fields: RecordFields,
var: Variable,
) -> Result<RecordStructure, RecordFieldsError> {
let (it, ext) = gather_fields_unsorted_iter(subs, other_fields, var)?;
let mut result: Vec<_> = it let mut result: Vec<_> = it
.map(|(ref_label, field)| (ref_label.clone(), field)) .map(|(ref_label, field)| (ref_label.clone(), field))
@ -1761,10 +1771,10 @@ pub fn gather_fields(subs: &Subs, other_fields: RecordFields, var: Variable) ->
result.sort_by(|(a, _), (b, _)| a.cmp(b)); result.sort_by(|(a, _), (b, _)| a.cmp(b));
RecordStructure { Ok(RecordStructure {
fields: result, fields: result,
ext, ext,
} })
} }
pub fn gather_tags_unsorted_iter( pub fn gather_tags_unsorted_iter(