From 9496b773a9a5c91cfa3b6937f5e7b2a6e0c0c466 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20H=C3=B8egh?= <dhoegh91@gmail.com>
Date: Mon, 30 May 2016 21:59:24 +0200
Subject: [PATCH] Fix so method errors caused by keywords is displayed as the
 original method in show_method_candidates with a text stating the not
 matching keyword. Fix #15639

---
 base/replutil.jl | 53 +++++++++++++++++++++++++++++++++++++++++-------
 test/replutil.jl | 50 +++++++++++++++++++++++++++++++++++++--------
 2 files changed, 88 insertions(+), 15 deletions(-)

diff --git a/base/replutil.jl b/base/replutil.jl
index 39d6563ccb560b..3eac9d9c0f9d9f 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 = [(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,13 @@ function showerror(io::IO, ex::MethodError)
             print(io, "::$typ")
             i == length(arg_types_param) || print(io, ", ")
         end
+        if 0 < length(kwargs)
+            print(io, "; ")
+            for (i ,(k, v)) in enumerate(kwargs)
+                print(io, k, "=", v)
+                i == length(kwargs) || print(io, ", ")
+            end
+        end
         print(io, ")")
     end
     if ft <: AbstractArray
@@ -189,7 +206,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 +239,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=Tuple{Symbol, 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 +250,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 +279,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 +334,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 +356,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/test/replutil.jl b/test/replutil.jl
index 136c024fcf99e4..05500b7b83e2a2 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,37 @@ 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 unrecognized 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 unrecognized keyword argument \"x\"\e[0m")
+else
+    @test contains(error_out, "method_c6(; x) got an unrecognized keyword argument \"y\"")
+    @test contains(error_out, "method_c6(!Matched::Any; y)")
+    @test contains(error_out1, "method_c6(::Any; y) got an unrecognized 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...)")
+
+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 unrecognized 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 unrecognized keyword argument \"uncset\"\n  addConstraint_15639(!Matched::Int64; uncset)")
+
 macro except_str(expr, err_type)
     return quote
         let err = nothing