Always call __eq when comparing for equality

Note: this RFC was adapted from an internal proposal that predates RFC process

Status: Implemented

Summary

__eq metamethod will always be called during ==/~= comparison, even for objects that are rawequal.

Motivation

Lua 5.x has the following algorithm it uses for comparing userdatas and tables:

In mid-2019, we’ve released Luau which implements a fast path for userdata comparison. This fast path accidentally omitted step 2 for userdatas with C __eq implementations (!), and thus comparing a userdata object vs itself would actually run __eq metamethod. This is significant as it allowed users to use v == v as a NaN check for vectors, coordinate frames, and other objects that have floating point contents.

Since this was a bug, we’re in a rather inconsistent state:

Design

Since developers started relying on == behavior for NaN checks in the last two years since Luau release, the bug has become a feature. Additionally, it’s sort of a good feature since it allows to implement NaN semantics for custom types - userdatas, tables, etc.

Thus the proposal suggests changing the rules so that when __eq metamethod is present, __eq is always called even when comparing the object to itself.

This would effectively make the current ruleset for userdata objects official, and change the behavior for table.find (which is probably not significant) and, more significantly, start calling user-provided __eq even when the object is the same. It’s expected that any reasonable __eq implementation can handle comparing the object to itself so this is not expected to result in breakage.

Drawbacks

This represents a difference in a rather core behavior from all upstream versions of Lua.

Alternatives

We could instead equalize (ha!) the behavior between Luau and Lua. In fact, this is what we tried to do initially as the userdata behavior was considered a bug, but encountered the issue with games already depending on the new behavior.

We could work with developers to change their games to stop relying on this. However, this is more complicated to deploy and - upon reflection - makes == less intuitive than the main proposal when comparing objects with NaN, since e.g. it means that these two functions have a different behavior:

function compare1(a: Vector3, b: Vector3)
    return a == b
end

function compare2(a: Vector3, b: Vector3)
    return a.X == b.X and a.Y == b.Y and a.Z == b.Z
end

References

https://devforum.roblox.com/t/call-eq-even-when-tables-are-rawequal/1088886 https://devforum.roblox.com/t/nan-vector3-comparison-broken-cframe-too/1130778