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

WIP: implement #4916, keyword args as dictionary #21915

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,6 @@ export
# constants
nothing, Main

const AnyVector = Array{Any,1}

abstract type Number end
abstract type Real <: Number end
abstract type AbstractFloat <: Real end
Expand Down
2 changes: 1 addition & 1 deletion base/coreimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ isdefined(Main, :Base) || ((::Type{T})(arg) where {T} = convert(T, arg)::T)
function return_type end

## Load essential files and libraries
include("pair.jl")
include("essentials.jl")
include("ctypes.jl")
include("generator.jl")
Expand All @@ -25,7 +26,6 @@ include("options.jl")
# core operations & types
include("promotion.jl")
include("tuple.jl")
include("pair.jl")
include("traits.jl")
include("range.jl")
include("expr.jl")
Expand Down
76 changes: 0 additions & 76 deletions base/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -595,81 +595,5 @@ function filter!(f, d::Union{ObjectIdDict,Dict})
return d
end

struct ImmutableDict{K,V} <: Associative{K,V}
parent::ImmutableDict{K,V}
key::K
value::V
ImmutableDict{K,V}() where {K,V} = new() # represents an empty dictionary
ImmutableDict{K,V}(key, value) where {K,V} = (empty = new(); new(empty, key, value))
ImmutableDict{K,V}(parent::ImmutableDict, key, value) where {K,V} = new(parent, key, value)
end

"""
ImmutableDict

ImmutableDict is a Dictionary implemented as an immutable linked list,
which is optimal for small dictionaries that are constructed over many individual insertions
Note that it is not possible to remove a value, although it can be partially overridden and hidden
by inserting a new value with the same key

ImmutableDict(KV::Pair)

Create a new entry in the Immutable Dictionary for the key => value pair

- use `(key => value) in dict` to see if this particular combination is in the properties set
- use `get(dict, key, default)` to retrieve the most recent value for a particular key

"""
ImmutableDict
ImmutableDict(KV::Pair{K,V}) where {K,V} = ImmutableDict{K,V}(KV[1], KV[2])
ImmutableDict(t::ImmutableDict{K,V}, KV::Pair) where {K,V} = ImmutableDict{K,V}(t, KV[1], KV[2])

function in(key_value::Pair, dict::ImmutableDict, valcmp=(==))
key, value = key_value
while isdefined(dict, :parent)
if dict.key == key
valcmp(value, dict.value) && return true
end
dict = dict.parent
end
return false
end

function haskey(dict::ImmutableDict, key)
while isdefined(dict, :parent)
dict.key == key && return true
dict = dict.parent
end
return false
end

function getindex(dict::ImmutableDict, key)
while isdefined(dict, :parent)
dict.key == key && return dict.value
dict = dict.parent
end
throw(KeyError(key))
end
function get(dict::ImmutableDict, key, default)
while isdefined(dict, :parent)
dict.key == key && return dict.value
dict = dict.parent
end
return default
end

# this actually defines reverse iteration (e.g. it should not be used for merge/copy/filter type operations)
start(t::ImmutableDict) = t
next(::ImmutableDict{K,V}, t) where {K,V} = (Pair{K,V}(t.key, t.value), t.parent)
done(::ImmutableDict, t) = !isdefined(t, :parent)
length(t::ImmutableDict) = count(x->true, t)
isempty(t::ImmutableDict) = done(t, start(t))
function similar(t::ImmutableDict)
while isdefined(t, :parent)
t = t.parent
end
return t
end

_similar_for{P<:Pair}(c::Dict, ::Type{P}, itr, isz) = similar(c, P)
_similar_for(c::Associative, T, itr, isz) = throw(ArgumentError("for Associatives, similar requires an element type of Pair;\n if calling map, consider a comprehension instead"))
6 changes: 3 additions & 3 deletions base/distributed/messages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ null_id(id) = id == RRID(0, 0)
struct CallMsg{Mode} <: AbstractMsg
f::Function
args::Tuple
kwargs::Array
kwargs::Base.KWDict
end
struct CallWaitMsg <: AbstractMsg
f::Function
args::Tuple
kwargs::Array
kwargs::Base.KWDict
end
struct RemoteDoMsg <: AbstractMsg
f::Function
args::Tuple
kwargs::Array
kwargs::Base.KWDict
end
struct ResultMsg <: AbstractMsg
value::Any
Expand Down
85 changes: 84 additions & 1 deletion base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,9 @@ function vector_any(xs::ANY...)
a
end

function as_kwargs(xs::Union{AbstractArray,Associative})
as_kwargs(xs::Union{AbstractArray,Associative}) = collect_as_kwargs(xs)

