jw3126 / ArgCheck.jl

Package for checking function arguments
Other
102 stars 6 forks source link

Confusing `UndefVarError` error #44

Open ericphanson opened 8 months ago

ericphanson commented 8 months ago
julia> using ArgCheck

julia> [1] .== [2]
1-element BitVector:
 0

julia> @check [1] .== [2]
ERROR: UndefVarError: `.==` not defined
Stacktrace:
 [1] top-level scope
   @ REPL[111]:1

Here, the check I wrote is bad since it doesn't return a bool, but the error message is confusing.

jw3126 commented 8 months ago

Nice catch, thanks! The @macroexpand looks innocent:

julia> @macroexpand @argcheck 1 .== 2
quote
    $(Expr(:meta, :begin_optional, ArgCheck.LabelArgCheck()))
    begin
        var"#264###calle#237" = (.==)
        var"#265###arg#235" = 1
        var"#266###arg#236" = 2
        if var"#264###calle#237"(var"#265###arg#235", var"#266###arg#236")
            ArgCheck.nothing
        else
            ArgCheck.throw_check_error(ArgCheck.CallErrorInfo($(QuoteNode(:(1 .== 2))), ArgCheck.ArgCheckFlavor(), $(QuoteNode(Any[:.==, 1, 2])), [var"#264###calle#237", var"#265###arg#235", var"#266###arg#236"], ()))
        end
    end
    $(Expr(:meta, :end_optional, ArgCheck.LabelArgCheck()))
end
ericphanson commented 8 months ago

hm, what does that imply?

jw3126 commented 8 months ago

I can reproduce the error, but I don't know how it arises. I can run var"#264###calle#237" = (.==) in the REPL just fine.

jw3126 commented 8 months ago

I think it is either some subtle macro shenanigan or something obvious I am missing.

ericphanson commented 8 months ago

Hm...

julia> expr = @macroexpand @check 1 .== 2
quote
    $(Expr(:meta, :begin_optional, ArgCheck.LabelArgCheck()))
    begin
        var"#22###calle#236" = (.==)
        var"#23###arg#234" = 1
        var"#24###arg#235" = 2
        if var"#22###calle#236"(var"#23###arg#234", var"#24###arg#235")
            ArgCheck.nothing
        else
            ArgCheck.throw_check_error(ArgCheck.CallErrorInfo($(QuoteNode(:(1 .== 2))), ArgCheck.CheckFlavor(), $(QuoteNode(Any[:.==, 1, 2])), [var"#22###calle#236", var"#23###arg#234", var"#24###arg#235"], ()))
        end
    end
    $(Expr(:meta, :end_optional, ArgCheck.LabelArgCheck()))
end

julia> eval(expr)
ERROR: UndefVarError: `.==` not defined
Stacktrace:
 [1] top-level scope
   @ :0
 [2] eval
   @ Core ./boot.jl:385 [inlined]
 [3] eval(x::Expr)
   @ Base.MainInclude ./client.jl:491
 [4] top-level scope
   @ REPL[13]:1

julia> expr2 = quote
           $(Expr(:meta, :begin_optional, ArgCheck.LabelArgCheck()))
           begin
               var"#22###calle#236" = (.==)
               var"#23###arg#234" = 1
               var"#24###arg#235" = 2
               if var"#22###calle#236"(var"#23###arg#234", var"#24###arg#235")
                   ArgCheck.nothing
               else
                   ArgCheck.throw_check_error(ArgCheck.CallErrorInfo($(QuoteNode(:(1 .== 2))), ArgCheck.CheckFlavor(), $(QuoteNode(Any[:.==, 1, 2])), [var"#22###calle#236", var"#23###arg#234", var"#24###arg#235"], ()))
               end
           end
           $(Expr(:meta, :end_optional, ArgCheck.LabelArgCheck()))
       end
quote
    #= REPL[14]:2 =#
    $(Expr(:meta, :begin_optional, ArgCheck.LabelArgCheck()))
    #= REPL[14]:3 =#
    begin
        #= REPL[14]:4 =#
        var"#22###calle#236" = (.==)
        #= REPL[14]:5 =#
        var"#23###arg#234" = 1
        #= REPL[14]:6 =#
        var"#24###arg#235" = 2
        #= REPL[14]:7 =#
        if var"#22###calle#236"(var"#23###arg#234", var"#24###arg#235")
            #= REPL[14]:8 =#
            ArgCheck.nothing
        else
            #= REPL[14]:10 =#
            ArgCheck.throw_check_error(ArgCheck.CallErrorInfo($(QuoteNode(:(1 .== 2))), ArgCheck.CheckFlavor(), $(QuoteNode(Any[:.==, 1, 2])), [var"#22###calle#236", var"#23###arg#234", var"#24###arg#235"], ()))
        end
    end
    #= REPL[14]:13 =#
    $(Expr(:meta, :end_optional, ArgCheck.LabelArgCheck()))
end

julia> eval(expr2)
ERROR: CheckError: 1 .== 2 must hold. Got
.== => BroadcastFunction(==)
Stacktrace:
 [1] throw_check_error(info::Any)
   @ ArgCheck ~/.julia/packages/ArgCheck/CA5vv/src/checks.jl:280
 [2] top-level scope
   @ REPL[14]:10
 [3] eval
   @ Core ./boot.jl:385 [inlined]
 [4] eval(x::Expr)
   @ Base.MainInclude ./client.jl:491
 [5] top-level scope
   @ REPL[15]:1

Very strange! If I copy the printed version, it works. If I dump both, I see that .== has two different representations. In the problematic one (generated by hte macro):

          head: Symbol =
          args: Array{Any}((2,))
            1: Symbol #22###calle#236
            2: Symbol .==

vs in the copy-paste printed one:

          head: Symbol =
          args: Array{Any}((2,))
            1: Symbol #22###calle#236
            2: Expr
              head: Symbol .
              args: Array{Any}((1,))
                1: Symbol ==
jw3126 commented 8 months ago

Good work. Now it feels to me this is a macro glitch, would be nice to produce an MWE that does not need ArgCheck.

jw3126 commented 8 months ago
macro m(code)
    esc(code.args[1])
end

@m 1 .== 2
ERROR: LoadError: UndefVarError: `.==` not defined
Stacktrace:
 [1] include(fname::String)
   @ Base.MainInclude ./client.jl:489
 [2] top-level scope
   @ REPL[1]:1
in expression starting at /home/jan/doit.jl:5
jw3126 commented 8 months ago

Opened an issue https://github.com/JuliaLang/julia/issues/53522