Skip to content

Commit

Permalink
fix #28159, better field reflection functions for NamedTuple (#28200)
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffBezanson authored and ararslan committed Jul 20, 2018
1 parent f5c46c8 commit 383751c
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 14 deletions.
26 changes: 20 additions & 6 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,18 @@ function resolve(g::GlobalRef; force::Bool=false)
return g
end

_fieldnames(@nospecialize t) = isdefined(t, :names) ? t.names : t.name.names
const NamedTuple_typename = NamedTuple.body.body.name

function _fieldnames(@nospecialize t)
if t.name === NamedTuple_typename
if t.parameters[1] isa Tuple
return t.parameters[1]
else
throw(ArgumentError("type does not have definite field names"))
end
end
isdefined(t, :names) ? t.names : t.name.names
end

"""
fieldname(x::DataType, i::Integer)
Expand All @@ -132,6 +143,9 @@ julia> fieldname(Rational, 2)
```
"""
function fieldname(t::DataType, i::Integer)
if t.abstract
throw(ArgumentError("type does not have definite field names"))
end
names = _fieldnames(t)
n_fields = length(names)
field_label = n_fields == 1 ? "field" : "fields"
Expand Down Expand Up @@ -159,7 +173,7 @@ fieldnames(t::DataType) = (fieldcount(t); # error check to make sure type is spe
(_fieldnames(t)...,))
fieldnames(t::UnionAll) = fieldnames(unwrap_unionall(t))
fieldnames(::Core.TypeofBottom) =
error("The empty type does not have field names since it does not have instances.")
throw(ArgumentError("The empty type does not have field names since it does not have instances."))
fieldnames(t::Type{<:Tuple}) = ntuple(identity, fieldcount(t))

