Skip to content

EqualitySaturationNeverChangesTheValueItClaimsToPreserve compares two expressions that have no value #1162

Description

@Rafael-SOWNet

((k and p) or (k and q)) = (k and (p or q)) and its dual are declared Soundness.Sound and
guarded with when: IsLogic(k, p, q). That guard passes a free variable, since a free variable
may turn out to be a boolean — and then the identity does not survive the variable being something
else.

Equality saturation reaches it and the existing property test catches it:

a and b or a and not b   ->   a and (b or not b)      via the distribution rule

at a = b = 0.37:
  0.37 and 0.37 or 0.37 and not 0.37   !=   0.37 and (0.37 or not 0.37)

TransformationTest.EqualitySaturationNeverChangesTheValueItClaimsToPreserve fails on
"a and b or a and not b".

Why nobody has seen it

The rule's growth is Unknown, and Saturation.RulesUpTo builds SafeRules as the rules up to
Rearranges, so equality saturation has never fired it. Unknown was standing in for a
soundness guard without anyone intending it to.

It surfaced while declaring growths for #825: the count for this rule is plainly -(1 + |k|), so
Collects is the right growth — and writing it down is what let the rule run and fail. The
declaration was withdrawn rather than shipped, with the reason recorded beside the rule, because the
growth is not what is wrong here.

What is actually wrong

One of two things, and it wants deciding rather than patching:

  • The tier. If IsLogic is meant to admit a free variable, the rule holds only when that
    variable really is a boolean, which is SoundUnderAssumptions and not Sound.
  • The guard. If the rule is meant to be Sound, IsLogic has to refuse a variable whose
    codomain does not say it is boolean, and then the rule simply does not fire on a and b.

The second is the stronger reading of what Sound is supposed to mean here — "holds for every value
the pattern admits, with nothing assumed" — but it narrows the rule, and how much it narrows it is
worth measuring before choosing.

The general point, which is worth more than this rule

A growth declaration is not metadata. Declaring one moves a rule into the set equality saturation
runs, so it can expose a rule whose soundness was wrong all along. Any batch of declarations should
expect to find this, and should treat a failure of
EqualitySaturationNeverChangesTheValueItClaimsToPreserve as a finding about the rule rather than
about the declaration.

Worth checking whether other rules guarded by IsLogic have the same hole; the two distributions are
simply the two that a declaration reached first.

Activity

  1. Rafael-SOWNet commented on Sep 5, 2026

    @Rafael-SOWNet
    MemberAuthor

    Retracting this issue's cause

    The rule is not unsound, and the description above is wrong. I filed it while declaring growths
    for #825, blamed IsLogic for admitting a free variable, and stated the mechanism before measuring
    the values it makes a claim about.

    Measured:

    (0.37 and 0.37) or (0.37 and not 0.37)   ->   37/100 and 37/100 or 37/100 and not 37/100
    0.37 and (0.37 or not 0.37)              ->   37/100 and (37/100 or not 37/100)
    

    A boolean operation on a non-boolean does not evaluate to NaN — it does not evaluate at all.
    Both sides come back stuck. Neither has a truth value, so nothing has changed between them.
    (a and b) or (a and not b) = a and (b or not b) is a boolean identity and holds.

    Where the defect actually is

    EqualitySaturationNeverChangesTheValueItClaimsToPreserve substitutes a real for every free
    variable and compares what the two sides evaluate to, with

    catch { continue; } // a boolean/set-valued corpus entry: not this test's claim

    .Evaled on 37/100 and 37/100 throws nothing, so that skip never fires. EqualsImprecisely then
    compares two different unevaluated forms of a thing with no value and calls it a disagreement. The
    test's comment names the entries it means to exclude and its mechanism for excluding them has never
    worked — and "a and b or a and not b" is in its own corpus precisely so the boolean rules get
    exercised.

    Fixed in #1166, and made stricter where it matters: skipped only when neither side has a value,
    and failing when one has a value and the other does not, since that is a rewrite which lost one
    and is exactly what the test is for. Today that case would have been compared structurally and might
    have passed.

    What was right in the original report

    Only the general point, which stands on its own: a growth declaration is not metadata. Writing
    one moves a rule into the set equality saturation runs, which may be the first time it has ever run,
    so a batch of declarations should expect to surface things — and should treat a failure of that
    property test as a finding to investigate rather than a number to work around. Here the finding
    turned out to be about the test.

    IsLogic admitting a free variable is fine as it stands; a variable may be a boolean, and the rules
    that need more than that — excluded middle, from #876 — already attach a TruthCondition.

    Closing as resolved by #1166.

  2. changed the title [-]A boolean distribution rule marked Sound changes the value when its operands are not booleans[/-] [+]EqualitySaturationNeverChangesTheValueItClaimsToPreserve compares two expressions that have no value[/+] on Sep 5, 2026
  3. added theissue type on Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions