Skip to content

Commit

Permalink
WIP: put rest keyword arguments in a dict. fixes #4916
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffBezanson committed May 16, 2017
1 parent a34fd51 commit 4f41043
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 102 deletions.
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
79 changes: 79 additions & 0 deletions base/essentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -369,3 +369,82 @@ 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()

"""
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/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
37 changes: 18 additions & 19 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 @@ -479,15 +477,17 @@
;; 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))))
;; 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 +523,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 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

0 comments on commit 4f41043

Please sign in to comment.