Skip to content

Conversation

sabiwara
Copy link
Contributor

* `:migrate_unless` (since v1.18.0) - when `true`,
rewrites `unless` expressions using `if` with a negated condition, for example
`if foo, do:` becomes `unless !foo, do:`.
Defaults to the value of the `:migrate` option. This option changes the AST.
Copy link
Member

Choose a reason for hiding this comment

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

What about the corner cases we discussed in the past, such as unless x = expr() do, do we handle those as well? Or we don't? If we don't, we must say not all cases can be automatically migrated. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do handle these, it becomes if !(x = expr()) which is terrible code, but it does work fine and I expect unless x = expr() to be a very rare need anyway since x could only be false or nil. WDYT?

#13769 (comment)
#13769 (comment)

Co-authored-by: Juan Peri <eternoperegrino@gmail.com>
@sabiwara sabiwara merged commit 5902c29 into elixir-lang:main Sep 20, 2024
9 checks passed
@sabiwara sabiwara deleted the migrate-unless branch September 20, 2024 14:00
@novaugust
Copy link
Contributor

Shipped this in styler's 1.1 today - though I see I'm a weekend behind elixir-lang!

I ended up using ! with everything but in since there's syntactic sugar for that, but same-same =)

Anyways, thanks for taking care of this in core ❤️ Looking forward to just passing |> Code.quoted_to_algebra([{:migrate_unless, true} | opts]) to take advantage of your work + deleting my version of it 😍

defp negate_condition(condition) do
case condition do
{neg, _, [condition]} when neg in [:!, :not] -> condition
{:|>, _, _} -> {:|>, [], [condition, {{:., [], [Kernel, :!]}, [closing: []], []}]}
Copy link
Contributor

Choose a reason for hiding this comment

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

we found the migrations more readable not doing this particular transformation, as this hides the most impactful part of the statement. compare:

# given:
unless Widget |> where(foo: ^bar) |> Repo.exists?(), do: ...
# rewritten to if with pipe
if Widget |> where(foo: ^bar) |> Repo.exists?() |> Kernel.!(), do: ...
# treated as any other statement
if !(Widget |> where(foo: ^bar) |> Repo.exists?()), do:

our team asked for the latter as it has a much more natural-language read (not to mention that Kernel.!() is unlikely to be written by a human in any case)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the suggestion 💜 Even if I'm the one who originally implemented it as |> Kernel.!(), now that you mention it I have to agree.

Plus this is removing complexity so I'm all for it.

Do you want to send a PR @novaugust?

Copy link
Contributor

Choose a reason for hiding this comment

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

happy to, i'll kick one out tomorrow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants