Skip to content

fix: compile Python reference-type equality to identity comparison - #6496

Open
fabiomadge wants to merge 6 commits into
dafny-lang:masterfrom
fabiomadge:fix/6491-python-ref-identity
Open

fix: compile Python reference-type equality to identity comparison#6496
fabiomadge wants to merge 6 commits into
dafny-lang:masterfrom
fabiomadge:fix/6491-python-ref-identity

Conversation

@fabiomadge

@fabiomadge fabiomadge commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Part of #6491. Dafny gives class types identity equality and the verifier reasons about them accordingly, but the Python backend compiled ==/!= on reference types to Python's ==/!=, which dispatch to the overridable __eq__ hook. An extern class overriding __eq__ therefore made verified reference (in)equalities evaluate incorrectly at run time:

var a := Box.Make(1);
var b := Box.Make(1);
assert a != b;              // verified
print a != b;               // printed false with a structural __eq__ extern

Generated datatype __eq__ methods compared class-typed fields the same way, so D(a, 0) != D(b, 0) was also verified-true but runtime-false.

Fix. Emit is/is not for EqCommon/NeqCommon at reference types (after DatatypeWrapperEraser.SimplifyType, as elsewhere), and identity comparisons for reference-typed fields in generated datatype __eq__ methods. This mirrors the discrimination the Java and C# backends already make at the same decision point (JavaCodeGenerator emits == (Object) for ref types; CsharpCodeGenerator emits == (object)). Python was the only backend where even direct equality was affected.

Non-regression checked: x == null, trait-typed comparisons, array identity, and the equality-sensitive comp/dafny0 compile tests across all targets. The regression test uses an extern with a structural __eq__ override, asserting both direct and datatype-field inequality survive compilation.

Known boundary, documented on the new EqualityOperator helper: equality at an abstract type parameter (e.g. inside method Eq<T(==)>(x: T, y: T), or a generic datatype's T-typed field) still compiles to == — the static type is not a reference type there. This matches the other backends, whose universal-equality fallbacks (java.util.Objects.equals, object.Equals) have the same hole; closing it needs runtime type information (the TypeDescriptor discussion in #6491).

Scope note: this intentionally does not address the collection-level unsoundness of #6491 (set<Box> collapsing etc.), which exists on several backends and needs the runtime collections to key on identity for reference types — a larger, cross-backend design discussed in the issue.

By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.

Dafny gives class types identity equality, and the verifier reasons
about them accordingly. The Python backend compiled == and != on
reference types to Python's ==/!=, which dispatch to the overridable
__eq__ hook — so an extern class overriding __eq__ (or a future
generated override) made verified reference (in)equalities evaluate
incorrectly at run time. Datatype __eq__ methods compared class-typed
fields the same way.

Emit 'is'/'is not' for EqCommon/NeqCommon at reference types, and
identity comparisons for reference-typed fields in generated datatype
__eq__ methods, mirroring how the Java and C# backends already special-
case IsRefType at the same decision point (JavaCodeGenerator EqCommon
emits '== (Object)').

Part of dafny-lang#6491 (the collection-level unsoundness on other backends is
tracked there).
Following the ExternCtors.dfy convention removes the axiomatized
factory method and its BigInteger handling from the extern; the
structural __eq__ needs no state at all to subvert identity.
…ation

The generic-datatype field case (G<T> instantiated at a reference type)
and equality at abstract type parameters still compile to ==, matching
the universal-equality fallbacks of the other backends; the helper's
doc comment records this boundary explicitly.
Document that reference-type == now compiles to identity (this PR), while the
built-in collections still consult __eq__/__hash__, so an extern overriding
those can still diverge from verified facts; point to datatype and
type {:extern} T(==) as the structural alternatives.

Relates to dafny-lang#6491.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant