Merge pull request #3989 from roc-lang/fix-glue-segfault

Fix glue segfault for newtype wrappers around recursive tag unions
This commit is contained in:
Richard Feldman 2022-09-13 13:47:51 -07:00 committed by GitHub
commit ac68d75eec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 54 deletions

View file

@ -328,7 +328,7 @@ fn add_single_tag_struct(
false,
);
let mut body = format!("#[repr(C)]\n{derive}\npub struct {name} ");
let mut body = format!("#[repr(transparent)]\n{derive}\npub struct {name} ");
if payload_fields.is_empty() {
// A single tag with no payload is a zero-sized unit type, so

View file

@ -1236,6 +1236,30 @@ fn add_tag_union<'a>(
};
let tag_union_type = match layout {
_ if union_tags.is_newtype_wrapper(subs)
&& matches!(
subs.get_content_without_compacting(var),
// Make sure this is a tag union, *not* a recursive tag union!
// Otherwise, we could end up with a recursive tag union
// getting unwrapped incorrectly.
Content::Structure(FlatType::TagUnion(_, _))
) =>
{
let (tag_name, payload_vars) = single_tag_payload(union_tags, subs);
// A newtype wrapper should always have exactly one payload.
debug_assert_eq!(payload_vars.len(), 1);
// A newtype wrapper should always have the same layout as its payload.
let payload_layout = layout;
let payload_id = add_type_help(env, payload_layout, payload_vars[0], None, types);
RocTagUnion::SingleTagStruct {
name: name.clone(),
tag_name: tag_name.to_string(),
payload_fields: vec![payload_id],
}
}
Layout::Union(union_layout) => {
use UnionLayout::*;
@ -1279,19 +1303,16 @@ fn add_tag_union<'a>(
}
}
NonNullableUnwrapped(_) => {
let mut tags =
union_tags_to_types(&name, union_tags, subs, env, types, layout, true);
debug_assert_eq!(tags.len(), 1);
let (tag_name, opt_payload) = tags.pop().unwrap();
let (tag_name, payload_vars) = single_tag_payload(union_tags, subs);
let (tag_name, opt_payload) =
tag_to_type(&name, env, tag_name, payload_vars, types, layout, true);
// A recursive tag union with just one constructor
// Optimization: No need to store a tag ID (the payload is "unwrapped")
// e.g. `RoseTree a : [Tree a (List (RoseTree a))]`
RocTagUnion::NonNullableUnwrapped {
name: name.clone(),
tag_name,
tag_name: tag_name.to_string(),
payload: opt_payload.unwrap(),
}
}
@ -1375,7 +1396,7 @@ fn add_tag_union<'a>(
// e.g. `RoseTree a : [Tree a (List (RoseTree a))]`
RocTagUnion::SingleTagStruct {
name: name.clone(),
tag_name,
tag_name: tag_name.to_string(),
payload_fields,
}
}
@ -1391,7 +1412,7 @@ fn add_tag_union<'a>(
RocTagUnion::SingleTagStruct {
name: name.clone(),
tag_name,
tag_name: tag_name.to_string(),
payload_fields: vec![type_id],
}
}
@ -1401,7 +1422,7 @@ fn add_tag_union<'a>(
RocTagUnion::SingleTagStruct {
name: name.clone(),
tag_name,
tag_name: tag_name.to_string(),
payload_fields,
}
}
@ -1459,31 +1480,44 @@ fn union_tags_to_types<'a>(
(name_str, payload_vars.to_vec())
})
.collect();
// Sort tags alphabetically by tag name
tags.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
tags_to_types(name, tags, env, types, layout, is_recursive)
tags.into_iter()
.map(|(tag_name, payload_vars)| {
tag_to_type(
name,
env,
tag_name,
&payload_vars,
types,
layout,
is_recursive,
)
})
.collect()
}
fn single_tag_payload<'a>(
union_tags: &'a UnionLabels<TagName>,
subs: &'a Subs,
) -> (String, &'a [Variable]) {
) -> (&'a str, &'a [Variable]) {
let mut iter = union_tags.iter_from_subs(subs);
let (tag_name, payload_vars) = iter.next().unwrap();
// This should be a single-tag union.
debug_assert_eq!(iter.next(), None);
(tag_name.0.as_str().to_string(), payload_vars)
(tag_name.0.as_str(), payload_vars)
}
fn single_tag_payload_fields<'a>(
union_tags: &UnionLabels<TagName>,
subs: &Subs,
fn single_tag_payload_fields<'a, 'b>(
union_tags: &'b UnionLabels<TagName>,
subs: &'b Subs,
field_layouts: &[Layout<'a>],
env: &mut Env<'a>,
types: &mut Types,
) -> (String, Vec<TypeId>) {
) -> (&'b str, Vec<TypeId>) {
let (tag_name, payload_vars) = single_tag_payload(union_tags, subs);
let payload_fields: Vec<TypeId> = payload_vars
@ -1495,48 +1529,44 @@ fn single_tag_payload_fields<'a>(
(tag_name, payload_fields)
}
fn tags_to_types<'a>(
fn tag_to_type<'a, D: Display>(
name: &str,
tags: Vec<(String, Vec<Variable>)>,
env: &mut Env<'a>,
tag_name: D,
payload_vars: &[Variable],
types: &mut Types,
layout: Layout<'a>,
is_recursive: bool,
) -> Vec<(String, Option<TypeId>)> {
tags.into_iter()
.map(|(tag_name, payload_vars)| {
match struct_fields_needed(env, payload_vars.iter().copied()) {
0 => {
// no payload
(tag_name, None)
}
1 if !is_recursive => {
// this isn't recursive and there's 1 payload item, so it doesn't
// need its own struct - e.g. for `[Foo Str, Bar Str]` both of them
// can have payloads of plain old Str, no struct wrapper needed.
let payload_var = payload_vars.get(0).unwrap();
let payload_layout = env
.layout_cache
.from_var(env.arena, *payload_var, env.subs)
.expect("Something weird ended up in the content");
let payload_id = add_type_help(env, payload_layout, *payload_var, None, types);
) -> (D, Option<TypeId>) {
match struct_fields_needed(env, payload_vars.iter().copied()) {
0 => {
// no payload
(tag_name, None)
}
1 if !is_recursive => {
// this isn't recursive and there's 1 payload item, so it doesn't
// need its own struct - e.g. for `[Foo Str, Bar Str]` both of them
// can have payloads of plain old Str, no struct wrapper needed.
let payload_var = payload_vars.get(0).unwrap();
let payload_layout = env
.layout_cache
.from_var(env.arena, *payload_var, env.subs)
.expect("Something weird ended up in the content");
let payload_id = add_type_help(env, payload_layout, *payload_var, None, types);
(tag_name, Some(payload_id))
}
_ => {
// create a RocType for the payload and save it
let struct_name = format!("{}_{}", &name, tag_name); // e.g. "MyUnion_MyVariant"
let fields = payload_vars.iter().copied().enumerate();
let struct_id =
add_struct(env, struct_name, fields, types, layout, |name, fields| {
RocType::TagUnionPayload { name, fields }
});
(tag_name, Some(payload_id))
}
_ => {
// create a RocType for the payload and save it
let struct_name = format!("{}_{}", &name, tag_name); // e.g. "MyUnion_MyVariant"
let fields = payload_vars.iter().copied().enumerate();
let struct_id = add_struct(env, struct_name, fields, types, layout, |name, fields| {
RocType::TagUnionPayload { name, fields }
});
(tag_name, Some(struct_id))
}
}
})
.collect()
(tag_name, Some(struct_id))
}
}
}
fn struct_fields_needed<I: IntoIterator<Item = Variable>>(env: &mut Env<'_>, vars: I) -> usize {