Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…Base.jl into LinearAlgebraExt
  • Loading branch information
longemen3000 committed Aug 19, 2024
2 parents b7ee255 + 71fb5a5 commit 6947d65
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 47 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ConstructionBase"
uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
authors = ["Takafumi Arakaki", "Rafael Schouten", "Jan Weidner"]
version = "1.5.5"
version = "1.5.7"

[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand Down
55 changes: 22 additions & 33 deletions src/ConstructionBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ if VERSION >= v"1.7"
end
else
function is_propertynames_overloaded(T::Type)::Bool
T <: NamedTuple && return false
which(propertynames, Tuple{T}).sig !== Tuple{typeof(propertynames), Any}
end

Expand Down Expand Up @@ -95,7 +96,9 @@ end
Tuple(vals)
end
# names are symbols: return namedtuple
@inline tuple_or_ntuple(::Type{Symbol}, names, vals::Tuple) = namedtuple(names, vals...)
@inline tuple_or_ntuple(::Type{Symbol}, names, vals) = NamedTuple{Tuple(names)}(vals)
@inline namedtuple(names, vals...) = NamedTuple{Tuple(names)}((vals...,)) # this seemingly unnecessary method encourages union splitting.
# otherwise: throw an error
tuple_or_ntuple(::Type, names, vals) = error("Only Int and Symbol propertynames are supported")

Expand Down Expand Up @@ -131,37 +134,16 @@ end

setproperties(obj , patch::Tuple ) = setproperties_object(obj , patch )
setproperties(obj , patch::NamedTuple ) = setproperties_object(obj , patch )
setproperties(obj::NamedTuple , patch::Tuple ) = setproperties_namedtuple(obj , patch )
setproperties(obj::NamedTuple , patch::NamedTuple ) = setproperties_namedtuple(obj , patch )
setproperties(obj::Tuple , patch::Tuple ) = setproperties_tuple(obj , patch )
setproperties(obj::Tuple , patch::NamedTuple ) = setproperties_tuple(obj , patch )

setproperties_namedtuple(obj, patch::Tuple{}) = obj
@noinline function setproperties_namedtuple(obj, patch::Tuple)
msg = """
setproperties(obj::NamedTuple, patch::Tuple) only allowed for empty Tuple. Got:
obj = $obj
patch = $patch
"""
throw(ArgumentError(msg))
end
function setproperties_namedtuple(obj, patch)
res = merge(obj, patch)
check_patch_properties_exist(res, obj, obj, patch)
res
@generated function check_patch_fields_exist(obj, patch)
fnames = fieldnames(obj)
pnames = fieldnames(patch)
pnames fnames ? :(nothing) : :(throw(ArgumentError($("Failed to assign fields $pnames to object with fields $fnames."))))
end
function check_patch_properties_exist(
nt_new::NamedTuple{fields}, nt_old::NamedTuple{fields}, obj, patch) where {fields}
nothing
end
@noinline function check_patch_properties_exist(nt_new, nt_old, obj, patch)
O = typeof(obj)
msg = """
Failed to assign properties $(propertynames(patch)) to object with properties $(propertynames(obj)).
"""
throw(ArgumentError(msg))
end
function setproperties_namedtuple(obj::NamedTuple{fields}, patch::NamedTuple{fields}) where {fields}

function setproperties(obj::NamedTuple{fields}, patch::NamedTuple{fields}) where {fields}
patch
end

Expand Down Expand Up @@ -210,13 +192,20 @@ setproperties_object(obj, patch::Tuple{}) = obj
end
setproperties_object(obj, patch::NamedTuple{()}) = obj

function setproperties_object(obj, patch)
@generated function setfields_object(obj, patch::NamedTuple)
args = Expr[]
pnames = fieldnames(patch)
for fname in fieldnames(obj)
source = fname in pnames ? :patch : :obj
push!(args, :(getproperty($source, $(QuoteNode(fname)))))
end
:(constructorof(typeof(obj))($(args...)))
end

function setproperties_object(obj, patch::NamedTuple)
check_properties_are_fields(obj)
nt = getproperties(obj)
nt_new = merge(nt, patch)
check_patch_properties_exist(nt_new, nt, obj, patch)
args = Tuple(nt_new) # old julia inference prefers if we wrap in Tuple
constructorof(typeof(obj))(args...)
check_patch_fields_exist(obj, patch)
setfields_object(obj, patch)
end

include("nonstandard.jl")
Expand Down
2 changes: 2 additions & 0 deletions src/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ struct FunctionConstructor{F} end
_isgensym(s::Symbol) = occursin("#", string(s))

@generated function (fc::FunctionConstructor{F})(args...) where F
isempty(args) && return Expr(:new, F)

T = getfield(parentmodule(F), nameof(F))
# We assume all gensym names are anonymous functions
_isgensym(nameof(F)) || return :($T(args...))
Expand Down
36 changes: 23 additions & 13 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,7 @@ end

@testset "getfields" begin
@test getfields(()) === ()

#on julia 1.10 onwards, Array has fields :ref and :size. the :ref field is a memory field
#with non-constant value (the pointer location in memory). The only constant field in []
#is it's size, (0,)
if !isdefined(Base,:Memory)
@test getfields([]) === NamedTuple()
else
arr = []
@test getfields(arr) === (ref = arr.ref, size = arr.size)
end
@test keys(getfields([])) == fieldnames(typeof([]))
@test getfields(Empty()) === NamedTuple()
@test getfields(NamedTuple()) === NamedTuple()
@test getfields((10,20,30)) === (10,20,30)
Expand Down Expand Up @@ -255,7 +246,7 @@ end
end
end

@testset "Anonymous function constructors" begin
@testset "default function constructors" begin
function multiplyer(a, b)
x -> x * a * b
end
Expand All @@ -268,6 +259,10 @@ end
multbc = @inferred constructorof(typeof(mult23))("b", "c")
@inferred multbc("a")
@test multbc("a") == "abc"

@test (@inferred constructorof(typeof(sin))()) === sin
f = x -> x^2
@test (@inferred constructorof(typeof(f))()) === f
end

struct Adder{V} <: Function
Expand Down Expand Up @@ -434,7 +429,7 @@ function write_output_to_ref!(f, out_ref::Ref, arg_ref1::Ref, arg_ref2::Ref)
out_ref
end
function hot_loop_allocs(f::F, args...) where {F}
# we want to test that f(args...) does not allocate
# we want to test that f(args...) does not allocate
# when used in hot loops
# however a naive @allocated f(args...)
# will not be representative of what happens in an inner loop
Expand Down Expand Up @@ -481,6 +476,21 @@ end
end
end

struct S2
a::Union{Nothing, Int}
b::Union{UInt32, Int32}
end

@testset "no allocs S2" begin
obj = S2(3, UInt32(5))
@test 0 == hot_loop_allocs(constructorof, typeof(obj))
if VERSION < v"1.6"
@test 32 hot_loop_allocs(setproperties, obj, (; a = nothing, b = Int32(6)))
else
@test 0 == hot_loop_allocs(setproperties, obj, (; a = nothing, b = Int32(6)))
end
end

@testset "inference" begin
@testset "Tuple n=$n" for n in [0,1,2,3,4,5,10,20,30,40]
t = funny_numbers(Tuple,n)
Expand Down Expand Up @@ -571,7 +581,7 @@ if isdefined(Base, :get_extension) # some 1.9 version
@test all(ma2 .=== @MMatrix [1 3; 2 4])

sz = SizedArray{Tuple{2,2}}([1 2;3 4])
sz2 = @inferred ConstructionBase.constructorof(typeof(sz))([:a :b; :c :d])
sz2 = @inferred ConstructionBase.constructorof(typeof(sz))([:a :b; :c :d])
@test sz2 == SizedArray{Tuple{2,2}}([:a :b; :c :d])
@test typeof(sz2) <: SizedArray{Tuple{2,2},Symbol,2,2}

Expand Down

0 comments on commit 6947d65

Please sign in to comment.