Dot-broadcasting for short-circuiting ops .&& and .|| (#39594)
I have long wanted a proper fix for issue #5187. It was the very first Julia issue I filed.
This is a shot at such a fix. This PR:
* Enables parsing for `.&&` and `.||`. They are parsed into `Expr(:call, :.&&, ...)` expressions at the same precedence as their respective `&&` and `||`:
```julia-repl
julia> Meta.show_sexpr(:(a .&& b))
(:call, :.&&, :a, :b)
```
* Unlike all other dotted operators `.op` (like `.+`), the `op`-alone part (`var"&&"`) is not an exported name from Base. As such, this effectively lowers to `broadcasted((x,y)->x && y, ...)`, but instead of using an anonymous function I've named it `Base.andand` and `Base.oror`:
```julia-repl
julia> Meta.@lower a .&& b
:($(Expr(:thunk, CodeInfo(
@ none within `top-level scope'
1 ─ %1 = Base.broadcasted(Base.andand, a, b)
│ %2 = Base.materialize(%1)
└── return %2
))))
```
* I've used a named function to enable short-circuiting behavior _within the broadcast kernel itself_. In the case that the second argument is a part of the same fused broadcast kernel, it will only evaluate if required:
```julia-repl
julia> mutable struct F5187; x; end
julia> (f::F5187)(x) = (f.x += x)
julia> (iseven.(1:4) .|| (F5187(0)).(ones(4)))
4-element Vector{Real}:
1.0
true
2.0
true
```
Co-authored-by: Simeon Schaub <simeondavidschaub99@gmail.com>