mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 00:24:34 +00:00
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:
parent
e728e319c6
commit
576f1293fd
7 changed files with 96 additions and 51 deletions
|
@ -765,7 +765,8 @@ fn type_to_variable<'a>(
|
|||
let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext);
|
||||
|
||||
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
|
||||
.into_iter()
|
||||
|
|
|
@ -3325,8 +3325,15 @@ pub fn with_hole<'a>(
|
|||
mut fields,
|
||||
..
|
||||
} => {
|
||||
let sorted_fields =
|
||||
crate::layout::sort_record_fields(env.arena, record_var, env.subs, env.ptr_bytes);
|
||||
let sorted_fields = match crate::layout::sort_record_fields(
|
||||
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 can_fields = Vec::with_capacity_in(fields.len(), env.arena);
|
||||
|
@ -3678,8 +3685,15 @@ pub fn with_hole<'a>(
|
|||
loc_expr,
|
||||
..
|
||||
} => {
|
||||
let sorted_fields =
|
||||
crate::layout::sort_record_fields(env.arena, record_var, env.subs, env.ptr_bytes);
|
||||
let sorted_fields = match crate::layout::sort_record_fields(
|
||||
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 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
|
||||
// counting
|
||||
|
||||
let sorted_fields =
|
||||
crate::layout::sort_record_fields(env.arena, record_var, env.subs, env.ptr_bytes);
|
||||
let sorted_fields = match crate::layout::sort_record_fields(
|
||||
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);
|
||||
|
||||
|
@ -4378,11 +4399,7 @@ pub fn with_hole<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
RuntimeError(e) => {
|
||||
eprintln!("emitted runtime error {:?}", &e);
|
||||
|
||||
Stmt::RuntimeError(env.arena.alloc(format!("{:?}", e)))
|
||||
}
|
||||
RuntimeError(e) => Stmt::RuntimeError(env.arena.alloc(format!("{:?}", e))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7369,7 +7386,8 @@ fn from_can_pattern_help<'a>(
|
|||
use crate::layout::UnionVariant::*;
|
||||
|
||||
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 {
|
||||
Ok(cached) => cached,
|
||||
|
@ -7789,7 +7807,8 @@ fn from_can_pattern_help<'a>(
|
|||
} => {
|
||||
// sorted fields based on the type
|
||||
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
|
||||
let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
|
||||
|
|
|
@ -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)| {
|
||||
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(_) => return None,
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -764,7 +764,8 @@ fn type_to_variable<'a>(
|
|||
let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext);
|
||||
|
||||
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
|
||||
.into_iter()
|
||||
|
|
|
@ -519,7 +519,8 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
|
|||
let RecordStructure {
|
||||
fields: sorted_fields,
|
||||
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;
|
||||
|
||||
if fields.is_empty() {
|
||||
|
|
|
@ -1968,7 +1968,8 @@ impl RecordFields {
|
|||
subs: &'a Subs,
|
||||
ext: Variable,
|
||||
) -> 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
|
||||
}
|
||||
|
@ -2003,7 +2004,8 @@ impl RecordFields {
|
|||
ext,
|
||||
)
|
||||
} 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()),
|
||||
|
|
|
@ -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(
|
||||
subs: &Subs,
|
||||
other_fields: RecordFields,
|
||||
mut var: Variable,
|
||||
) -> (
|
||||
) -> Result<
|
||||
(
|
||||
impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + '_,
|
||||
Variable,
|
||||
) {
|
||||
),
|
||||
RecordFieldsError,
|
||||
> {
|
||||
use crate::subs::Content::*;
|
||||
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!
|
||||
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)
|
||||
});
|
||||
|
||||
(it, var)
|
||||
Ok((it, var))
|
||||
}
|
||||
|
||||
pub fn gather_fields(subs: &Subs, other_fields: RecordFields, var: Variable) -> RecordStructure {
|
||||
let (it, ext) = gather_fields_unsorted_iter(subs, other_fields, var);
|
||||
pub fn gather_fields(
|
||||
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
|
||||
.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));
|
||||
|
||||
RecordStructure {
|
||||
Ok(RecordStructure {
|
||||
fields: result,
|
||||
ext,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn gather_tags_unsorted_iter(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue