What happened?
Scalar::eq compares dtypes with DType::eq_ignore_nullability, which ignores nullability recursively. Scalar::hash hashes dtype.as_nonnullable(), which only removes top-level nullability.
As a result, equal scalars with nested dtypes can produce different hashes, violating Rust's Eq/Hash contract. This affects nested List, FixedSizeList, Struct, Union, and extension storage dtypes.
The typed views appear to have the same issue: ListScalar, StructScalar, and ExtScalar compare dtypes ignoring nullability but hash their raw dtypes.
Steps to reproduce
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use vortex_array::dtype::{DType, Nullability, PType};
use vortex_array::scalar::Scalar;
fn hash(value: &Scalar) -> u64 {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}
let nullable = Scalar::list(
DType::Primitive(PType::I32, Nullability::Nullable),
vec![Scalar::primitive(42_i32, Nullability::Nullable)],
Nullability::NonNullable,
);
let non_nullable = Scalar::list(
DType::Primitive(PType::I32, Nullability::NonNullable),
vec![Scalar::primitive(42_i32, Nullability::NonNullable)],
Nullability::NonNullable,
);
assert_eq!(nullable, non_nullable);
assert_eq!(hash(&nullable), hash(&non_nullable)); // fails
Environment
- Vortex version: current
develop
- Rust
- All platforms
Additional context
DType::with_nullability only replaces the outer nullability, while DType::eq_ignore_nullability recursively compares nested dtypes.
Hashing needs to use the same nullability-insensitive equivalence relation as equality, or the equality contract needs to be narrowed.
What happened?
Scalar::eqcompares dtypes withDType::eq_ignore_nullability, which ignores nullability recursively.Scalar::hashhashesdtype.as_nonnullable(), which only removes top-level nullability.As a result, equal scalars with nested dtypes can produce different hashes, violating Rust's
Eq/Hashcontract. This affects nestedList,FixedSizeList,Struct,Union, and extension storage dtypes.The typed views appear to have the same issue:
ListScalar,StructScalar, andExtScalarcompare dtypes ignoring nullability but hash their raw dtypes.Steps to reproduce
Environment
developAdditional context
DType::with_nullabilityonly replaces the outer nullability, whileDType::eq_ignore_nullabilityrecursively compares nested dtypes.Hashing needs to use the same nullability-insensitive equivalence relation as equality, or the equality contract needs to be narrowed.