Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Caml_obj equal function to support errors #6937

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

DZakh
Copy link
Contributor

@DZakh DZakh commented Aug 4, 2024

And improve performance

Comment on lines -321 to -323
if a_type == "function" || b_type == "function" {
raise(Invalid_argument("equal: functional value"))
} /* first, check using reference equality */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we checked the functions using strict equal a === b, it doesn't make sense to raise an error at this point.

Copy link
Collaborator

@cristianoc cristianoc Aug 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not?
The error says you cannot compare functional values. (Unless they are identical, in which case you can).
We might change the stance on this, but both choices are valid design choices.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think according to the funciton name equal it makes sense that when you compare two non-identical function values, it returns false. Also, it improves performance for non function values, since there's no need to specifically check for it.

Comment on lines -311 to +306
if (
a_type == "string" ||
(a_type == "number" ||
(a_type == "bigint" ||
(a_type == "boolean" ||
(a_type == "undefined" || a === %raw(`null`)))))
) {
if a_type !== "object" || a === %raw(`null`) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same, but shorter, faster, easier to maintain, and prevents from forgetting symbol and other types.

Comment on lines -325 to +310
/* a_type = "object" || "symbol" */
b_type == "number" || (b_type == "bigint" || (b_type == "undefined" || b === %raw(`null`)))
) {
if b_type !== "object" || b === %raw(`null`) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good example of forgetting checking various types

Comment on lines -333 to -336
if tag_a == 248 /* object/exception */ {
Obj.magic(Obj.field(a, 1)) === Obj.magic(Obj.field(b, 1))
} else if tag_a == 251 /* abstract_tag */ {
raise(Invalid_argument("equal: abstract value"))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed legacy check

Comment on lines +316 to 317
if tag_a !== tag_b {
false
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure of the usefulness of the optimisation 🤔

But let's keep it.

Comment on lines +318 to 320
} else if O.isArray(a) {
let len_a = Obj.size(a)
let len_b = Obj.size(b)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Length check makes sense only for arrays, so moved it inside of the if O.isArray(a) block

Comment on lines +334 to +335
} else if %raw(`a instanceof Date`) {
if %raw(`b instanceof Date`) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split the check so it earlier returns false when b is not an instance of Date without running some unexpected code.

false
}
} else {
aux_obj_equal(a, b)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've taken a look at the generated code for the function, and it's not very optimised. Not the goal of the PR, so kept it as it is.

Comment on lines 326 to 333
} else if %raw(`a instanceof Error`) {
let a: {..} = Obj.magic(a)
let b: {..} = Obj.magic(b)
if %raw(`b instanceof Error`) && a["message"] === b["message"] {
equal(a["clause"], b["clause"])
} else {
false
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for in doesn't work on Error, so have a special case for them.

@DZakh
Copy link
Contributor Author

DZakh commented Aug 4, 2024

@cknitt Extracted it from the PR #6933
It should be ready for review if the tests are passing ✅

jscomp/runtime/caml_obj.res Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
@DZakh DZakh force-pushed the make-obj-equal-support-errors branch from 1c5b0bf to c912856 Compare August 7, 2024 14:14
@cristianoc
Copy link
Collaborator

Can't find the convo anymore.
f == f being false has obvious footgun properties

The other way is not perfect either

Just saying: there are trade offs and one just needs to decide, with no clear superior choice

@DZakh
Copy link
Contributor Author

DZakh commented Aug 7, 2024

f == f being false has obvious footgun properties

let f = () => ()
f == f // Will be true the same as before

After the change == works the same as in Js for functions, so I don't see why it's going to be a footgun

@cknitt
Copy link
Member

cknitt commented Aug 15, 2024

I don't see a real downside to the function equality change either.

We need to document it as a (potentially) breaking change, but I think it is unlikely in practice that people are using this with functions and relying on an exception being thrown in that case.

@DZakh
Copy link
Contributor Author

DZakh commented Aug 15, 2024

I don't see a real downside to the function equality change either.

We need to document it as a (potentially) breaking change, but I think it is unlikely in practice that people are using this with functions and relying on an exception being thrown in that case.

Agree. I didn't include it in changelog, since I can't imagine a case when somebody can rely on it

@DZakh DZakh requested review from cknitt and cristianoc August 17, 2024 11:14
Copy link
Collaborator

@cristianoc cristianoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.
I remember there are some corner case tests in rescript-core that would make sense to check.
@zth do you remember?

@zth
Copy link
Collaborator

zth commented Aug 18, 2024

Looks good. I remember there are some corner case tests in rescript-core that would make sense to check. @zth do you remember?

Hmmm, I don't immediately recall that. But @DZakh maybe you could try running the tests in Core against this change?

@DZakh
Copy link
Contributor Author

DZakh commented Aug 19, 2024

@cristianoc @zth I ran the tests with my changes and it fails. But that's because of the exns changes I'm fixing right now. In other words, It's not working with rescript-12-alpha

image

@cometkim
Copy link
Member

cometkim commented Aug 26, 2024

I missed here.. What are the use cases of the equality check for Error objects and its messages? Can you add some examples or tests here?

I see... so this is just for completeness
https://github.com/rescript-lang/rescript-compiler/pull/6937/files#diff-9de48029996f7467369a01612855af6297a8e2f68a16be1e927e4a73bdf19ecbR217-R221

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.

5 participants