Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: required keyword arguments #25830

Merged
merged 5 commits into from Feb 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion NEWS.md
Expand Up @@ -44,6 +44,9 @@ New language features
* Values for `Enum`s can now be specified inside of a `begin` block when using the
`@enum` macro ([#25424]).

* Keyword arguments can be required: if a default value is omitted, then an
exception is thrown if the caller does not assign the keyword a value ([#25830]).

Language changes
----------------

Expand Down Expand Up @@ -1260,4 +1263,4 @@ Command-line option changes
[#25622]: https://github.com/JuliaLang/julia/issues/25622
[#25634]: https://github.com/JuliaLang/julia/issues/25634
[#25654]: https://github.com/JuliaLang/julia/issues/25654
[#25655]: https://github.com/JuliaLang/julia/issues/25655
[#25655]: https://github.com/JuliaLang/julia/issues/25655
4 changes: 4 additions & 0 deletions base/boot.jl
Expand Up @@ -139,6 +139,7 @@ export
InterruptException, InexactError, OutOfMemoryError, ReadOnlyMemoryError,
OverflowError, StackOverflowError, SegmentationFault, UndefRefError, UndefVarError,
TypeError, ArgumentError, MethodError, AssertionError, LoadError, InitError,
UndefKeywordError,
# AST representation
Expr, GotoNode, LabelNode, LineNumberNode, QuoteNode,
GlobalRef, NewvarNode, SSAValue, Slot, SlotNumber, TypedSlot,
Expand Down Expand Up @@ -253,6 +254,9 @@ end
struct ArgumentError <: Exception
msg::AbstractString
end
struct UndefKeywordError <: Exception
var::Symbol
end

struct MethodError <: Exception
f
Expand Down
7 changes: 7 additions & 0 deletions base/docs/basedocs.jl
Expand Up @@ -1019,6 +1019,13 @@ A symbol in the current scope is not defined.
"""
UndefVarError

"""
UndefKeywordError(var::Symbol)

The required keyword argument `var` was not assigned in a function call.
"""
UndefKeywordError

"""
OverflowError(msg)

Expand Down
3 changes: 3 additions & 0 deletions base/errorshow.jl
Expand Up @@ -134,6 +134,9 @@ showerror(io::IO, ex::ArgumentError) = print(io, "ArgumentError: $(ex.msg)")
showerror(io::IO, ex::AssertionError) = print(io, "AssertionError: $(ex.msg)")
showerror(io::IO, ex::OverflowError) = print(io, "OverflowError: $(ex.msg)")

showerror(io::IO, ex::UndefKeywordError) =
print(io, "UndefKeywordError: keyword argument $(ex.var) not assigned")

function showerror(io::IO, ex::UndefVarError)
if ex.var in [:UTF16String, :UTF32String, :WString, :utf16, :utf32, :wstring, :RepString]
return showerror(io, ErrorException("""
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/base.md
Expand Up @@ -298,6 +298,7 @@ Base.ParseError
Core.StackOverflowError
Base.SystemError
Core.TypeError
Core.UndefKeywordError
Core.UndefRefError
Core.UndefVarError
Base.InitError
Expand Down
11 changes: 11 additions & 0 deletions doc/src/manual/functions.md
Expand Up @@ -518,6 +518,17 @@ function f(x; y=0, kwargs...)
end
```

If a keyword argument is not assigned a default value in the method definition,
then it is *required*: an [`UndefKeywordError`](@ref) exception will be thrown
if the caller does not assign it a value:
```julia
function f(x; y)
###
end
f(3, y=5) # ok, y is assigned
f(3) # throws UndefKeywordError(:y)
```

Inside `f`, `kwargs` will be a named tuple. Named tuples (as well as dictionaries) can be passed as
keyword arguments using a semicolon in a call, e.g. `f(x, z=1; kwargs...)`.

Expand Down
24 changes: 17 additions & 7 deletions src/julia-syntax.scm
Expand Up @@ -644,12 +644,22 @@
(if (pair? invalid)
(if (and (pair? (car invalid)) (eq? 'parameters (caar invalid)))
(error "more than one semicolon in argument list")
(cond ((symbol? (car invalid))
(error (string "keyword argument \"" (car invalid) "\" needs a default value")))
(else
(error (string "invalid keyword argument syntax \""
(deparse (car invalid))
"\" (expected assignment)"))))))))
(error (string "invalid keyword argument syntax \""
(deparse (car invalid)) "\""))))))

; replace unassigned kw args with assignment to throw() call (forcing the caller to assign the keyword)
(define (throw-unassigned-kw-args argl)
(define (throw-unassigned argname)
`(call (core throw) (call (core UndefKeywordError) (inert ,argname))))
(if (has-parameters? argl)
(cons (cons 'parameters
(map (lambda (x)
(cond ((symbol? x) `(kw ,x ,(throw-unassigned x)))
((decl? x) `(kw ,x ,(throw-unassigned (cadr x))))
(else x)))
(cdar argl)))
(cdr argl))
argl))

;; method-def-expr checks for keyword arguments, and if there are any, calls
;; keywords-method-def-expr to expand the definition into several method
Expand All @@ -658,7 +668,7 @@
;; which handles optional positional arguments by adding the needed small
;; boilerplate definitions.
(define (method-def-expr name sparams argl body rett)
(let ((argl (remove-empty-parameters argl)))
(let ((argl (throw-unassigned-kw-args (remove-empty-parameters argl))))
(if (has-parameters? argl)
;; has keywords
(begin (check-kw-args (cdar argl))
Expand Down
12 changes: 12 additions & 0 deletions test/keywordargs.jl
Expand Up @@ -308,3 +308,15 @@ end
((1, 3, 5, 6, 7),
(a = 2, b = 4, c = 8, d = 9, f = 10))
end

@testset "required keyword arguments" begin
f(x; y, z=3) = x + 2y + 3z
@test f(1, y=2) === 14 === f(10, y=2, z=0)
@test_throws UndefKeywordError f(1)
@test_throws UndefKeywordError f(1, z=2)
g(x; y::Int, z=3) = x + 2y + 3z
@test g(1, y=2) === 14 === g(10, y=2, z=0)
@test_throws TypeError g(1, y=2.3)
@test_throws UndefKeywordError g(1)
@test_throws UndefKeywordError g(1, z=2)
end
4 changes: 0 additions & 4 deletions test/syntax.jl
Expand Up @@ -500,10 +500,6 @@ let m_error, error_out, filename = Base.source_path()
error_out = sprint(showerror, m_error)
@test startswith(error_out, "ArgumentError: invalid type for argument number 1 in method definition for method_c6 at $filename:")

m_error = try @eval method_c6(A; B) = 3; catch e; e; end
error_out = sprint(showerror, m_error)
@test error_out == "syntax: keyword argument \"B\" needs a default value"

# issue #20614
m_error = try @eval foo(types::NTuple{N}, values::Vararg{Any,N}, c) where {N} = nothing; catch e; e; end
error_out = sprint(showerror, m_error)
Expand Down