diff --git a/base/namedtuple.jl b/base/namedtuple.jl index 46927b51d89d4..2f9bc8cb39f86 100644 --- a/base/namedtuple.jl +++ b/base/namedtuple.jl @@ -213,16 +213,25 @@ end end """ - merge(a::NamedTuple, b::NamedTuple) + merge(a::NamedTuple, bs::NamedTuple...) -Construct a new named tuple by merging two existing ones. -The order of fields in `a` is preserved, but values are taken from matching -fields in `b`. Fields present only in `b` are appended at the end. +Construct a new named tuple by merging two or more existing ones, in a left-associative +manner. Merging proceeds left-to-right, between pairs of named tuples, and so the order of fields +present in both the leftmost and rightmost named tuples take the same position as they are found in the +leftmost named tuple. However, values are taken from matching fields in the rightmost named tuple that +contains that field. Fields present in only the rightmost named tuple of a pair are appended at the end. +A fallback is implemented for when only a single named tuple is supplied, +with signature `merge(a::NamedTuple)`. ```jldoctest julia> merge((a=1, b=2, c=3), (b=4, d=5)) (a = 1, b = 4, c = 3, d = 5) ``` + +```jldoctest +julia> merge((a=1, b=2), (b=3, c=(d=1,)), (c=(d=2,),)) +(a = 1, b = 3, c = (d = 2,)) +``` """ function merge(a::NamedTuple{an}, b::NamedTuple{bn}) where {an, bn} if @generated @@ -241,6 +250,10 @@ merge(a::NamedTuple{()}, b::NamedTuple) = b merge(a::NamedTuple, b::Iterators.Pairs{<:Any,<:Any,<:Any,<:NamedTuple}) = merge(a, b.data) +merge(a::NamedTuple, b::NamedTuple, cs::NamedTuple...) = merge(merge(a, b), cs...) + +merge(a::NamedTuple) = a + """ merge(a::NamedTuple, iterable) diff --git a/test/namedtuple.jl b/test/namedtuple.jl index 18395fe2d9508..b88e7d5663e3e 100644 --- a/test/namedtuple.jl +++ b/test/namedtuple.jl @@ -240,3 +240,8 @@ y = map(v -> (a=v.a, b=v.a + v.b), [(a=1, b=missing), (a=1, b=2)]) # Iterator constructor @test NamedTuple{(:a, :b), Tuple{Int, Float64}}(Any[1.0, 2]) === (a=1, b=2.0) @test NamedTuple{(:a, :b)}(Any[1.0, 2]) === (a=1.0, b=2) + +# Left-associative merge, issue #29215 +@test merge((a=1, b=2), (b=3, c=4), (c=5,)) === (a=1, b=3, c=5) +@test merge((a=1, b=2), (b=3, c=(d=1,)), (c=(d=2,),)) === (a=1, b=3, c=(d=2,)) +@test merge((a=1, b=2)) === (a=1, b=2)