Stream: helpdesk (published)

Topic: JET analysis of Base.isless for field Union{T, Nothing}


view this post on Zulip Jesper Stemann Andersen (Aug 14 2023 at 21:02):

I have a (mutable, edit) struct with a field that has type Union{T, Nothing}, and I've been trying to implement Base.isless for this struct, but I cannot figure out how to make JET.jl happy...

A simple function works fine,

julia> function foo(a::Union{Int, Nothing}, b::Union{Int, Nothing})
           if a isa Nothing
               if b isa Nothing
                   return false
               else
                   return true
               end
           else
               if b isa Nothing
                   return false
               else
                   return a < b
               end
           end
       end
foo (generic function with 1 method)

julia> JET.@report_opt foo(1, 2)
No errors detected

julia> JET.@report_call foo(1,2)
No errors detected

But once the a and b are encapsulated in a struct, JET complains:

julia> mutable struct Bar
           x::Union{Int, Nothing}
       end

julia> function Base.isless(a::Bar, b::Bar)
           if a.x isa Nothing
               if b.x isa Nothing
                   return false
               else
                   return true
               end
           else
               if b.x isa Nothing
                   return false
               else
                   return a.x < b.x
               end
           end
       end

julia> JET.@report_opt Base.isless(Bar(1), Bar(2))
═════ 2 possible errors found ═════
┌ isless(a::Bar, b::Bar) @ Main ./REPL[16]:12
│┌ <(x::Int64, y::Nothing) @ Base ./operators.jl:343
││ runtime dispatch detected: isless(x::Int64, y::Nothing)
│└────────────────────
│┌ <(x::Nothing, y::Int64) @ Base ./operators.jl:343
││ runtime dispatch detected: isless(x::Nothing, y::Int64)
│└────────────────────

julia> JET.@report_call Base.isless(Bar(1), Bar(2))
═════ 3 possible errors found ═════
┌ isless(a::Bar, b::Bar) @ Main ./REPL[16]:12
│┌ <(x::Nothing, y::Nothing) @ Base ./operators.jl:343
││ no matching method found `isless(::Nothing, ::Nothing)`: isless(x::Nothing, y::Nothing)
│└────────────────────
│┌ <(x::Int64, y::Nothing) @ Base ./operators.jl:343
││ no matching method found `isless(::Int64, ::Nothing)`: isless(x::Int64, y::Nothing)
│└────────────────────
│┌ <(x::Nothing, y::Int64) @ Base ./operators.jl:343
││ no matching method found `isless(::Nothing, ::Int64)`: isless(x::Nothing, y::Int64)
│└────────────────────

Using JET v0.8.9 on Julia v1.9.2.

Any ideas?

view this post on Zulip Jesper Stemann Andersen (Aug 14 2023 at 21:12):

... I guess JET.@report_opt is already telling me, quite clearly, that it ends up in dynamic dispatch..., so of course making the Bar field type a type parameter <: Union{Int, Nothing} works...

julia> mutable struct Bar{T<:Union{Int, Nothing}}
           x::T
       end

julia> function Base.isless(a::Bar, b::Bar)
                  if a.x isa Nothing
                      if b.x isa Nothing
                          return false
                      else
                          return true
                      end
                  else
                      if b.x isa Nothing
                          return false
                      else
                          return a.x < b.x
                      end
                  end
              end

julia> using JET

julia> JET.@report_opt Base.isless(Bar(1), Bar(2))
No errors detected

julia> JET.@report_call Base.isless(Bar(1), Bar(2))
No errors detected

... but that's not what I was looking for - I would like to be able to modify the field - make it a Nothing and vice versa...


Last updated: Oct 02 2023 at 04:34 UTC