# Code\_errortype: Error if code\_warntype shows red

**URL:** <https://discourse.julialang.org/t/code-errortype-error-if-code-warntype-shows-red/101809>\
**Category:** General Usage\
**Tags:** testing, code\_warntype, static-analysis\
**Created:** [July 19, 2023, 10:21pm UTC](https://discourse.julialang.org/t/code-errortype-error-if-code-warntype-shows-red/101809 "2023-07-19T22:21:18Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [July 19, 2023, 10:21pm UTC](https://discourse.julialang.org/t/code-errortype-error-if-code-warntype-shows-red/101809/1 "2023-07-19T22:21:18Z")

</div>

I was thinking about the Appendix with JET.jl of the Rust vs Julia in scientific computing blog, and I started wondering if we could create a `code_errortype` or `@code_errortype` that errors if `@code_warntype` would display red. This would allow us to test for the presence of `Any` when we did not intend to use dynamic typing.

> [@Blog post: Rust vs Julia in scientific computing](https://discourse.julialang.org/t/blog-post-rust-vs-julia-in-scientific-computing/101711/1):
>
> Why I think that Julia doesn’t solve the two-language problem and when you should use Rust instead. The blog post is the base for my tiny talk at [Scientific Computing in Rust 2023](https://scientificcomputing.rs/) which is now public: [[Mo Bitar - Rust vs Julia in scientific computing] ](https://www.youtube.com/watch?v=0JkbNFpXlXc) Think of the recording as a trailer. The blog post has many more details and aspects that can not fit into 7 minutes hourglass I hope for discussions across both communities which is why I post here …

In the appendix of the blog, a distinction is made between Rust’s type inference and Julia’s type inference. This difference is due to the semantics of Rust being distinct from Julia in terms of when type inference can be done. In the example below, the type of `v` is not determined until `v.push(1.0)`.

> [@Blog post: Rust vs Julia in scientific computing](https://discourse.julialang.org/t/blog-post-rust-vs-julia-in-scientific-computing/101711/25):
>
> ```julia
> // The type of the vector is not known yet, but it will be derived.
> let mut v = Vec::new();
> 
> // We push a float with 64 bits, therefore v has the type Vec<f64>.
> v.push(1.0);
> 
> // The type will not change!
> // The line below will give a compile error because we are pushing an integer into a vector of floats.
> // v.push(1);
> 
> ```

Rust is allowed to retroactively determine the type of `v` due to [type inference](https://doc.rust-lang.org/rust-by-example/types/inference.html) for `v`, but Julia is not. `Vec::new()` does not imply any element type here.

In Julia, `v = []` is equivalent to `v = Any[]`. The `Any` element type is implied, and the type of `v` is determined on that line as `Vector{Any}`. It’s not that Julia cannot do type inference here, it’s just that we never allowed or asked it to do so while implicitly asking Julia to use dynamic typing.

We can ask Julia to do type inference at the construction of `v1` though if this is done after at least one element types is known.

```julia
julia> function test()
           v2 = [1.0] # Vector{Float64}, type inference
           v1 = [v2] # Vector{Vector{Float64}}, type inference
           push!(v1, [1]) # The type of v1 does not change!
           # [1] is converted from Vector{Int} to Vector{Float64}

           last = pop!(v1) # Vector{Float64}
           println("OK") # OK
           println(last.pop()) # JET.jl static analysis error
           println("No problem!") # Unreachable

           nothing
       end
test (generic function with 1 method)

julia> @report_call test()
═════ 1 possible error found ═════
┌ test() @ Main ./REPL[68]:8
│┌ getproperty(x::Vector{Float64}, f::Symbol) @ Base ./Base.jl:37
││ type Array has no field pop: Base.getfield(x::Vector{Float64}, f::Symbol)
│└────────────────────

```

Not only does `@code_warntype` know that `v1` will be `Vector{Vector{Float64}}`. It even figured out that the function is not going to be returning `nothing`.

```julia
julia> @code_warntype test()
MethodInstance for test()
  from test() @ Main REPL[68]:1
Arguments
  #self#::Core.Const(test)
Locals
  last::Vector{Float64}
  v1::Vector{Vector{Float64}}
  v2::Vector{Float64}
Body::Union{}
1 ─ (v2 = Base.vect(1.0))
│ (v1 = Base.vect(v2))
│ %3 = v1::Vector{Vector{Float64}}
│ %4 = Base.vect(1)::Vector{Int64}
│ Main.push!(%3, %4)
│ (last = Main.pop!(v1))
│ Main.println("OK")
│ Base.getproperty(last, :pop)
│ Core.Const(:((%8)()))
│ Core.Const(:(Main.println(%9)))
│ Core.Const(:(Main.println("No problem!")))
└── Core.Const(:(return Main.nothing))

```

Compare that output of `@code_warntype` to the following which knows the return type.

```julia
julia> function test_nopop()
           v2 = [1.0] # Vector{Float64}, type inference
           v1 = [v2] # Vector{Vector{Float64}}, type inference
           push!(v1, [1]) # The type of v1 does not change!

           last = pop!(v1) # Vector{Float64}
           println("OK") # OK
           #println(last.pop()) # Runtime error 💥
           println("No problem!") # Not reachable

           nothing
       end
test_nopop (generic function with 1 method)

julia> @code_warntype test_nopop()
MethodInstance for test_nopop()
  from test_nopop() @ Main REPL[91]:1
Arguments
  #self#::Core.Const(test_nopop)
Locals
  last::Vector{Float64}
  v1::Vector{Vector{Float64}}
  v2::Vector{Float64}
Body::Nothing
1 ─ (v2 = Base.vect(1.0))
│ (v1 = Base.vect(v2))
│ %3 = v1::Vector{Vector{Float64}}
│ %4 = Base.vect(1)::Vector{Int64}
│ Main.push!(%3, %4)
│ (last = Main.pop!(v1))
│ Main.println("OK")
│ Main.println("No problem!")
└── return Main.nothing

```

The appendix amounts to “JET.jl static analysis does not work when we ask Julia to be dynamic”. That’s fair.

However, we also have tools to check for dynamic types and it may be possible to refactor to avoid them. In this specific case, after noting the differences between how Rust and Julia do type inference, we could actually refactor the Julia code to infer the type of `v1` to be a `Vector` with a narrower element type.

One capability that is missing is the ability to detect “red” in `@code_warntype` as part of testing.

```julia
julia> function code_errortype(f, types; optimize::Bool=false, debuginfo::Symbol=:default, kwargs...)
           for (src, rettype) in code_typed(f, types; optimize, kwargs...)
               p = src.parent
               nargs::Int = 0
               if p isa Core.MethodInstance
                   p.def isa Method && (nargs = p.def.nargs)
               end
               if src.slotnames !== nothing
                   slotnames = Base.sourceinfo_slotnames(src)[nargs+1:end]
                   non_dispatchable = (!Base.isdispatchelem).(src.slottypes[nargs+1:end])
                   if any(non_dispatchable)
                       msg_io = IOContext(IOBuffer(), :color => true)
                       println(msg_io, "Non-dispatchable slot type detected. Consider improving type inference:")
                       for (nd, name, type) in zip(non_dispatchable, slotnames, src.slottypes[nargs+1:end])
                           if nd
                               print(msg_io, " ", name)
                               Base.emphasize(msg_io, "::$type")
                               println(msg_io)
                           end
                       end
                       error(String(take!(msg_io.io)))
                   end
               end
           end
       end
code_errortype (generic function with 1 method)

julia> function test()
           v1 = [] # Vector{Any}
           v2 = [1.0]
           push!(v1, v2) # v1 is still Vector{Any}

           last = pop!(v1) # Any
           println("OK") # OK
           println(last.pop()) # Runtime error 💥
           println("No problem!") # Not reachable

           nothing
       end
test (generic function with 1 method)

julia> code_errortype(test, Tuple{})
ERROR: Non-dispatchable slot type detected. Consider improving type inference:
  last::Any

Stacktrace:
 [1] error(s::String)
   @ Base .\error.jl:35
 [2] code_errortype(f::Function, types::Type; optimize::Bool, debuginfo::Symbol, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ Main .\REPL[256]:21
 [3] code_errortype(f::Function, types::Type)
   @ Main .\REPL[256]:1
 [4] top-level scope
   @ REPL[258]:1

```

Some questions for the reader:

- Does a function such as `code_errortype` already exist?
- Do you have any suggestions how to improve this detection function?
- Should the presence of `Vector{Any}` also be reported as an issue?
- Does a return type of `Union{}` indicate that the method always throws?

---

<div class="post-metadata">

**Author:** ![Zentrik](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/zentrik/32/35409_2.png) [@Zentrik](https://discourse.julialang.org/u/Zentrik)\
**Post date:** [July 19, 2023, 11:27pm UTC](https://discourse.julialang.org/t/code-errortype-error-if-code-warntype-shows-red/101809/2 "2023-07-19T23:27:38Z")

</div>

Cthulhu.jl checks whether each type is stable or not here, [https://github.com/JuliaDebug/Cthulhu.jl/blob/master/TypedSyntax/src/show.jl#L106](https://github.com/JuliaDebug/Cthulhu.jl/blob/master/TypedSyntax/src/show.jl#L106). So alternatively you could throw an error here if you use cthulhu.
