From a9b0a3d67b7c6abb3a3072f348780725458ce9c5 Mon Sep 17 00:00:00 2001 From: timkittel <19408882+timkittel@users.noreply.github.com> Date: Wed, 18 Jul 2018 17:24:56 +0200 Subject: [PATCH] required keyword arguments (#586) * required keyword arguments working * using @test_throws and UndeKeywordError now * defining UndefKeywordError only if not yet in Base * corrected UndefKeywordError message * added README entry for required keyword arguments * Update README.md * updated README with link --- README.md | 3 +++ src/compatmacro.jl | 18 ++++++++++++++++++ test/runtests.jl | 18 ++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/README.md b/README.md index 9781a6265..e8bc94440 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ Currently, the `@compat` macro supports the following syntaxes: `foo(::CartesianRange{CartesianIndex{N}})` ([#20974]). Note that `CartesianRange` now has two type parameters, so using them as fields in other `struct`s requires manual intervention. + +* Required keyword arguments ([#25830]). For example, `@compat foo(; x, y)` makes `x` and `y` required keyword arguments: when calling `foo`, an error is thrown if `x` or `y` is not explicitly provided. ## Module Aliases @@ -633,6 +635,7 @@ includes this fix. Find the minimum version from there. [#25780]: https://github.com/JuliaLang/julia/issues/25780 [#25812]: https://github.com/JuliaLang/julia/issues/25812 [#25819]: https://github.com/JuliaLang/julia/issues/25819 +[#25830]: https://github.com/JuliaLang/julia/issues/25830 [#25873]: https://github.com/JuliaLang/julia/issues/25873 [#25896]: https://github.com/JuliaLang/julia/issues/25896 [#25935]: https://github.com/JuliaLang/julia/issues/25935 diff --git a/src/compatmacro.jl b/src/compatmacro.jl index e5a4a8c62..28a57ba9b 100644 --- a/src/compatmacro.jl +++ b/src/compatmacro.jl @@ -25,6 +25,20 @@ else new_style_typealias(ex) = false end +if !isdefined(Base, :UndefKeywordError) + struct UndefKeywordError <: Exception + kw + end + Base.showerror(io::IO, e::UndefKeywordError) = print(io, "UndefKeywordError: keyword argument $(e.kw) not assigned") + export UndefKeywordError +end + +"Convert a functions symbol argument to the corresponding required keyword argument." +function symbol2kw(sym::Symbol) + Expr(:kw, sym, Expr(:call, throw, UndefKeywordError(sym))) +end +symbol2kw(arg) = arg + function _compat(ex::Expr) if ex.head === :call f = ex.args[1] @@ -32,6 +46,10 @@ function _compat(ex::Expr) istopsymbol(withincurly(ex.args[1]), :Base, :Nullable) ex = Expr(:call, f, ex.args[2], Expr(:call, :(Compat._Nullable_field2), ex.args[3])) end + if !isdefined(Base, :UndefKeywordError) && length(ex.args) > 1 && isexpr(ex.args[2], :parameters) + params = ex.args[2] + params.args = map(symbol2kw, params.args) + end elseif ex.head === :curly f = ex.args[1] if VERSION < v"0.6.0-dev.2575" #20414 diff --git a/test/runtests.jl b/test/runtests.jl index 807c297cd..3e15cce1c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1898,6 +1898,24 @@ let @test Compat.split(str, r"\.+:\.+"; limit=3, keepempty=true) == ["a","ba","cba.:.:.dcba.:."] end +let + # test required keyword arguments + @compat func1() = 1 + @test func1() == 1 # using the function works + @compat func2(x) = x + @test func2(3) == 3 # using the function works + @compat func3(;y) = y + @test func3(y=2) == 2 # using the function works + @test_throws UndefKeywordError func3() + @compat func4(x; z) = x*z + @test func4(2,z=3) == 6 # using the function works + @test_throws UndefKeywordError func4(2) + @compat func5(;x=1, y) = x*y + @test func5(y=3) == 3 + @test func5(y=3, x=2) == 6 + @test_throws UndefKeywordError func5(x=2) +end + # 0.7.0-beta.73 let a = rand(5,5) s = mapslices(sort, a, dims=[1])