"""
Expand Down Expand Up @@ -582,16 +596,16 @@ function fieldcount(@nospecialize t)
if t isa UnionAll || t isa Union
t = argument_datatype(t)
if t === nothing
error("type does not have a definite number of fields")
throw(ArgumentError("type does not have a definite number of fields"))
end
t = t::DataType
elseif t == Union{}
error("The empty type does not have a well-defined number of fields since it does not have instances.")
throw(ArgumentError("The empty type does not have a well-defined number of fields since it does not have instances."))
end
if !(t isa DataType)
throw(TypeError(:fieldcount, "", Type, t))
end
if t.name === NamedTuple.body.body.name
if t.name === NamedTuple_typename
names, types = t.parameters
if names isa Tuple
return length(names)
Expand All @@ -604,7 +618,7 @@ function fieldcount(@nospecialize t)
abstr = t.abstract || (t.name === Tuple.name && isvatuple(t))
end
if abstr
error("type does not have a definite number of fields")
throw(ArgumentError("type does not have a definite number of fields"))
end
return length(t.types)
end
Expand Down
14 changes: 14 additions & 0 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,20 @@ static jl_value_t *get_fieldtype(jl_value_t *t, jl_value_t *f)
int field_index;
if (jl_is_long(f)) {
field_index = jl_unbox_long(f) - 1;
if (st->name == jl_namedtuple_typename) {
jl_value_t *nm = jl_tparam0(st);
if (jl_is_tuple(nm)) {
int nf = jl_nfields(nm);
if (field_index < 0 || field_index >= nf)
jl_bounds_error(t, f);
}
jl_value_t *tt = jl_tparam1(st);
while (jl_is_typevar(tt))
tt = ((jl_tvar_t*)tt)->ub;
if (tt == (jl_value_t*)jl_any_type)
return (jl_value_t*)jl_any_type;
return get_fieldtype(tt, f);
}
int nf = jl_field_count(st);
if (nf > 0 && field_index >= nf-1 && st->name == jl_tuple_typename) {
jl_value_t *ft = jl_field_type(st, nf-1);
Expand Down
4 changes: 2 additions & 2 deletions test/namedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

@test fieldcount(NamedTuple{(:a,:b,:c)}) == 3
@test fieldcount(NamedTuple{<:Any,Tuple{Int,Int}}) == 2
@test_throws ErrorException fieldcount(NamedTuple)
@test_throws ErrorException fieldcount(NamedTuple{<:Any,<:Tuple{Int,Vararg{Int}}})
@test_throws ArgumentError fieldcount(NamedTuple)
@test_throws ArgumentError fieldcount(NamedTuple{<:Any,<:Tuple{Int,Vararg{Int}}})

@test (a=1,).a == 1
@test (a=2,)[1] == 2
Expand Down
35 changes: 29 additions & 6 deletions test/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ tlayout = TLayout(5,7,11)
@test fieldnames(TLayout) == (:x, :y, :z) == Base.propertynames(tlayout)
@test [(fieldoffset(TLayout,i), fieldname(TLayout,i), fieldtype(TLayout,i)) for i = 1:fieldcount(TLayout)] ==
[(0, :x, Int8), (2, :y, Int16), (4, :z, Int32)]
@test fieldnames(Complex) === (:re, :im)
@test_throws BoundsError fieldtype(TLayout, 0)
@test_throws ArgumentError fieldname(TLayout, 0)
@test_throws BoundsError fieldoffset(TLayout, 0)
Expand All @@ -235,10 +236,32 @@ tlayout = TLayout(5,7,11)
@test_throws BoundsError fieldtype(Tuple{Vararg{Int8}}, 0)

@test fieldnames(NTuple{3, Int}) == ntuple(i -> fieldname(NTuple{3, Int}, i), 3) == (1, 2, 3)
@test_throws ErrorException fieldnames(Union{})
@test_throws ArgumentError fieldnames(Union{})
@test_throws BoundsError fieldname(NTuple{3, Int}, 0)
@test_throws BoundsError fieldname(NTuple{3, Int}, 4)

@test fieldnames(NamedTuple{(:z,:a)}) === (:z,:a)
@test fieldname(NamedTuple{(:z,:a)}, 1) === :z
@test fieldname(NamedTuple{(:z,:a)}, 2) === :a
@test_throws ArgumentError fieldname(NamedTuple{(:z,:a)}, 3)
@test_throws ArgumentError fieldnames(NamedTuple)
@test_throws ArgumentError fieldnames(NamedTuple{T,Tuple{Int,Int}} where T)
@test_throws ArgumentError fieldnames(Real)
@test_throws ArgumentError fieldnames(AbstractArray)

@test fieldtype((NamedTuple{T,Tuple{Int,String}} where T), 1) === Int
@test fieldtype((NamedTuple{T,Tuple{Int,String}} where T), 2) === String
@test_throws BoundsError fieldtype((NamedTuple{T,Tuple{Int,String}} where T), 3)

@test fieldtype(NamedTuple, 42) === Any
@test_throws BoundsError fieldtype(NamedTuple, 0)
@test_throws BoundsError fieldtype(NamedTuple, -1)

@test fieldtype(NamedTuple{(:a,:b)}, 1) === Any
@test fieldtype(NamedTuple{(:a,:b)}, 2) === Any
@test fieldtype((NamedTuple{(:a,:b),T} where T<:Tuple{Vararg{Integer}}), 2) === Integer
@test_throws BoundsError fieldtype(NamedTuple{(:a,:b)}, 3)

import Base: datatype_alignment, return_types
@test datatype_alignment(UInt16) == 2
@test datatype_alignment(TLayout) == 4
Expand Down Expand Up @@ -591,16 +614,16 @@ end
@test nfields(()) == 0
@test nfields(nothing) == fieldcount(Nothing) == 0
@test nfields(1) == 0
@test_throws ErrorException fieldcount(Union{})
@test_throws ArgumentError fieldcount(Union{})
@test fieldcount(Tuple{Any,Any,T} where T) == 3
@test fieldcount(Complex) == fieldcount(ComplexF32) == 2
@test fieldcount(Union{ComplexF32,ComplexF64}) == 2
@test fieldcount(Int) == 0
@test_throws(ErrorException("type does not have a definite number of fields"),
@test_throws(ArgumentError("type does not have a definite number of fields"),
fieldcount(Union{Complex,Pair}))
@test_throws ErrorException fieldcount(Real)
@test_throws ErrorException fieldcount(AbstractArray)
@test_throws ErrorException fieldcount(Tuple{Any,Vararg{Any}})
@test_throws ArgumentError fieldcount(Real)
@test_throws ArgumentError fieldcount(AbstractArray)
@test_throws ArgumentError fieldcount(Tuple{Any,Vararg{Any}})

# PR #22979

Expand Down
3 changes: 3 additions & 0 deletions test/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,9 @@ end

# issue #27979 (dislaying arrays of pairs containing arrays as first member)
@test replstr([[1.0]=>1.0]) == "1-element Array{Pair{Array{Float64,1},Float64},1}:\n [1.0] => 1.0"

# issue #28159
@test replstr([(a=1, b=2), (a=3,c=4)]) == "2-element Array{NamedTuple{names,Tuple{$Int,$Int}} where names,1}:\n (a = 1, b = 2)\n (a = 3, c = 4)"
end

@testset "#14684: `display` should print associative types in full" begin
Expand Down

0 comments on commit 383751c

Please sign in to comment.