Skip to content

Scalar hashing violates Eq for nested nullability #8744

Description

@connortsui20

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA bug issue

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions