diff --git a/base/REPLCompletions.jl b/base/REPLCompletions.jl index c056987ae25d9..207be534a0520 100644 --- a/base/REPLCompletions.jl +++ b/base/REPLCompletions.jl @@ -323,10 +323,15 @@ function complete_methods(ex_org::Expr) out = String[] t_in = Tuple{Core.Typeof(func), args_ex...} # Input types na = length(args_ex)+1 - for method in methods(func) + ml = methods(func) + kwtype = isdefined(ml.mt, :kwsorter) ? Nullable{DataType}(typeof(ml.mt.kwsorter)) : Nullable{DataType}() + io = IOBuffer() + for method in ml # Check if the method's type signature intersects the input types - typeintersect(Tuple{method.sig.parameters[1 : min(na, end)]...}, t_in) != Union{} && - push!(out,string(method)) + if typeintersect(Tuple{method.sig.parameters[1 : min(na, end)]...}, t_in) != Union{} + show(io, method, kwtype=kwtype) + push!(out, takebuf_string(io)) + end end return out end diff --git a/base/error.jl b/base/error.jl index 994dde48f23a1..70a771d754545 100644 --- a/base/error.jl +++ b/base/error.jl @@ -27,7 +27,7 @@ backtrace() = ccall(:jl_backtrace_from_here, Array{Ptr{Void},1}, (Int32,), false catch_backtrace() = ccall(:jl_get_backtrace, Array{Ptr{Void},1}, ()) ## keyword arg lowering generates calls to this ## -kwerr(kw) = error("unrecognized keyword argument \"", kw, "\"") +kwerr(kw, args...) = throw(MethodError(typeof(args[1]).name.mt.kwsorter, (kw,args...))) ## system error handling ## diff --git a/base/methodshow.jl b/base/methodshow.jl index 928c8b09fe70a..fd9aa090b1f0b 100644 --- a/base/methodshow.jl +++ b/base/methodshow.jl @@ -78,7 +78,13 @@ function kwarg_decl(sig::ANY, kwtype::DataType) kwli = ccall(:jl_methtable_lookup, Any, (Any, Any), kwtype.name.mt, sig) if kwli !== nothing kwli = kwli::Method - return filter(x->!('#' in string(x)), kwli.lambda_template.slotnames[kwli.lambda_template.nargs+1:end]) + kws = filter(x->!('#' in string(x)), kwli.lambda_template.slotnames[kwli.lambda_template.nargs+1:end]) + # ensure the kwarg... is always printed last. The order of the arguments are not + # necessarily the same as defined in the function + i = findfirst(x -> endswith(string(x), "..."), kws) + i==0 && return kws + push!(kws, kws[i]) + return deleteat!(kws,i) end return () end diff --git a/base/replutil.jl b/base/replutil.jl index c1ed0f2543953..90260b515363c 100644 --- a/base/replutil.jl +++ b/base/replutil.jl @@ -123,6 +123,16 @@ function showerror(io::IO, ex::MethodError) ft = typeof(f) name = ft.name.mt.name f_is_function = false + kwargs = Any[] + if startswith(string(ft.name), "#kw#") + f = ex.args[2] + ft = typeof(f) + name = ft.name.mt.name + arg_types_param = arg_types_param[3:end] + temp = ex.args[1] + kwargs = Any[(temp[i*2-1], temp[i*2]) for i in 1:(length(temp) รท 2)] + ex = MethodError(f, ex.args[3:end]) + end if f == Base.convert && length(arg_types_param) == 2 && !is_arg_types f_is_function = true # See #13033 @@ -150,6 +160,14 @@ function showerror(io::IO, ex::MethodError) print(io, "::$typ") i == length(arg_types_param) || print(io, ", ") end + if !isempty(kwargs) + print(io, "; ") + for (i, (k, v)) in enumerate(kwargs) + print(io, k, "=") + show(IOContext(io, :limit=>true), v) + i == length(kwargs) || print(io, ", ") + end + end print(io, ")") end if ft <: AbstractArray @@ -189,7 +207,7 @@ function showerror(io::IO, ex::MethodError) "\nsince type constructors fall back to convert methods.") end try - show_method_candidates(io, ex) + show_method_candidates(io, ex, kwargs) catch warn(io, "Error showing method candidates, aborted") end @@ -222,7 +240,7 @@ function showerror_nostdio(err, msg::AbstractString) ccall(:jl_printf, Cint, (Ptr{Void},Cstring), stderr_stream, "\n") end -function show_method_candidates(io::IO, ex::MethodError) +function show_method_candidates(io::IO, ex::MethodError, kwargs::Vector=Any[]) is_arg_types = isa(ex.args, DataType) arg_types = is_arg_types ? ex.args : typesof(ex.args...) arg_types_param = Any[arg_types.parameters...] @@ -233,7 +251,7 @@ function show_method_candidates(io::IO, ex::MethodError) else f = ex.f end - + ft = typeof(f) lines = [] # These functions are special cased to only show if first argument is matched. special = f in [convert, getindex, setindex!] @@ -262,7 +280,6 @@ function show_method_candidates(io::IO, ex::MethodError) use_constructor_syntax = isa(func, Type) print(buf, use_constructor_syntax ? func : typeof(func).name.mt.name) end - right_matches = 0 tv = method.tvars if !isa(tv,SimpleVector) tv = Any[tv] @@ -318,17 +335,19 @@ function show_method_candidates(io::IO, ex::MethodError) end end - if right_matches > 0 + if right_matches > 0 || length(ex.args) < 2 if length(t_i) < length(sig) # If the methods args is longer than input then the method # arguments is printed as not a match - for sigtype in sig[length(t_i)+1:end] + for (k, sigtype) in enumerate(sig[length(t_i)+1:end]) if Base.isvarargtype(sigtype) sigstr = string(sigtype.parameters[1], "...") else sigstr = string(sigtype) end - print(buf, ", ") + if !((min(length(t_i), length(sig)) == 0) && k==1) + print(buf, ", ") + end if Base.have_color Base.with_output_color(:red, buf) do buf print(buf, "::$sigstr") @@ -338,7 +357,28 @@ function show_method_candidates(io::IO, ex::MethodError) end end end + kwords = Symbol[] + if isdefined(ft.name.mt, :kwsorter) + kwsorter_t = typeof(ft.name.mt.kwsorter) + kwords = kwarg_decl(method.sig, kwsorter_t) + length(kwords) > 0 && print(buf, "; ", join(kwords, ", ")) + end print(buf, ")") + if !isempty(kwargs) + unexpected = Symbol[] + if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords)) + for (k, v) in kwargs + if !(k in kwords) + push!(unexpected, k) + end + end + end + if !isempty(unexpected) + Base.with_output_color(:red, buf) do buf + print(buf, " got an unsupported keyword argument \"", join(unexpected, "\", \""), "\"") + end + end + end push!(lines, (buf, right_matches)) end end diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 92a6fbcf1ad05..2a081e9e5a7bb 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -411,7 +411,8 @@ sparams)) (keyword-sparam-names (map (lambda (s) (if (symbol? s) s (cadr s))) keyword-sparams))) - (let ((kw (gensy)) (i (gensy)) (ii (gensy)) (elt (gensy)) (rkw (gensy)) + (let ((kw (gensy)) (i (gensy)) (ii (gensy)) (elt (gensy)) + (rkw (if (null? restkw) '() (symbol (string (car restkw) "...")))) (mangled (symbol (string "#" (if name (undot-name name) 'call) "#" (string (current-julia-module-counter))))) (flags (map (lambda (x) (gensy)) vals))) @@ -506,7 +507,8 @@ ,else))) (if (null? restkw) ;; if no rest kw, give error for unrecognized - `(call (top kwerr) ,elt) + `(call (top kwerr) ,kw ,@(map arg-name pargl),@(if (null? vararg) '() + (list `(... ,(arg-name (car vararg)))))) ;; otherwise add to rest keywords `(ccall 'jl_array_ptr_1d_push Void (tuple Any Any) ,rkw (tuple ,elt diff --git a/test/keywordargs.jl b/test/keywordargs.jl index ce2c979f051af..eff8844ec7fa4 100644 --- a/test/keywordargs.jl +++ b/test/keywordargs.jl @@ -9,7 +9,7 @@ kwf1(ones; tens=0, hundreds=0) = ones + 10*tens + 100*hundreds @test kwf1(3, tens=7, hundreds=2) == 273 @test_throws MethodError kwf1() # no method, too few args -@test_throws ErrorException kwf1(1, z=0) # unsupported keyword +@test_throws MethodError kwf1(1, z=0) # unsupported keyword @test_throws MethodError kwf1(1, 2) # no method, too many positional args # keyword args plus varargs @@ -18,7 +18,7 @@ kwf2(x, rest...; y=1) = (x, y, rest) @test isequal(kwf2(0,1,2), (0, 1, (1,2))) @test isequal(kwf2(0,1,2,y=88), (0, 88, (1,2))) @test isequal(kwf2(0,y=88,1,2), (0, 88, (1,2))) -@test_throws ErrorException kwf2(0, z=1) +@test_throws MethodError kwf2(0, z=1) @test_throws MethodError kwf2(y=1) # keyword arg with declared type @@ -191,7 +191,7 @@ let f = (x;a=1,b=2)->(x, a, b) @test f(0) === (0, 1, 2) @test f(1,a=10,b=20) === (1,10,20) @test f(0,b=88) === (0, 1, 88) - @test_throws ErrorException f(0,z=1) + @test_throws MethodError f(0,z=1) end @test ((a=2)->10a)(3) == 30 @test ((a=2)->10a)() == 20 diff --git a/test/replcompletions.jl b/test/replcompletions.jl index f7d9d88531b7f..0bc56bb4cc11b 100644 --- a/test/replcompletions.jl +++ b/test/replcompletions.jl @@ -43,6 +43,8 @@ module CompletionFoo const a=x->x test6()=[a, a] + kwtest(; x=1, y=2, w...) = pass + array = [1, 1] varfloat = 0.1 @@ -325,6 +327,12 @@ c, r, res = test_complete(s) @test length(c) == 2 ################################################################# +s = "CompletionFoo.kwtest( " +c, r, res = test_complete(s) +@test !res +@test length(c) == 1 +@test contains(c[1], "x, y, w...") + # Test of inference based getfield completion s = "\"\"." c,r = test_complete(s) diff --git a/test/replutil.jl b/test/replutil.jl index 40d69ef76f15f..a8e0d2acead96 100644 --- a/test/replutil.jl +++ b/test/replutil.jl @@ -52,16 +52,19 @@ no_color = no_color = "\nClosest candidates are:\n method_c3(::Float64, !Matche test_have_color(buf, color, no_color) # Test for the method error in issue #8651 -Base.show_method_candidates(buf, MethodError(readline,("",))) -test_have_color(buf, "\e[0m\nClosest candidates are:\n readline(::AbstractString)\e[0m", "\nClosest candidates are:\n readline(::AbstractString)") +method_c4() = true +method_c4(x::AbstractString) = false +Base.show_method_candidates(buf, MethodError(method_c4,("",))) +test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c4(::AbstractString)\n method_c4()\e[0m", "\nClosest candidates are:\n method_c4(::AbstractString)\n method_c4()") -method_c4(::Type{Float64}) = true -Base.show_method_candidates(buf, MethodError(method_c4,(Float64,))) -test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c4(::Type{Float64})\e[0m", - "\nClosest candidates are:\n method_c4(::Type{Float64})") +method_c5(::Type{Float64}) = true +Base.show_method_candidates(buf, MethodError(method_c5,(Float64,))) +test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c5(::Type{Float64})\e[0m", + "\nClosest candidates are:\n method_c5(::Type{Float64})") -Base.show_method_candidates(buf, MethodError(method_c4,(Int32,))) -test_have_color(buf, "", "") +Base.show_method_candidates(buf, MethodError(method_c5,(Int32,))) +test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c5(\e[1m\e[31m::Type{Float64}\e[0m)\e[0m", + "\nClosest candidates are:\n method_c5(!Matched::Type{Float64})") type Test_type end test_type = Test_type() @@ -83,6 +86,41 @@ Base.show_method_candidates(buf, MethodError(PR16155,(Int64(3), 2.0, Int64(3)))) test_have_color(buf, "\e[0m\nClosest candidates are:\n PR16155(::Int64, ::Any)\n PR16155(::Any, ::Any)\n PR16155{T}(::Any)\e[0m", "\nClosest candidates are:\n PR16155(::Int64, ::Any)\n PR16155(::Any, ::Any)\n PR16155{T}(::Any)") +method_c6(; x=1) = x +method_c6(a; y=1) = y +m_error = try method_c6(y=1) catch e; e; end +showerror(buf, m_error) +error_out = takebuf_string(buf) +m_error = try method_c6(1, x=1) catch e; e; end +showerror(buf, m_error) +error_out1 = takebuf_string(buf) + +if Base.have_color + @test contains(error_out, "method_c6(; x)\e[1m\e[31m got an unsupported keyword argument \"y\"\e[0m") + @test contains(error_out, "method_c6(\e[1m\e[31m::Any\e[0m; y)") + @test contains(error_out1, "method_c6(::Any; y)\e[1m\e[31m got an unsupported keyword argument \"x\"\e[0m") +else + @test contains(error_out, "method_c6(; x) got an unsupported keyword argument \"y\"") + @test contains(error_out, "method_c6(!Matched::Any; y)") + @test contains(error_out1, "method_c6(::Any; y) got an unsupported keyword argument \"x\"") +end + +method_c7(a, b; kargs...) = a +Base.show_method_candidates(buf, MethodError(method_c7, (1, 1)), [(:x, 1), (:y, 2)]) +test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c7(::Any, ::Any; kargs...)\e[0m", + "\nClosest candidates are:\n method_c7(::Any, ::Any; kargs...)") +method_c8(a, b; y=1, w=1) = a +Base.show_method_candidates(buf, MethodError(method_c8, (1, 1)), [(:x, 1), (:y, 2), (:z, 1), (:w, 1)]) +test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c8(::Any, ::Any; y, w)\e[1m\e[31m got an unsupported keyword argument \"x\", \"z\"\e[0m\e[0m", + "\nClosest candidates are:\n method_c8(::Any, ::Any; y, w) got an unsupported keyword argument \"x\", \"z\"") + +addConstraint_15639(c::Int32) = c +addConstraint_15639(c::Int64; uncset=nothing) = addConstraint_15639(Int32(c), uncset=uncset) + +Base.show_method_candidates(buf, MethodError(addConstraint_15639, (Int32(1),)), [(:uncset, nothing)]) +test_have_color(buf, "\e[0m\nClosest candidates are:\n addConstraint_15639(::Int32)\e[1m\e[31m got an unsupported keyword argument \"uncset\"\e[0m\n addConstraint_15639(\e[1m\e[31m::Int64\e[0m; uncset)\e[0m", + "\nClosest candidates are:\n addConstraint_15639(::Int32) got an unsupported keyword argument \"uncset\"\n addConstraint_15639(!Matched::Int64; uncset)") + macro except_str(expr, err_type) return quote let err = nothing diff --git a/test/show.jl b/test/show.jl index e9e8eccd60fba..0d0dfc61eaf25 100644 --- a/test/show.jl +++ b/test/show.jl @@ -358,10 +358,14 @@ end f5971(x, y...; z=1, w...) = nothing let repr = sprint(io -> show(io,"text/plain", methods(f5971))) - @test contains(repr, "f5971(x, y...; z)") + @test contains(repr, "f5971(x, y...; z, w...)") end let repr = sprint(io -> show(io,"text/html", methods(f5971))) - @test contains(repr, "f5971(x, y...; z)") + @test contains(repr, "f5971(x, y...; z, w...)") +end +f16580(x, y...; z=1, w=y+x, q...) = nothing +let repr = sprint(io -> show(io,"text/html", methods(f16580))) + @test contains(repr, "f16580(x, y...; z, w, q...)") end if isempty(Base.GIT_VERSION_INFO.commit)