Fix Var hashing to prevent silent data loss and enable hashability

Merged
MA
1mo ago
+194-103

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Description

This PR fixes critical bugs in Var hashing and equality that were causing hooks and imports to be silently dropped from compiled output, and making certain Var types unhashable.

Problems fixed:

  1. Silent data loss on var interpolation: When two vars with identical values but different metadata (hooks, imports, dependencies) were interpolated into the same f-string, they would hash to the same value and collide in _global_vars, causing one var's metadata to be silently dropped from the compiled output.

  2. VarTypeError in Var.equals(): The Var.equals() method was comparing VarData.deps element-wise, which invoked Var.__eq__(). Since Var.__eq__() returns a BooleanVar rather than a bool, this raised VarTypeError when containers tried to compare Vars.

  3. NumberVar and BooleanVar unhashable: Defining __eq__ without explicitly preserving __hash__ made these types unhashable, breaking their use in sets and dicts.

Solution:

  • Introduced Var._hash_key() method that returns a hashable tuple containing the var's canonical identity (expression, type, and var data). This key contains no Var objects, making it safe for containers to use.
  • Updated Var.__hash__() and Var.equals() to use _hash_key() instead of direct field comparison, ensuring they never invoke Var.__eq__().
  • Updated VarData._identity_key to convert deps to their hash keys, preventing Var objects from leaking into the identity tuple.
  • Deduplicated deps in VarData.merge() using hash keys to avoid redundant dependencies.
  • Cached VarData._identity_key and _cached_hash as properties to avoid recomputation during every var interpolation.
  • Removed redundant __hash__ overrides from subclasses (NumberVar, BooleanVar, LiteralVar, etc.) that were inconsistent with the base implementation.
  • Explicitly preserved __hash__ on NumberVar to prevent it from becoming unhashable when __eq__ is defined.

Testing

  • Added comprehensive unit tests covering:
    • Var.equals() with vars carrying dependencies
    • VarData.merge() deduplication
    • Hash collision prevention for literal vars with different metadata
    • Hash consistency between .to() conversions and originals
    • Hash consistency with equals() across all var types
    • Verification that hash keys contain no Var objects
    • Hashability of NumberVar and BooleanVar
  • All new tests pass; existing tests continue to pass.

Changelog

Added news fragment: news/+var-hash-identity.bugfix.md

https://claude.ai/code/session_01UbCysGoqoSYh53PNBGpHZQ