Skip to content

Support type renaming #22721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would need a call to unwrap_unionall here.

T
end

const NamedTuple_typename = NamedTuple.body.body.name

function _fieldnames(@nospecialize t)
Expand Down
10 changes: 10 additions & 0 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion test/choosetests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
31 changes: 31 additions & 0 deletions test/testhelpers/RedefStruct.jl
Original file line number Diff line number Diff line change
@@ -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
142 changes: 142 additions & 0 deletions test/type_redefinition.jl
Original file line number Diff line number Diff line change
@@ -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})