function collect_as_kwargs(xs::Union{AbstractArray,Associative})
n = length(xs)
to = Vector{Any}(n*2)
i = 1
Expand Down Expand Up @@ -369,3 +371,84 @@ call obsolete versions of a function `f`.
`f` directly, and the type of the result cannot be inferred by the compiler.)
"""
invokelatest(f, args...) = Core._apply_latest(f, args)

struct ImmutableDict{K,V} <: Associative{K,V}
parent::ImmutableDict{K,V}
key::K
value::V
ImmutableDict{K,V}() where {K,V} = new() # represents an empty dictionary
ImmutableDict{K,V}(key, value) where {K,V} = (empty = new(); new(empty, key, value))
ImmutableDict{K,V}(parent::ImmutableDict, key, value) where {K,V} = new(parent, key, value)
end

const KWDict = ImmutableDict{Symbol,Any}
const EmptyKWDict = KWDict()

as_kwargs(xs::KWDict) = xs

"""
ImmutableDict

ImmutableDict is a Dictionary implemented as an immutable linked list,
which is optimal for small dictionaries that are constructed over many individual insertions
Note that it is not possible to remove a value, although it can be partially overridden and hidden
by inserting a new value with the same key

ImmutableDict(KV::Pair)

Create a new entry in the Immutable Dictionary for the key => value pair

- use `(key => value) in dict` to see if this particular combination is in the properties set
- use `get(dict, key, default)` to retrieve the most recent value for a particular key

