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...

view this post on Zulip Jakob Nybo Nissen (Aug 15 2023 at 04:52):

The issue is that currently, the Julia compiler doesn't realise that if you load the same field multiple times from the same struct, the field is of the same type. So, even though you checked that a.x is not nothing and b.x is not nothing, when you do the comparison, Julia has "forgotten" that you checked

view this post on Zulip Jakob Nybo Nissen (Aug 15 2023 at 04:53):

You solve it by storing them in a variable

view this post on Zulip Jakob Nybo Nissen (Aug 15 2023 at 04:56):

See more here: https://aviatesk.github.io/JET.jl/stable/jetanalysis/#no-matching-method-found-(x/y-union-split)

view this post on Zulip Jesper Stemann Andersen (Aug 15 2023 at 05:20):

Right! I should’ve re-read the documentation for JET… Thanks!

view this post on Zulip Notification Bot (Aug 15 2023 at 06:09):

Jesper Stemann Andersen has marked this topic as resolved.

view this post on Zulip Sebastian Pfitzner (Aug 15 2023 at 07:11):

worth noting that JET is correct in this specific case -- Bar is mutable after all, so some other thread could write a nothing to a.x or b.x after your check. Only accessing the field once and storing that in a local variable technically changes the behaviour of your program

view this post on Zulip Jesper Stemann Andersen (Aug 15 2023 at 07:16):

Right ... I was also wondering whether that point should be added as a footnote to the JET documentation...


Last updated: Nov 06 2024 at 04:40 UTC