Skip to content

Create all_equal_linter() #2885

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

Create all_equal_linter() #2885

wants to merge 6 commits into from

Conversation

Bisaloo
Copy link
Collaborator

@Bisaloo Bisaloo commented Jul 22, 2025

Fix #2610

Copy link

codecov bot commented Jul 22, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.27%. Comparing base (ab329d8) to head (27cfd18).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2885   +/-   ##
=======================================
  Coverage   99.27%   99.27%           
=======================================
  Files         128      129    +1     
  Lines        7160     7177   +17     
=======================================
+ Hits         7108     7125   +17     
  Misses         52       52           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Bisaloo Bisaloo force-pushed the all_equal_linter branch from a7111d8 to a5e900b Compare July 22, 2025 08:55
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shall we cover attr.all.equal too?

Either TRUE (NULL for attr.all.equal) or a vector of mode "character" describing the differences between target and current.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I would say probably not. Since it returns NULL in the case of no differences, I would expect that it's already wrapped in is.null() and thus safe.

#' Usage of `all.equal()` without wrapping it is `isTRUE()` in `if` clauses, or
#' preceded by the negation operator `!`, are thus likely to generate unexpected
#' errors if the compared objects have differences.
#' An alternative is to use `identical()` to compare vector of strings or when
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wouldn't advertise this:

  • isTRUE() is specifically designed to overcome shortcomings of identical(x, TRUE)
  • I guess it's rare to do something like identical(all.equal(x, y), "Mean relative difference ...")

There may be some advantages to identical() but I don't think we need to cite them here, too detailed

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it became clearer later in the review but the idea was not to recommend wrapping all.equal() in identical(). I agree isTRUE() is a better solution here.

But I have seen the following thought process happen:

x <- letters

if (x == letters) {
  message("success")
}
#> Error in if (x == letters) {: the condition has length > 1

# Oh no, it doesn't work. How can I compare that all elements of x are equal to
# elements of letters

if (all.equal(x, letters)) {
  message("success")
}
#> success

Created on 2025-07-23 with reprex v2.1.1

In reality, in these cases, the author was really looking for identical(), not all.equal().

I have the feeling this is what happened in most cases where all.equal() compares character vectors (see https://github.com/search?q=org%3Acran%20%2Fall%5C.equal%5C(names%5C(%2F&type=code). This might be a good new case for this linter in a follow up PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Would you have a suggestion to phrase this idea more clearly?

#' )
#'
#' lint(
#' text = '!identical(a, b)',
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would just use isTRUE(), I think that's the safest drop-in alternative

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see, this corresponds to the clarified note in the lint_message... maybe just add a comment above this example with the same clarification about not needing tolerance?

#' preceded by the negation operator `!`, are thus likely to generate unexpected
#' errors if the compared objects have differences.
#' An alternative is to use `identical()` to compare vector of strings or when
#' exact equality is expected.
Copy link
Collaborator

@MichaelChirico MichaelChirico Jul 22, 2025

Choose a reason for hiding this comment

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

BTW stopifnot() has some built-in logic for handling all.equal() checks, c.f.

stopifnot(all.equal(1, 1.001))
# Error: 1 and 1.001 are not equal:
#   Mean relative difference: 0.001

I might actually lint this code while we're at it:

stopifnot(isTRUE(all.equal(x, y)))

Since it gives a worse error:

stopifnot(isTRUE(all.equal(1, 1.001)))
# Error: isTRUE(all.equal(1, 1.001)) is not TRUE

Some hits:

https://github.com/search?q=lang%3AR%20%2Fstopifnot%5C(%2F%20%2FisTRUE%5C(all%5C.equal%5C(%2F&type=code

We can do that as a follow-up if you'd like; in this PR at least I'd want to have some mention of stopifnot().

Copy link
Collaborator

Choose a reason for hiding this comment

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

We might as well test isFALSE(all.equal()) here too, it gets some usage:

https://github.com/search?q=lang%3AR+%2FisFALSE%5C%28all%5C.equal%5C%28%2F&type=code

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, isFALSE(all.equal()) is definitely wrong since it'll always return FALSE. I wonder if that'd fit better under a istrue_linter() that check cases where authors wanted !isTRUE() rather than isFALSE().

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Similarly for stopifnot(), great catch but I wonder if it should live here or in stopifnot_all_linter() 🤔

]"
)

xml_nodes_to_lints(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If we don't end up covering more cases in this linter, that'd be a good candidate for make_lint_from_function_xpath().

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.

New linter for if(all.equal(...))
2 participants