"""
ImmutableDict
ImmutableDict(KV::Pair{K,V}) where {K,V} = ImmutableDict{K,V}(KV[1], KV[2])
ImmutableDict(t::ImmutableDict{K,V}, KV::Pair) where {K,V} = ImmutableDict{K,V}(t, KV[1], KV[2])

function in(key_value::Pair, dict::ImmutableDict, valcmp=(==))
key, value = key_value
while isdefined(dict, :parent)
if dict.key == key
valcmp(value, dict.value) && return true
end
dict = dict.parent
end
return false
end

function haskey(dict::ImmutableDict, key)
while isdefined(dict, :parent)
dict.key == key && return true
dict = dict.parent
end
return false
end

function getindex(dict::ImmutableDict, key)
while isdefined(dict, :parent)
dict.key == key && return dict.value
dict = dict.parent
end
throw(KeyError(key))
end
function get(dict::ImmutableDict, key, default)
while isdefined(dict, :parent)
dict.key == key && return dict.value
dict = dict.parent
end
return default
end

# this actually defines reverse iteration (e.g. it should not be used for merge/copy/filter type operations)
start(t::ImmutableDict) = t
next(::ImmutableDict{K,V}, t) where {K,V} = (Pair{K,V}(t.key, t.value), t.parent)
done(::ImmutableDict, t) = !isdefined(t, :parent)
length(t::ImmutableDict) = count(x->true, t)
isempty(t::ImmutableDict) = done(t, start(t))
function similar(t::ImmutableDict)
while isdefined(t, :parent)
t = t.parent
end
return t
end
2 changes: 1 addition & 1 deletion base/methodshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function arg_decl_parts(m::Method)
end

function kwarg_decl(m::Method, kwtype::DataType)
sig = rewrap_unionall(Tuple{kwtype, Core.AnyVector, unwrap_unionall(m.sig).parameters...}, m.sig)
sig = rewrap_unionall(Tuple{kwtype, Any, unwrap_unionall(m.sig).parameters...}, m.sig)
kwli = ccall(:jl_methtable_lookup, Any, (Any, Any, UInt), kwtype.name.mt, sig, typemax(UInt))
if kwli !== nothing
kwli = kwli::Method
Expand Down
2 changes: 1 addition & 1 deletion base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ if false
end

## Load essential files and libraries
include("pair.jl")
include("essentials.jl")
include("ctypes.jl")
include("base.jl")
Expand All @@ -55,7 +56,6 @@ include("options.jl")
# core operations & types
include("promotion.jl")
include("tuple.jl")
include("pair.jl")
include("traits.jl")
include("range.jl")
include("twiceprecision.jl")
Expand Down
47 changes: 27 additions & 20 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,6 @@
(or (number? x) (string? x) (char? x) (and (pair? x) (memq (car x) '(quote inert)))
(eq? x 'true) (eq? x 'false)))

(define empty-vector-any '(call (core AnyVector) 0))

(define (keywords-method-def-expr name sparams argl body isstaged rett)
(let* ((kargl (cdar argl)) ;; keyword expressions (= k v)
(pargl (cdr argl)) ;; positional args
Expand Down Expand Up @@ -443,7 +441,7 @@
;; call mangled(vals..., [rest_kw,] pargs..., [vararg]...)
(return (call ,mangled
,@(if ordered-defaults keynames vals)
,@(if (null? restkw) '() (list empty-vector-any))
,@(if (null? restkw) '() (list '(top EmptyKWDict)))
,@(map arg-name pargl)
,@(if (null? vararg) '()
(list `(... ,(arg-name (car vararg))))))))
Expand Down Expand Up @@ -474,20 +472,29 @@
`((|::|
;; if there are optional positional args, we need to be able to reference the function name
,(if (any kwarg? pargl) (gensy) UNUSED)
(call (core kwftype) ,ftype)) (:: ,kw (core AnyVector)) ,@pargl ,@vararg)
(call (core kwftype) ,ftype)) ,kw ,@pargl ,@vararg)
`(block
;; initialize keyword args to their defaults, or set a flag telling
;; whether this keyword needs to be set.
,@(map (lambda (kwname) `(local ,kwname)) keynames)
,@(map (lambda (name dflt flag)
(if (const-default? dflt)
`(= ,name ,dflt)
`(= ,flag true)))
keynames vals flags)
,@(apply append (map (lambda (name dflt)
(if (const-default? dflt)
`((= ,name ,dflt))
'()))
keynames vals))
,@(map (lambda (flag) `(= ,flag true))
flags)
,@(if (null? restkw) '()
`((= ,rkw ,empty-vector-any)))
`((= ,rkw (top EmptyKWDict))))
,(if (and (not (null? restkw)) (null? keynames))
`(if (call (core isa) ,kw (top KWDict))
(block
(= ,rkw ,kw) ;; reuse kwarg list for delegation if possible
(symbolicgoto do_call)))
`(if (call (core isa) ,kw (top KWDict))
(= ,kw (call (top collect_as_kwargs) ,kw))))
;; for i = 1:(length(kw)>>1)
(for (= ,i (: 1 (call (top >>) (call (top length) ,kw) 1)))
(for (= ,i (: (call (top >>) (call (top length) ,kw) 1) -1 1))
(block
;; ii = i*2 - 1
(= ,ii (call (top -) (call (top *) ,i 2) 1))
Expand Down Expand Up @@ -523,22 +530,21 @@
rval0)))
;; if kw[ii] == 'k; k = kw[ii+1]::Type; end
`(if (comparison ,elt === (quote ,(decl-var k)))
(block
(= ,(decl-var k) ,rval)
,@(if (not (const-default? (cadr kvf)))
`((= ,(caddr kvf) false))
'()))
(if ,(caddr kvf)
(block
(= ,(decl-var k) ,rval)
(= ,(caddr kvf) false)))
,else)))
(if (null? restkw)
;; if no rest kw, give error for unrecognized
`(call (top kwerr) ,kw ,@(map arg-name pargl)
,@(if (null? vararg) '()
(list `(... ,(arg-name (car vararg))))))
;; otherwise add to rest keywords
`(foreigncall 'jl_array_ptr_1d_push (core Void) (call (core svec) Any Any)
,rkw 0 (tuple ,elt
(call (core arrayref) ,kw
(call (top +) ,ii 1))) 0))
`(if (call (top haskey) ,rkw ,elt)
(null)
(= ,rkw (call (top KWDict) ,rkw ,elt (call (core arrayref) ,kw
(call (top +) ,ii 1))))))
(map list vars vals flags))))
;; set keywords that weren't present to their default values
,@(apply append
Expand All @@ -547,6 +553,7 @@
'()
`((if ,flag (= ,name ,dflt)))))
keynames vals flags))
(symboliclabel do_call)
;; finally, call the core function
(return (call ,mangled
,@keynames
Expand Down
16 changes: 15 additions & 1 deletion test/keywordargs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ function test4974(;kwargs...)
end
end

@test test4974(a=1) == (2, [(:a, 1)])
@test test4974(a=1) == (2, Base.KWDict(:a, 1))

# issue #7704, computed keywords
@test kwf1(1; (:tens, 2)) == 21
Expand Down Expand Up @@ -285,3 +285,17 @@ let a = 0
g21518()(; :kw=>1)
@test a == 2
end

# issue #4916 - rest keywords as a dict
let ks(;xs...) = xs
d = ks(a=1, b=2)
@test d[:a] == 1
@test d[:b] == 2
# preserving order
@test collect(ks(a=1, b=2)) == [:a=>1, :b=>2]
@test collect(ks(b=2, a=1)) == [:b=>2, :a=>1]
# deduplication
@test collect(ks(b=2, a=1; :b=>3)) == [:a=>1, :b=>3]
@test isempty(ks())
@test eltype(ks()) <: Pair
end
2 changes: 1 addition & 1 deletion test/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -989,7 +989,7 @@ let f = function (x; kw...)
g = function (x; a = 2)
return (x, a)
end
@test f(1) == (1, Any[])
@test f(1) == (1, Base.KWDict())
@test g(1) == (1, 2)
end

Expand Down