From c5f943563481818cdf4be02b6461dd9c3cf2b929 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 27 Jul 2026 11:00:01 -0700 Subject: [PATCH 1/2] Fix `PartialEq` for component model types The previous implementation derived `PartialEq` which tested for semantic equality of maps internally, but the intended implementation is to test for equality of order of items/names as well. --- crates/environ/src/component/types.rs | 111 +++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index b0622bfb350d..2a2e6d3bfa15 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -1053,7 +1053,7 @@ pub struct RecordField { /// Variants are close to Rust `enum` declarations where a value is one of many /// cases and each case has a unique name and an optional payload associated /// with it. -#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct TypeVariant { /// The list of cases that this variant can take. pub cases: IndexMap>, @@ -1063,6 +1063,20 @@ pub struct TypeVariant { pub info: VariantInfo, } +// NB: the order of `cases` matter, so this `PartialEq` disagrees with +// `IndexMap`'s implementation. +impl PartialEq for TypeVariant { + fn eq(&self, other: &TypeVariant) -> bool { + let TypeVariant { cases, abi, info } = self; + cases.len() == other.cases.len() + && cases.iter().eq(other.cases.iter()) + && *abi == other.abi + && *info == other.info + } +} + +impl Eq for TypeVariant {} + impl Hash for TypeVariant { fn hash(&self, h: &mut H) { let TypeVariant { cases, abi, info } = self; @@ -1091,7 +1105,7 @@ pub struct TypeTuple { /// /// This can be thought of as a record-of-bools, although the representation is /// more efficient as bitflags. -#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct TypeFlags { /// The names of all flags, all of which are unique. pub names: IndexSet, @@ -1099,6 +1113,17 @@ pub struct TypeFlags { pub abi: CanonicalAbiInfo, } +// NB: the order of `names` matter, so this `PartialEq` disagrees with +// `IndexMap`'s implementation. +impl PartialEq for TypeFlags { + fn eq(&self, other: &TypeFlags) -> bool { + let TypeFlags { names, abi } = self; + names.len() == other.names.len() && names.iter().eq(other.names.iter()) && *abi == other.abi + } +} + +impl Eq for TypeFlags {} + impl Hash for TypeFlags { fn hash(&self, h: &mut H) { let TypeFlags { names, abi } = self; @@ -1115,7 +1140,7 @@ impl Hash for TypeFlags { /// /// In interface types enums are simply a bag of names, and can be seen as a /// variant where all payloads are `Unit`. -#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct TypeEnum { /// The names of this enum, all of which are unique. pub names: IndexSet, @@ -1125,6 +1150,20 @@ pub struct TypeEnum { pub info: VariantInfo, } +// NB: the order of `names` matter, so this `PartialEq` disagrees with +// `IndexMap`'s implementation. +impl PartialEq for TypeEnum { + fn eq(&self, other: &TypeEnum) -> bool { + let TypeEnum { names, abi, info } = self; + names.len() == other.names.len() + && names.iter().eq(other.names.iter()) + && *abi == other.abi + && *info == other.info + } +} + +impl Eq for TypeEnum {} + impl Hash for TypeEnum { fn hash(&self, h: &mut H) { let TypeEnum { names, abi, info } = self; @@ -1350,3 +1389,69 @@ pub enum FlatType { F32, F64, } + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::hash_map::DefaultHasher; + + fn variant(cases: &[(&str, InterfaceType)]) -> TypeVariant { + TypeVariant { + cases: cases + .iter() + .map(|(name, ty)| (name.to_string(), Some(*ty))) + .collect(), + abi: CanonicalAbiInfo::default(), + info: VariantInfo { + size: DiscriminantSize::Size1, + payload_offset32: 4, + payload_offset64: 8, + }, + } + } + + fn flags(names: &[&str]) -> TypeFlags { + TypeFlags { + names: names.iter().map(|n| n.to_string()).collect(), + abi: CanonicalAbiInfo::default(), + } + } + + fn enum_(names: &[&str]) -> TypeEnum { + TypeEnum { + names: names.iter().map(|n| n.to_string()).collect(), + abi: CanonicalAbiInfo::default(), + info: VariantInfo { + size: DiscriminantSize::Size1, + payload_offset32: 4, + payload_offset64: 8, + }, + } + } + + #[test] + fn variant_case_order_is_significant() { + let a = variant(&[("n", InterfaceType::U32), ("s", InterfaceType::String)]); + let b = variant(&[("s", InterfaceType::String), ("n", InterfaceType::U32)]); + assert_ne!(a, b); + assert_eq!(a, a.clone()); + } + + #[test] + fn flags_name_order_is_significant() { + let a = flags(&["a", "b", "c"]); + let b = flags(&["c", "b", "a"]); + + assert_ne!(a, b); + assert_eq!(a, a.clone()); + } + + #[test] + fn enum_name_order_is_significant() { + let a = enum_(&["red", "green", "blue"]); + let b = enum_(&["blue", "green", "red"]); + + assert_ne!(a, b); + assert_eq!(a, a.clone()); + } +} From 5de7acbebac0624fd36432b785bde5750d3344c4 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 27 Jul 2026 11:43:03 -0700 Subject: [PATCH 2/2] Fix CI --- crates/environ/src/component/types.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 2a2e6d3bfa15..18d37a429688 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -1393,7 +1393,6 @@ pub enum FlatType { #[cfg(test)] mod tests { use super::*; - use std::collections::hash_map::DefaultHasher; fn variant(cases: &[(&str, InterfaceType)]) -> TypeVariant { TypeVariant {