From bb6d58da531baabe34b5ea0dbf35d33ee66e1bb3 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 15 Jul 2017 08:54:44 -0500 Subject: [PATCH] Support renaming of old types --- base/reflection.jl | 7 ++ src/module.c | 10 +++ test/choosetests.jl | 2 +- test/testhelpers/RedefStruct.jl | 31 +++++++ test/type_redefinition.jl | 142 ++++++++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 test/testhelpers/RedefStruct.jl create mode 100644 test/type_redefinition.jl diff --git a/base/reflection.jl b/base/reflection.jl index b8548356362c4..3fb38e52b9717 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -115,6 +115,13 @@ function resolve(g::GlobalRef; force::Bool=false) return g end +function rename_binding(m::Module, oldname::Symbol, newname::Symbol) + T = unwrap_unionall(getfield(m, oldname)) + ccall(:jl_rename_binding, Cvoid, (Any, Any, Any), m, oldname, newname) + T.name.name = newname + T +end + const NamedTuple_typename = NamedTuple.body.body.name function _fieldnames(@nospecialize t) diff --git a/src/module.c b/src/module.c index 33e7afedc1696..027fa301000d9 100644 --- a/src/module.c +++ b/src/module.c @@ -682,6 +682,16 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b) } } +JL_DLLEXPORT void jl_rename_binding(jl_module_t *m, jl_sym_t *oldvar, jl_sym_t *newvar) +{ + jl_binding_t *b = jl_get_binding(m, oldvar); + if (b) { + b->name = newvar; + ptrhash_remove(&m->bindings, oldvar); + ptrhash_put(&m->bindings, newvar, b); + } +} + JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) { if (b->constp && b->value != NULL) { diff --git a/test/choosetests.jl b/test/choosetests.jl index 93378a14840e2..1aaa8ab51dbe1 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -34,7 +34,7 @@ in the `choices` argument: function choosetests(choices = []) testnames = [ "subarray", "core", "compiler", "worlds", - "keywordargs", "numbers", "subtype", + "keywordargs", "numbers", "subtype", "type_redefinition", "char", "strings", "triplequote", "unicode", "intrinsics", "dict", "hashing", "iobuffer", "staged", "offsetarray", "arrayops", "tuple", "reduce", "reducedim", "abstractarray", diff --git a/test/testhelpers/RedefStruct.jl b/test/testhelpers/RedefStruct.jl new file mode 100644 index 0000000000000..2fc90cdf2d1a0 --- /dev/null +++ b/test/testhelpers/RedefStruct.jl @@ -0,0 +1,31 @@ +module RedefStruct + +struct A + x::Int +end + +struct B{T<:AbstractFloat} + y::T +end + +struct C{T,N,A<:AbstractArray{T}} + data::A + + function C{T,N,A}(data::AbstractArray{T}) where {T,N,A} + ndims(data) == N-1 || error("wrong dimensionality") + return new{T,N,A}(data) + end +end +C(data::AbstractArray{T,N}) where {T,N} = C{T,N+1,typeof(data)}(data) + +# Methods that take the type as an argument +fA(a::A) = true +fB(b::B{T}) where T = true +fC(c::C{T,N}) where {T,N} = N + +# Methods that call the constructor +gA(x) = A(x) +gB(::Type{T}) where T = B{float(T)}(-2) +gC(sz) = C(zeros(sz)) + +end diff --git a/test/type_redefinition.jl b/test/type_redefinition.jl new file mode 100644 index 0000000000000..bfcb6d258f440 --- /dev/null +++ b/test/type_redefinition.jl @@ -0,0 +1,142 @@ +using Test + +function invalidate_struct(m::Module, name::Symbol) + # Delete the constructors for the old type, in an attempt to ensure that + # old objects can't be created anymore. + T = getfield(m, name) + for constructor in methods(T) + Base.delete_method(constructor) + end + # Rename the old type (move it "out of the way") + new_name = gensym(name) + return Base.rename_binding(m, name, new_name) +end + +## In the first set of tests, we redefine the types before using them +include("testhelpers/RedefStruct.jl") + +oldtype = invalidate_struct(RedefStruct, :A) +ex = quote + struct A + x::Int8 # change the field type + end +end +Core.eval(RedefStruct, ex) +@test fieldtype(RedefStruct.A, :x) === Int8 +a = RedefStruct.A(-1) +@test a.x === Int8(-1) +@test_throws MethodError RedefStruct.fA(a) # this method was defined for the old A +aa = RedefStruct.gA(5) +@test !isa(aa, oldtype) +@test aa.x === Int8(5) + +oldtype = invalidate_struct(RedefStruct, :B) +ex = quote + struct B{T<:Integer} + z::T + end +end +Core.eval(RedefStruct, ex) +@test fieldtype(RedefStruct.B{Int16}, :z) === Int16 +b = RedefStruct.B{Int16}(-1) +@test b.z === Int16(-1) +@test_throws MethodError RedefStruct.fB(b) +# It's not possible to directly construct the TypeError that gets returned, so let's cheat +typerr = try RedefStruct.gB(Int) catch err err end +@test isa(typerr, TypeError) && typerr.func == :B && typerr.context == "T" && + typerr.expected.name == :T && typerr.expected.ub === Integer && typerr.got === Float64 +Core.eval(RedefStruct, :(gB(::Type{T}) where T = B{unsigned(T)}(2))) +bb = RedefStruct.gB(Int) +@test !isa(bb, oldtype) +@test bb.z === UInt(2) + +oldtype = invalidate_struct(RedefStruct, :C) +ex = quote + struct C{T,N,A<:AbstractArray{T,N}} + data::A + + function C{T,N,A}(data::AbstractArray) where {T,N,A} + return new{T,N,A}(data) + end + end +end +Core.eval(RedefStruct, ex) +c32 = RedefStruct.C{Float32,2,Array{Float32,2}}([0.1 0.2; 0.3 0.4]) +@test c32.data isa Array{Float32,2} +@test_throws MethodError RedefStruct.C([0.1 0.2; 0.3 0.4]) # outer constructor is not yet defined +Core.eval(RedefStruct, :(C(data::AbstractArray{T,N}) where {T,N} = C{T,N,typeof(data)}(data))) +c64 = RedefStruct.C([0.1 0.2; 0.3 0.4]) +@test c64.data isa Array{Float64,2} +@test_throws MethodError RedefStruct.fC(c32) +cc = RedefStruct.gC((3,2)) +@test cc.data == zeros(3, 2) && isa(cc.data, Matrix{Float64}) + + +## Do it again, this time having already used the old methods +include("testhelpers/RedefStruct.jl") + +a = RedefStruct.gA(3) +@test RedefStruct.fA(a) +oldtype = invalidate_struct(RedefStruct, :A) +ex = quote + struct A + x::Int8 # change the field type + end +end +Core.eval(RedefStruct, ex) +@test fieldtype(RedefStruct.A, :x) === Int8 +a = RedefStruct.A(-1) +@test a.x === Int8(-1) +@test_throws MethodError RedefStruct.fA(a) # this method was defined for the old A +aa = RedefStruct.gA(5) +@test !isa(aa, oldtype) +@test aa.x === Int8(5) + +b = RedefStruct.gB(Int) +@test b isa RedefStruct.B{Float64} +@test RedefStruct.fB(b) +oldtype = invalidate_struct(RedefStruct, :B) +ex = quote + struct B{T<:Integer} + z::T + end +end +Core.eval(RedefStruct, ex) +@test fieldtype(RedefStruct.B{Int16}, :z) === Int16 +b = RedefStruct.B{Int16}(-1) +@test b.z === Int16(-1) +@test_throws MethodError RedefStruct.fB(b) +typerr = try RedefStruct.gB(Int) catch err err end +@test_broken isa(typerr, TypeError) && typerr.func == :B && typerr.context == "T" && + typerr.expected.name == :T && typerr.expected.ub === Integer && typerr.got === Float64 +typerr = try RedefStruct.gB(Float32) catch err err end +@test isa(typerr, TypeError) && typerr.func == :B && typerr.context == "T" && + typerr.expected.name == :T && typerr.expected.ub === Integer && typerr.got === Float32 +Core.eval(RedefStruct, :(gB(::Type{T}) where T = B{unsigned(T)}(2))) +bb = RedefStruct.gB(Int) +@test !isa(bb, oldtype) +@test bb.z === UInt(2) + +c = RedefStruct.gC((3,2)) +@test RedefStruct.fC(c) == 3 +@test c.data == zeros(3, 2) +oldtype = invalidate_struct(RedefStruct, :C) +ex = quote + struct C{T,N,A<:AbstractArray{T,N}} + data::A + + function C{T,N,A}(data::AbstractArray) where {T,N,A} + return new{T,N,A}(data) + end + end +end +Core.eval(RedefStruct, ex) +c32 = RedefStruct.C{Float32,2,Array{Float32,2}}([0.1 0.2; 0.3 0.4]) +@test c32.data isa Array{Float32,2} +@test_throws MethodError RedefStruct.C([0.1 0.2; 0.3 0.4]) # outer constructor is not yet defined +Core.eval(RedefStruct, :(C(data::AbstractArray{T,N}) where {T,N} = C{T,N,typeof(data)}(data))) +c64 = RedefStruct.C([0.1 0.2; 0.3 0.4]) +@test c64.data isa Array{Float64,2} +@test_throws MethodError RedefStruct.fC(c32) +cc = RedefStruct.gC((3,2)) +@test cc.data == zeros(3, 2) && isa(cc.data, Matrix{Float64})