Skip to content

Commit bb6d58d

Browse files
committed
Support renaming of old types
1 parent 89a51fb commit bb6d58d

File tree

5 files changed

+191
-1
lines changed

5 files changed

+191
-1
lines changed

base/reflection.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ function resolve(g::GlobalRef; force::Bool=false)
115115
return g
116116
end
117117

118+
function rename_binding(m::Module, oldname::Symbol, newname::Symbol)
119+
T = unwrap_unionall(getfield(m, oldname))
120+
ccall(:jl_rename_binding, Cvoid, (Any, Any, Any), m, oldname, newname)
121+
T.name.name = newname
122+
T
123+
end
124+
118125
const NamedTuple_typename = NamedTuple.body.body.name
119126

120127
function _fieldnames(@nospecialize t)

src/module.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,16 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b)
682682
}
683683
}
684684

685+
JL_DLLEXPORT void jl_rename_binding(jl_module_t *m, jl_sym_t *oldvar, jl_sym_t *newvar)
686+
{
687+
jl_binding_t *b = jl_get_binding(m, oldvar);
688+
if (b) {
689+
b->name = newvar;
690+
ptrhash_remove(&m->bindings, oldvar);
691+
ptrhash_put(&m->bindings, newvar, b);
692+
}
693+
}
694+
685695
JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs)
686696
{
687697
if (b->constp && b->value != NULL) {

test/choosetests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ in the `choices` argument:
3434
function choosetests(choices = [])
3535
testnames = [
3636
"subarray", "core", "compiler", "worlds",
37-
"keywordargs", "numbers", "subtype",
37+
"keywordargs", "numbers", "subtype", "type_redefinition",
3838
"char", "strings", "triplequote", "unicode", "intrinsics",
3939
"dict", "hashing", "iobuffer", "staged", "offsetarray",
4040
"arrayops", "tuple", "reduce", "reducedim", "abstractarray",

test/testhelpers/RedefStruct.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
module RedefStruct
2+
3+
struct A
4+
x::Int
5+
end
6+
7+
struct B{T<:AbstractFloat}
8+
y::T
9+
end
10+
11+
struct C{T,N,A<:AbstractArray{T}}
12+
data::A
13+
14+
function C{T,N,A}(data::AbstractArray{T}) where {T,N,A}
15+
ndims(data) == N-1 || error("wrong dimensionality")
16+
return new{T,N,A}(data)
17+
end
18+
end
19+
C(data::AbstractArray{T,N}) where {T,N} = C{T,N+1,typeof(data)}(data)
20+
21+
# Methods that take the type as an argument
22+
fA(a::A) = true
23+
fB(b::B{T}) where T = true
24+
fC(c::C{T,N}) where {T,N} = N
25+
26+
# Methods that call the constructor
27+
gA(x) = A(x)
28+
gB(::Type{T}) where T = B{float(T)}(-2)
29+
gC(sz) = C(zeros(sz))
30+
31+
end

test/type_redefinition.jl

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using Test
2+
3+
function invalidate_struct(m::Module, name::Symbol)
4+
# Delete the constructors for the old type, in an attempt to ensure that
5+
# old objects can't be created anymore.
6+
T = getfield(m, name)
7+
for constructor in methods(T)
8+
Base.delete_method(constructor)
9+
end
10+
# Rename the old type (move it "out of the way")
11+
new_name = gensym(name)
12+
return Base.rename_binding(m, name, new_name)
13+
end
14+
15+
## In the first set of tests, we redefine the types before using them
16+
include("testhelpers/RedefStruct.jl")
17+
18+
oldtype = invalidate_struct(RedefStruct, :A)
19+
ex = quote
20+
struct A
21+
x::Int8 # change the field type
22+
end
23+
end
24+
Core.eval(RedefStruct, ex)
25+
@test fieldtype(RedefStruct.A, :x) === Int8
26+
a = RedefStruct.A(-1)
27+
@test a.x === Int8(-1)
28+
@test_throws MethodError RedefStruct.fA(a) # this method was defined for the old A
29+
aa = RedefStruct.gA(5)
30+
@test !isa(aa, oldtype)
31+
@test aa.x === Int8(5)
32+
33+
oldtype = invalidate_struct(RedefStruct, :B)
34+
ex = quote
35+
struct B{T<:Integer}
36+
z::T
37+
end
38+
end
39+
Core.eval(RedefStruct, ex)
40+
@test fieldtype(RedefStruct.B{Int16}, :z) === Int16
41+
b = RedefStruct.B{Int16}(-1)
42+
@test b.z === Int16(-1)
43+
@test_throws MethodError RedefStruct.fB(b)
44+
# It's not possible to directly construct the TypeError that gets returned, so let's cheat
45+
typerr = try RedefStruct.gB(Int) catch err err end
46+
@test isa(typerr, TypeError) && typerr.func == :B && typerr.context == "T" &&
47+
typerr.expected.name == :T && typerr.expected.ub === Integer && typerr.got === Float64
48+
Core.eval(RedefStruct, :(gB(::Type{T}) where T = B{unsigned(T)}(2)))
49+
bb = RedefStruct.gB(Int)
50+
@test !isa(bb, oldtype)
51+
@test bb.z === UInt(2)
52+
53+
oldtype = invalidate_struct(RedefStruct, :C)
54+
ex = quote
55+
struct C{T,N,A<:AbstractArray{T,N}}
56+
data::A
57+
58+
function C{T,N,A}(data::AbstractArray) where {T,N,A}
59+
return new{T,N,A}(data)
60+
end
61+
end
62+
end
63+
Core.eval(RedefStruct, ex)
64+
c32 = RedefStruct.C{Float32,2,Array{Float32,2}}([0.1 0.2; 0.3 0.4])
65+
@test c32.data isa Array{Float32,2}
66+
@test_throws MethodError RedefStruct.C([0.1 0.2; 0.3 0.4]) # outer constructor is not yet defined
67+
Core.eval(RedefStruct, :(C(data::AbstractArray{T,N}) where {T,N} = C{T,N,typeof(data)}(data)))
68+
c64 = RedefStruct.C([0.1 0.2; 0.3 0.4])
69+
@test c64.data isa Array{Float64,2}
70+
@test_throws MethodError RedefStruct.fC(c32)
71+
cc = RedefStruct.gC((3,2))
72+
@test cc.data == zeros(3, 2) && isa(cc.data, Matrix{Float64})
73+
74+
75+
## Do it again, this time having already used the old methods
76+
include("testhelpers/RedefStruct.jl")
77+
78+
a = RedefStruct.gA(3)
79+
@test RedefStruct.fA(a)
80+
oldtype = invalidate_struct(RedefStruct, :A)
81+
ex = quote
82+
struct A
83+
x::Int8 # change the field type
84+
end
85+
end
86+
Core.eval(RedefStruct, ex)
87+
@test fieldtype(RedefStruct.A, :x) === Int8
88+
a = RedefStruct.A(-1)
89+
@test a.x === Int8(-1)
90+
@test_throws MethodError RedefStruct.fA(a) # this method was defined for the old A
91+
aa = RedefStruct.gA(5)
92+
@test !isa(aa, oldtype)
93+
@test aa.x === Int8(5)
94+
95+
b = RedefStruct.gB(Int)
96+
@test b isa RedefStruct.B{Float64}
97+
@test RedefStruct.fB(b)
98+
oldtype = invalidate_struct(RedefStruct, :B)
99+
ex = quote
100+
struct B{T<:Integer}
101+
z::T
102+
end
103+
end
104+
Core.eval(RedefStruct, ex)
105+
@test fieldtype(RedefStruct.B{Int16}, :z) === Int16
106+
b = RedefStruct.B{Int16}(-1)
107+
@test b.z === Int16(-1)
108+
@test_throws MethodError RedefStruct.fB(b)
109+
typerr = try RedefStruct.gB(Int) catch err err end
110+
@test_broken isa(typerr, TypeError) && typerr.func == :B && typerr.context == "T" &&
111+
typerr.expected.name == :T && typerr.expected.ub === Integer && typerr.got === Float64
112+
typerr = try RedefStruct.gB(Float32) catch err err end
113+
@test isa(typerr, TypeError) && typerr.func == :B && typerr.context == "T" &&
114+
typerr.expected.name == :T && typerr.expected.ub === Integer && typerr.got === Float32
115+
Core.eval(RedefStruct, :(gB(::Type{T}) where T = B{unsigned(T)}(2)))
116+
bb = RedefStruct.gB(Int)
117+
@test !isa(bb, oldtype)
118+
@test bb.z === UInt(2)
119+
120+
c = RedefStruct.gC((3,2))
121+
@test RedefStruct.fC(c) == 3
122+
@test c.data == zeros(3, 2)
123+
oldtype = invalidate_struct(RedefStruct, :C)
124+
ex = quote
125+
struct C{T,N,A<:AbstractArray{T,N}}
126+
data::A
127+
128+
function C{T,N,A}(data::AbstractArray) where {T,N,A}
129+
return new{T,N,A}(data)
130+
end
131+
end
132+
end
133+
Core.eval(RedefStruct, ex)
134+
c32 = RedefStruct.C{Float32,2,Array{Float32,2}}([0.1 0.2; 0.3 0.4])
135+
@test c32.data isa Array{Float32,2}
136+
@test_throws MethodError RedefStruct.C([0.1 0.2; 0.3 0.4]) # outer constructor is not yet defined
137+
Core.eval(RedefStruct, :(C(data::AbstractArray{T,N}) where {T,N} = C{T,N,typeof(data)}(data)))
138+
c64 = RedefStruct.C([0.1 0.2; 0.3 0.4])
139+
@test c64.data isa Array{Float64,2}
140+
@test_throws MethodError RedefStruct.fC(c32)
141+
cc = RedefStruct.gC((3,2))
142+
@test cc.data == zeros(3, 2) && isa(cc.data, Matrix{Float64})

0 commit comments

Comments
 (0)