diff --git a/Cargo.lock b/Cargo.lock index 0e2494dbf4..e8da71cc88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3323,6 +3323,7 @@ dependencies = [ "cli_utils", "ctor", "dircpy", + "fnv", "indexmap", "indoc", "pretty_assertions", diff --git a/crates/bindgen/Cargo.toml b/crates/bindgen/Cargo.toml index 3274ed76de..3311d7111f 100644 --- a/crates/bindgen/Cargo.toml +++ b/crates/bindgen/Cargo.toml @@ -31,6 +31,7 @@ clap = { version = "3.1.15", default-features = false, features = ["std", "color strum = "0.24.0" strum_macros = "0.24" indexmap = "1.8.1" +fnv = "1.0.7" [dev-dependencies] pretty_assertions = "1.0.0" diff --git a/crates/bindgen/src/bindgen_rs.rs b/crates/bindgen/src/bindgen_rs.rs index fe7eaf2f56..b4c06d30d3 100644 --- a/crates/bindgen/src/bindgen_rs.rs +++ b/crates/bindgen/src/bindgen_rs.rs @@ -269,7 +269,7 @@ fn add_type(target_info: TargetInfo, id: TypeId, types: &Types, impls: &mut Impl // This is recursively pointing to a type that should already have been added, // so no extra work needs to happen. } - RocType::Function(_, _) => { + RocType::Function { .. } => { // TODO actually bindgen functions! } } @@ -657,7 +657,7 @@ pub struct {name} {{ payload_args = answer.payload_args; args_to_payload = answer.args_to_payload; } - RocType::Function(_, _) => todo!(), + RocType::Function { .. } => todo!(), }; { @@ -1133,7 +1133,7 @@ pub struct {name} {{ buf.join("\n") } - RocType::Function(_, _) => todo!(), + RocType::Function { .. } => todo!(), }; format!( @@ -1309,7 +1309,7 @@ fn type_name(id: TypeId, types: &Types) -> String { | RocType::TagUnion(RocTagUnion::NullableUnwrapped { name, .. }) | RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { name, .. }) => name.clone(), RocType::RecursivePointer(content) => type_name(*content, types), - RocType::Function(_, _) => todo!(), + RocType::Function { name, .. } => name.clone(), } } @@ -1457,7 +1457,7 @@ pub struct {name} {{ owned_ret_type = answer.owned_ret_type; borrowed_ret_type = answer.borrowed_ret_type; } - RocType::Function(_, _) => todo!(), + RocType::Function { .. } => todo!(), }; // Add a convenience constructor function for the tag with the payload, e.g. @@ -1690,7 +1690,7 @@ pub struct {name} {{ buf.join(&format!("\n{INDENT}{INDENT}{INDENT}{INDENT}{INDENT}")) } - RocType::Function(_, _) => todo!(), + RocType::Function { .. } => todo!(), }; let body = format!( @@ -1892,7 +1892,7 @@ fn cannot_derive_default(roc_type: &RocType, types: &Types) -> bool { | RocType::TagUnion { .. } | RocType::RocResult(_, _) | RocType::RecursivePointer { .. } - | RocType::Function(_, _) => true, + | RocType::Function { .. } => true, RocType::RocStr | RocType::Bool | RocType::Num(_) => false, RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => { cannot_derive_default(types.get_type(*id), types) @@ -1918,7 +1918,7 @@ fn cannot_derive_copy(roc_type: &RocType, types: &Types) -> bool { | RocType::Bool | RocType::Num(_) | RocType::TagUnion(RocTagUnion::Enumeration { .. }) - | RocType::Function(_, _) => false, + | RocType::Function { .. } => false, RocType::RocStr | RocType::RocList(_) | RocType::RocDict(_, _) @@ -1969,7 +1969,7 @@ fn has_float_help(roc_type: &RocType, types: &Types, do_not_recurse: &[TypeId]) | RocType::RocStr | RocType::Bool | RocType::TagUnion(RocTagUnion::Enumeration { .. }) - | RocType::Function(_, _) => false, + | RocType::Function { .. } => false, RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => { has_float_help(types.get_type(*id), types, do_not_recurse) } diff --git a/crates/bindgen/src/types.rs b/crates/bindgen/src/types.rs index 6fa6ebc3bc..4bfd853b13 100644 --- a/crates/bindgen/src/types.rs +++ b/crates/bindgen/src/types.rs @@ -1,6 +1,7 @@ use crate::enums::Enums; use crate::structs::Structs; use bumpalo::Bump; +use fnv::FnvHashMap; use roc_builtins::bitcode::{ FloatWidth::*, IntWidth::{self, *}, @@ -38,6 +39,9 @@ pub struct Types { sizes: Vec, aligns: Vec, + // Needed to check for duplicates + types_by_name: FnvHashMap, + /// Dependencies - that is, which type depends on which other type. /// This is important for declaration order in C; we need to output a /// type declaration earlier in the file than where it gets referenced by another type. @@ -50,13 +54,277 @@ impl Types { Self { target: target_info, types: Vec::with_capacity(cap), + types_by_name: FnvHashMap::with_capacity_and_hasher(10, Default::default()), sizes: Vec::new(), aligns: Vec::new(), deps: VecMap::with_capacity(cap), } } - pub fn add(&mut self, typ: RocType, layout: Layout<'_>) -> TypeId { + pub fn is_equivalent(&self, a: &RocType, b: &RocType) -> bool { + use RocType::*; + + match (a, b) { + (RocStr, RocStr) | (Bool, Bool) | (EmptyTagUnion, EmptyTagUnion) | (Unit, Unit) => true, + (RocResult(ok_a, err_a), RocResult(ok_b, err_b)) => { + self.is_equivalent(self.get_type(*ok_a), self.get_type(*ok_b)) + && self.is_equivalent(self.get_type(*err_a), self.get_type(*err_b)) + } + (Num(num_a), Num(num_b)) => num_a == num_b, + (RocList(elem_a), RocList(elem_b)) + | (RocSet(elem_a), RocSet(elem_b)) + | (RocBox(elem_a), RocBox(elem_b)) + | (RecursivePointer(elem_a), RecursivePointer(elem_b)) => { + self.is_equivalent(self.get_type(*elem_a), self.get_type(*elem_b)) + } + (RocDict(key_a, val_a), RocDict(key_b, val_b)) => { + self.is_equivalent(self.get_type(*key_a), self.get_type(*key_b)) + && self.is_equivalent(self.get_type(*val_a), self.get_type(*val_b)) + } + (TagUnion(union_a), TagUnion(union_b)) => { + use RocTagUnion::*; + + match (union_a, union_b) { + (Enumeration { tags: tags_a, .. }, Enumeration { tags: tags_b, .. }) => { + tags_a == tags_b + } + ( + NonRecursive { + tags: tags_a, + discriminant_type: disc_a, + .. + }, + NonRecursive { + tags: tags_b, + discriminant_type: disc_b, + .. + }, + ) + | ( + Recursive { + tags: tags_a, + discriminant_type: disc_a, + .. + }, + Recursive { + tags: tags_b, + discriminant_type: disc_b, + .. + }, + ) => { + if disc_a != disc_b || tags_a.len() != tags_b.len() { + false + } else { + tags_a.iter().zip(tags_b.iter()).all( + |((name_a, opt_id_a), (name_b, opt_id_b))| { + name_a == name_b + && match (opt_id_a, opt_id_b) { + (Some(id_a), Some(id_b)) => self.is_equivalent( + self.get_type(*id_a), + self.get_type(*id_b), + ), + (None, None) => true, + (None, Some(_)) | (Some(_), None) => false, + } + }, + ) + } + } + ( + NonNullableUnwrapped { + content: content_a, .. + }, + NonNullableUnwrapped { + content: content_b, .. + }, + ) => content_a == content_b, + ( + NullableWrapped { + null_tag: null_a, + non_null_tags: non_null_a, + .. + }, + NullableWrapped { + null_tag: null_b, + non_null_tags: non_null_b, + .. + }, + ) => { + if null_a != null_b || non_null_a.len() != non_null_b.len() { + false + } else { + non_null_a.iter().zip(non_null_b.iter()).all( + |((disc_a, name_a, opt_id_a), (disc_b, name_b, opt_id_b))| { + disc_a == disc_b + && name_a == name_b + && match (opt_id_a, opt_id_b) { + (Some(id_a), Some(id_b)) => self.is_equivalent( + self.get_type(*id_a), + self.get_type(*id_b), + ), + (None, None) => true, + (None, Some(_)) | (Some(_), None) => false, + } + }, + ) + } + } + ( + NullableUnwrapped { + null_tag: null_tag_a, + non_null_tag: non_null_tag_a, + non_null_payload: non_null_payload_a, + null_represents_first_tag: null_represents_first_tag_a, + .. + }, + NullableUnwrapped { + null_tag: null_tag_b, + non_null_tag: non_null_tag_b, + non_null_payload: non_null_payload_b, + null_represents_first_tag: null_represents_first_tag_b, + .. + }, + ) => { + null_tag_a == null_tag_b + && non_null_tag_a == non_null_tag_b + && non_null_payload_a == non_null_payload_b + && null_represents_first_tag_a == null_represents_first_tag_b + } + // These are all listed explicitly so that if we ever add a new variant, + // we'll get an exhaustiveness error here. + (Enumeration { .. }, _) + | (_, Enumeration { .. }) + | (NonRecursive { .. }, _) + | (_, NonRecursive { .. }) + | (Recursive { .. }, _) + | (_, Recursive { .. }) + | (NonNullableUnwrapped { .. }, _) + | (_, NonNullableUnwrapped { .. }) + | (NullableUnwrapped { .. }, _) + | (_, NullableUnwrapped { .. }) => false, + } + } + ( + Struct { + fields: fields_a, .. + }, + Struct { + fields: fields_b, .. + }, + ) => { + if fields_a.len() == fields_b.len() { + fields_a + .iter() + .zip(fields_b.iter()) + .all(|((name_a, id_a), (name_b, id_b))| { + name_a == name_b + && self.is_equivalent(self.get_type(*id_a), self.get_type(*id_b)) + }) + } else { + false + } + } + ( + TagUnionPayload { + fields: fields_a, .. + }, + TagUnionPayload { + fields: fields_b, .. + }, + ) => { + if fields_a.len() == fields_b.len() { + fields_a + .iter() + .zip(fields_b.iter()) + .all(|((name_a, id_a), (name_b, id_b))| { + name_a == name_b + && self.is_equivalent(self.get_type(*id_a), self.get_type(*id_b)) + }) + } else { + false + } + } + ( + Function { + name: name_a, + args: args_a, + ret: ret_a, + }, + Function { + name: name_b, + args: args_b, + ret: ret_b, + }, + ) => { + // for functions, the name is actually important because two functions + // with the same type could have completely different implementations! + if name_a == name_b + && args_a.len() == args_b.len() + && self.is_equivalent(self.get_type(*ret_a), self.get_type(*ret_b)) + { + args_a.iter().zip(args_b.iter()).all(|(id_a, id_b)| { + self.is_equivalent(self.get_type(*id_a), self.get_type(*id_b)) + }) + } else { + false + } + } + // These are all listed explicitly so that if we ever add a new variant, + // we'll get an exhaustiveness error here. + (RocStr, _) + | (_, RocStr) + | (Bool, _) + | (_, Bool) + | (RocResult(_, _), _) + | (_, RocResult(_, _)) + | (Num(_), _) + | (_, Num(_)) + | (RocList(_), _) + | (_, RocList(_)) + | (RocDict(_, _), _) + | (_, RocDict(_, _)) + | (RocSet(_), _) + | (_, RocSet(_)) + | (RocBox(_), _) + | (_, RocBox(_)) + | (TagUnion(_), _) + | (_, TagUnion(_)) + | (EmptyTagUnion, _) + | (_, EmptyTagUnion) + | (Struct { .. }, _) + | (_, Struct { .. }) + | (TagUnionPayload { .. }, _) + | (_, TagUnionPayload { .. }) + | (RecursivePointer(_), _) + | (_, RecursivePointer(_)) + | (Function { .. }, _) + | (_, Function { .. }) => false, + } + } + + pub fn add_named(&mut self, name: String, typ: RocType, layout: Layout<'_>) -> TypeId { + if let Some(existing_type_id) = self.types_by_name.get(&name) { + let existing_type = self.get_type(*existing_type_id); + + if self.is_equivalent(existing_type, &typ) { + *existing_type_id + } else { + // TODO report this gracefully! + panic!( + "Duplicate name detected - {:?} could refer to either {:?} or {:?}", + name, existing_type, typ + ); + } + } else { + let id = self.add_anonymous(typ, layout); + + self.types_by_name.insert(name, id); + + id + } + } + + pub fn add_anonymous(&mut self, typ: RocType, layout: Layout<'_>) -> TypeId { let id = TypeId(self.types.len()); assert!(id.0 <= TypeId::MAX.0); @@ -163,7 +431,11 @@ pub enum RocType { /// this would be the field of Cons containing the (recursive) StrConsList type, /// and the TypeId is the TypeId of StrConsList itself. RecursivePointer(TypeId), - Function(Vec, TypeId), + Function { + name: String, + args: Vec, + ret: TypeId, + }, /// A zero-sized type, such as an empty record or a single-tag union with no payload Unit, } @@ -438,7 +710,7 @@ fn add_type_help<'a>( } } }, - Content::Structure(FlatType::Func(args, _closure_var, ret_var)) => { + Content::Structure(FlatType::Func(args, closure_var, ret_var)) => { let args = env.subs.get_subs_slice(*args); let mut arg_type_ids = Vec::with_capacity(args.len()); @@ -460,8 +732,16 @@ fn add_type_help<'a>( add_type_help(env, ret_layout, *ret_var, None, types) }; - let fn_type_id = - types.add(RocType::Function(arg_type_ids.clone(), ret_type_id), layout); + let name = format!("TODO_roc_function_{:?}", closure_var); + let fn_type_id = types.add_named( + name.clone(), + RocType::Function { + name, + args: arg_type_ids.clone(), + ret: ret_type_id, + }, + layout, + ); types.depends(fn_type_id, ret_type_id); @@ -475,8 +755,10 @@ fn add_type_help<'a>( todo!() } Content::Structure(FlatType::Erroneous(_)) => todo!(), - Content::Structure(FlatType::EmptyRecord) => types.add(RocType::Unit, layout), - Content::Structure(FlatType::EmptyTagUnion) => types.add(RocType::EmptyTagUnion, layout), + Content::Structure(FlatType::EmptyRecord) => types.add_anonymous(RocType::Unit, layout), + Content::Structure(FlatType::EmptyTagUnion) => { + types.add_anonymous(RocType::EmptyTagUnion, layout) + } Content::Alias(name, alias_vars, real_var, _) => { if name.is_builtin() { match layout { @@ -498,7 +780,7 @@ fn add_type_help<'a>( } } - types.add(RocType::Bool, layout) + types.add_anonymous(RocType::Bool, layout) } Layout::Union(union_layout) if *name == Symbol::RESULT_RESULT => { match union_layout { @@ -519,7 +801,8 @@ fn add_type_help<'a>( env.layout_cache.from_var(env.arena, err_var, subs).unwrap(); let err_id = add_type_help(env, err_layout, err_var, None, types); - let type_id = types.add(RocType::RocResult(ok_id, err_id), layout); + let type_id = + types.add_anonymous(RocType::RocResult(ok_id, err_id), layout); types.depends(type_id, ok_id); types.depends(type_id, err_id); @@ -547,7 +830,7 @@ fn add_type_help<'a>( Content::RangedNumber(_) => todo!(), Content::Error => todo!(), Content::RecursionVar { structure, .. } => { - let type_id = types.add(RocType::RecursivePointer(TypeId::PENDING), layout); + let type_id = types.add_anonymous(RocType::RecursivePointer(TypeId::PENDING), layout); let structure_layout = env .layout_cache .from_var(env.arena, *structure, subs) @@ -577,31 +860,31 @@ fn add_builtin_type<'a>( match (builtin, builtin_type) { (Builtin::Int(width), _) => match width { - U8 => types.add(RocType::Num(RocNum::U8), layout), - U16 => types.add(RocType::Num(RocNum::U16), layout), - U32 => types.add(RocType::Num(RocNum::U32), layout), - U64 => types.add(RocType::Num(RocNum::U64), layout), - U128 => types.add(RocType::Num(RocNum::U128), layout), - I8 => types.add(RocType::Num(RocNum::I8), layout), - I16 => types.add(RocType::Num(RocNum::I16), layout), - I32 => types.add(RocType::Num(RocNum::I32), layout), - I64 => types.add(RocType::Num(RocNum::I64), layout), - I128 => types.add(RocType::Num(RocNum::I128), layout), + U8 => types.add_anonymous(RocType::Num(RocNum::U8), layout), + U16 => types.add_anonymous(RocType::Num(RocNum::U16), layout), + U32 => types.add_anonymous(RocType::Num(RocNum::U32), layout), + U64 => types.add_anonymous(RocType::Num(RocNum::U64), layout), + U128 => types.add_anonymous(RocType::Num(RocNum::U128), layout), + I8 => types.add_anonymous(RocType::Num(RocNum::I8), layout), + I16 => types.add_anonymous(RocType::Num(RocNum::I16), layout), + I32 => types.add_anonymous(RocType::Num(RocNum::I32), layout), + I64 => types.add_anonymous(RocType::Num(RocNum::I64), layout), + I128 => types.add_anonymous(RocType::Num(RocNum::I128), layout), }, (Builtin::Float(width), _) => match width { - F32 => types.add(RocType::Num(RocNum::F32), layout), - F64 => types.add(RocType::Num(RocNum::F64), layout), - F128 => types.add(RocType::Num(RocNum::F128), layout), + F32 => types.add_anonymous(RocType::Num(RocNum::F32), layout), + F64 => types.add_anonymous(RocType::Num(RocNum::F64), layout), + F128 => types.add_anonymous(RocType::Num(RocNum::F128), layout), }, - (Builtin::Decimal, _) => types.add(RocType::Num(RocNum::Dec), layout), - (Builtin::Bool, _) => types.add(RocType::Bool, layout), - (Builtin::Str, _) => types.add(RocType::RocStr, layout), + (Builtin::Decimal, _) => types.add_anonymous(RocType::Num(RocNum::Dec), layout), + (Builtin::Bool, _) => types.add_anonymous(RocType::Bool, layout), + (Builtin::Str, _) => types.add_anonymous(RocType::RocStr, layout), (Builtin::List(elem_layout), Structure(Apply(Symbol::LIST_LIST, args))) => { let args = env.subs.get_subs_slice(*args); debug_assert_eq!(args.len(), 1); let elem_id = add_type_help(env, *elem_layout, args[0], opt_name, types); - let list_id = types.add(RocType::RocList(elem_id), layout); + let list_id = types.add_anonymous(RocType::RocList(elem_id), layout); types.depends(list_id, elem_id); @@ -658,7 +941,7 @@ where }) .collect::>(); - types.add(to_type(name, fields), layout) + types.add_named(name.clone(), to_type(name, fields), layout) } fn add_tag_union<'a>( @@ -712,7 +995,7 @@ fn add_tag_union<'a>( } _ => { // create a RocType for the payload and save it - let struct_name = format!("{}_{}", name, tag_name); // e.g. "MyUnion_MyVariant" + 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| { @@ -736,7 +1019,7 @@ fn add_tag_union<'a>( let discriminant_type = UnionLayout::discriminant_size(tags.len()).into(); RocType::TagUnion(RocTagUnion::NonRecursive { - name, + name: name.clone(), tags, discriminant_type, }) @@ -747,7 +1030,7 @@ fn add_tag_union<'a>( let discriminant_type = UnionLayout::discriminant_size(tags.len()).into(); RocType::TagUnion(RocTagUnion::Recursive { - name, + name: name.clone(), tags, discriminant_type, }) @@ -793,7 +1076,7 @@ fn add_tag_union<'a>( let (non_null_tag, non_null_payload) = non_null; RocType::TagUnion(RocTagUnion::NullableUnwrapped { - name, + name: name.clone(), null_tag, non_null_tag, non_null_payload: non_null_payload.unwrap(), @@ -803,7 +1086,7 @@ fn add_tag_union<'a>( } } Layout::Builtin(Builtin::Int(_)) => RocType::TagUnion(RocTagUnion::Enumeration { - name, + name: name.clone(), tags: tags.into_iter().map(|(tag_name, _)| tag_name).collect(), }), Layout::Builtin(_) @@ -817,14 +1100,14 @@ fn add_tag_union<'a>( // This should be a very rare use case, and it's not worth overcomplicating // the rest of bindgen to make it do something different. RocType::TagUnion(RocTagUnion::NonRecursive { - name, + name: name.clone(), tags, discriminant_type: RocNum::U8, }) } }; - let type_id = types.add(typ, layout); + let type_id = types.add_named(name, typ, layout); if is_recursive { env.known_recursive_types.insert(layout, type_id);