Add mutation from `@foo ||= bar` to `@foo = bar || @foo`
This mutation would have helped me locate a short circuit issue that individual unit tests did not surface:
def initialize
@changed = false
end
def add(x)
@changed ||= add_record(x)
end
def remove(x)
@changed ||= remove_record(x)
end
or
def initialize
@changed = false
end
def change(x)
@changed ||= remove_record(x)
@changed ||= add_record(x)
end
if add(x) is called then subsequently remove(x) (or vice versa) the short_circuit caused the second execution to fail. I could not find this mutation in the current code. Testing either add(x) or remove(x) individually would not be an effective spec to surface an issue. This may be a HOM.
I assumed (which may not be the case) that mutant already does foo || bar -> bar || foo and more importantly perhaps foo && bar -> bar && foo mutations.
This library is a little new to me. What category of transformation would this be? I see that it would have exposed the logic error in your code, but how is it more generally applicable?
@tjchambers Would it have helped if expressions that are just syntactic sugar were expanded first, then mutated:
@foo ||= bar
Would become:
@foo = @foo || bar
Then that would be mutated in the different ways that mutant does, including negating @foo and bar, replacing each with nil, and so on.
@tjchambers Would it have helped if expressions that are just syntactic sugar were expanded first, then mutated:
@dkubb JFYI: a ||= b is NOT syntactic sugar for a = a || bar. At least not in all cases.
If a is an lvar / ivar / gvar your statement is true. If a is an attribute its not true. Since it would trigger an attribute read and an attribute write separately:
class Foo
def attribute=(value)
p __method__
end
def attribute
p __method__
nil
end
end
Foo.new.attribute ||= :bar
prints:
:attribute
:attribute=
BTW Above inorthogonality is the reason rubies parser has separate nodes for all op assigns.
@mbj so it's more like:
foo || (foo = bar)
@mbj so it's more like:
foo || (foo = bar)
Yes.