Skip to content

Commit bd18c6f

Browse files
committed
clarify type reflection functions
- deprecate `fieldnames(v)` to disambiguate whether it operates on types or their instances. - similarly, separate `nfields` for instances and `fieldcount` for types - more errors for abstract types in `sizeof` - allow `sizeof(::Symbol)` - use more abstract type property access in src/ - consolidate make_singleton logic to one place
1 parent ff781e5 commit bd18c6f

29 files changed

+193
-120
lines changed

NEWS.md

+6
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ Deprecated or removed
151151
* `Bidiagonal` constructors now use a `Symbol` (`:U` or `:L`) for the upper/lower
152152
argument, instead of a `Bool` or a `Char` ([#22703]).
153153

154+
* Calling `nfields` on a type to find out how many fields its instances have is deprecated.
155+
Use `fieldcount` instead. Use `nfields` only to get the number of fields in a specific object ([#22350]).
156+
157+
* `fieldnames` now operates only on types. To get the names of fields in an object, use
158+
`fieldnames(typeof(x))` ([#22350]).
159+
154160

155161
Julia v0.6.0 Release Notes
156162
==========================

base/dates/io.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ Parse a time from a time string `t` using a `DateFormat` object `df`.
470470
Time(t::AbstractString, df::DateFormat=ISOTimeFormat) = parse(Time, t, df)
471471

472472
@generated function format(io::IO, dt::TimeType, fmt::DateFormat{<:Any,T}) where T
473-
N = nfields(T)
473+
N = fieldcount(T)
474474
quote
475475
ts = fmt.tokens
476476
loc = fmt.locale

base/deepcopy.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ end
5353

5454
function deepcopy_internal(x::ANY, stackdict::ObjectIdDict)
5555
T = typeof(x)::DataType
56-
nf = nfields(T)
56+
nf = nfields(x)
5757
(isbits(T) || nf == 0) && return x
5858
if haskey(stackdict, x)
5959
return stackdict[x]

base/deprecated.jl

+4
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,10 @@ end
15601560
@deprecate Bidiagonal(dv::AbstractVector, ev::AbstractVector, uplo::Char) Bidiagonal(dv, ev, ifelse(uplo == 'U', :U, :L))
15611561
@deprecate Bidiagonal(A::AbstractMatrix, isupper::Bool) Bidiagonal(A, ifelse(isupper, :U, :L))
15621562

1563+
@deprecate fieldnames(v) fieldnames(typeof(v))
1564+
# nfields(::Type) deprecation in builtins.c: update nfields tfunc in inference.jl when it is removed.
1565+
# also replace `_nfields` with `nfields` in summarysize.c when this is removed.
1566+
15631567
# END 0.7 deprecations
15641568

15651569
# BEGIN 1.0 deprecations

base/distributed/messages.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ let msg_cases = :(assert(false))
9595
for i = length(msgtypes):-1:1
9696
mti = msgtypes[i]
9797
msg_cases = :(if idx == $i
98-
return $(Expr(:call, QuoteNode(mti), fill(:(deserialize(s)), nfields(mti))...))
98+
return $(Expr(:call, QuoteNode(mti), fill(:(deserialize(s)), fieldcount(mti))...))
9999
else
100100
$msg_cases
101101
end)

base/docs/Docs.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ function summarize(io::IO, T::DataType, binding)
388388
" ", T, " <: ", supertype(T)
389389
)
390390
println(io, "```")
391-
if !isempty(fieldnames(T))
391+
if !T.abstract && T.name !== Tuple.name && !isempty(fieldnames(T))
392392
println(io, "**Fields:**")
393393
println(io, "```")
394394
pad = maximum(length(string(f)) for f in fieldnames(T))

base/docs/helpdb/Base.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -1476,9 +1476,9 @@ Seek a stream to its beginning.
14761476
seekstart
14771477

14781478
"""
1479-
nfields(x::DataType) -> Int
1479+
nfields(x) -> Int
14801480
1481-
Get the number of fields of a `DataType`.
1481+
Get the number of fields in the given object.
14821482
"""
14831483
nfields
14841484

base/exports.jl

+1
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,7 @@ export
946946
fieldoffset,
947947
fieldname,
948948
fieldnames,
949+
fieldcount,
949950
isleaftype,
950951
oftype,
951952
promote,

base/inference.jl

+9-7
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ function isdefined_tfunc(args...)
597597
end
598598
if 1 <= idx <= a1.ninitialized
599599
return Const(true)
600-
elseif idx <= 0 || (idx > nfields(a1) && !isvatuple(a1))
600+
elseif idx <= 0 || (!isvatuple(a1) && idx > fieldcount(a1))
601601
return Const(false)
602602
end
603603
end
@@ -617,16 +617,18 @@ add_tfunc(Core.sizeof, 1, 1,
617617
function (x::ANY)
618618
isa(x, Const) && return _const_sizeof(x.val)
619619
isa(x, Conditional) && return _const_sizeof(Bool)
620-
isType(x) && return _const_sizeof(x.parameters[1])
620+
isconstType(x) && return _const_sizeof(x.parameters[1])
621621
x !== DataType && isleaftype(x) && return _const_sizeof(x)
622622
return Int
623623
end, 0)
624+
old_nfields(x::ANY) = length((isa(x,DataType) ? x : typeof(x)).types)
624625
add_tfunc(nfields, 1, 1,
625626
function (x::ANY)
626-
isa(x,Const) && return Const(nfields(x.val))
627-
isa(x,Conditional) && return Const(nfields(Bool))
627+
isa(x,Const) && return Const(old_nfields(x.val))
628+
isa(x,Conditional) && return Const(old_nfields(Bool))
628629
if isType(x)
629-
isleaftype(x.parameters[1]) && return Const(nfields(x.parameters[1]))
630+
# TODO: remove with deprecation in builtins.c for nfields(::Type)
631+
isleaftype(x.parameters[1]) && return Const(old_nfields(x.parameters[1]))
630632
elseif isa(x,DataType) && !x.abstract && !(x.name === Tuple.name && isvatuple(x)) && x !== DataType
631633
return Const(length(x.types))
632634
end
@@ -5593,8 +5595,8 @@ function is_allocation(e::ANY, sv::InferenceState)
55935595
if isa(typ, DataType) && isleaftype(typ)
55945596
nf = length(e.args) - 1
55955597
names = fieldnames(typ)
5596-
@assert(nf <= nfields(typ))
5597-
if nf < nfields(typ)
5598+
@assert(nf <= length(names))
5599+
if nf < length(names)
55985600
# some fields were left undef
55995601
# we could potentially propagate Bottom
56005602
# for pointer fields

base/linalg/factorization.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ end
4949
convert(::Type{Factorization{T}}, F::Factorization{T}) where {T} = F
5050
inv(F::Factorization{T}) where {T} = A_ldiv_B!(F, eye(T, size(F,1)))
5151

52-
Base.hash(F::Factorization, h::UInt) = mapreduce(f -> hash(getfield(F, f)), hash, h, fieldnames(F))
53-
Base.:(==)( F::T, G::T) where {T<:Factorization} = all(f -> getfield(F, f) == getfield(G, f), fieldnames(F))
54-
Base.isequal(F::T, G::T) where {T<:Factorization} = all(f -> isequal(getfield(F, f), getfield(G, f)), fieldnames(F))
52+
Base.hash(F::Factorization, h::UInt) = mapreduce(f -> hash(getfield(F, f)), hash, h, 1:nfields(F))
53+
Base.:(==)( F::T, G::T) where {T<:Factorization} = all(f -> getfield(F, f) == getfield(G, f), 1:nfields(F))
54+
Base.isequal(F::T, G::T) where {T<:Factorization} = all(f -> isequal(getfield(F, f), getfield(G, f)), 1:nfields(F))
5555

5656
# With a real lhs and complex rhs with the same precision, we can reinterpret
5757
# the complex rhs as a real rhs with twice the number of columns

base/options.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ JLOptions() = unsafe_load(cglobal(:jl_options, JLOptions))
4343

4444
function show(io::IO, opt::JLOptions)
4545
print(io, "JLOptions(")
46-
fields = fieldnames(opt)
46+
fields = fieldnames(JLOptions)
4747
nfields = length(fields)
4848
for (i, f) in enumerate(fields)
4949
v = getfield(opt, i)

base/reflection.jl

+31-12
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ julia> fieldname(SparseMatrixCSC, 5)
124124
"""
125125
fieldname(t::DataType, i::Integer) = t.name.names[i]::Symbol
126126
fieldname(t::UnionAll, i::Integer) = fieldname(unwrap_unionall(t), i)
127-
fieldname(t::Type{<:Tuple}, i::Integer) = i < 1 || i > nfields(t) ? throw(BoundsError(t, i)) : Int(i)
127+
fieldname(t::Type{<:Tuple}, i::Integer) =
128+
i < 1 || i > fieldcount(t) ? throw(BoundsError(t, i)) : Int(i)
128129

129130
"""
130131
fieldnames(x::DataType)
@@ -139,16 +140,9 @@ julia> fieldnames(Hermitian)
139140
:uplo
140141
```
141142
"""
142-
function fieldnames(v)
143-
t = typeof(v)
144-
if !isa(t,DataType)
145-
throw(ArgumentError("cannot call fieldnames() on a non-composite type"))
146-
end
147-
return fieldnames(t)
148-
end
149-
fieldnames(t::DataType) = Symbol[fieldname(t, n) for n in 1:nfields(t)]
143+
fieldnames(t::DataType) = Symbol[fieldname(t, n) for n in 1:fieldcount(t)]
150144
fieldnames(t::UnionAll) = fieldnames(unwrap_unionall(t))
151-
fieldnames(t::Type{<:Tuple}) = Int[n for n in 1:nfields(t)]
145+
fieldnames(t::Type{<:Tuple}) = Int[n for n in 1:fieldcount(t)]
152146

153147
"""
154148
Base.datatype_name(t) -> Symbol
@@ -265,7 +259,7 @@ false
265259
```
266260
"""
267261
isimmutable(x::ANY) = (@_pure_meta; (isa(x,Tuple) || !typeof(x).mutable))
268-
isstructtype(t::DataType) = (@_pure_meta; nfields(t) != 0 || (t.size==0 && !t.abstract))
262+
isstructtype(t::DataType) = (@_pure_meta; length(t.types) != 0 || (t.size==0 && !t.abstract))
269263
isstructtype(x) = (@_pure_meta; false)
270264

271265
"""
@@ -371,7 +365,7 @@ The byte offset of field `i` of a type relative to the data start. For example,
371365
use it in the following manner to summarize information about a struct:
372366
373367
```jldoctest
374-
julia> structinfo(T) = [(fieldoffset(T,i), fieldname(T,i), fieldtype(T,i)) for i = 1:nfields(T)];
368+
julia> structinfo(T) = [(fieldoffset(T,i), fieldname(T,i), fieldtype(T,i)) for i = 1:fieldcount(T)];
375369
376370
julia> structinfo(Base.Filesystem.StatStruct)
377371
12-element Array{Tuple{UInt64,Symbol,DataType},1}:
@@ -440,6 +434,31 @@ end
440434

441435
type_alignment(x::DataType) = (@_pure_meta; ccall(:jl_get_alignment, Csize_t, (Any,), x))
442436

437+
"""
438+
fieldcount(t::Type)
439+
440+
Get the number of fields that an instance of the given type would have.
441+
An error is thrown if the type is too abstract to determine this.
442+
"""
443+
function fieldcount(t::ANY)
444+
if t isa UnionAll || t isa Union
445+
t = ccall(:jl_argument_datatype, Any, (Any,), t)
446+
if t === nothing
447+
error("type does not have a definite number of fields")
448+
end
449+
t = t::DataType
450+
elseif t == Union{}
451+
return 0
452+
end
453+
if !(t isa DataType)
454+
throw(TypeError(:fieldcount, "", Type, t))
455+
end
456+
if t.abstract || (t.name === Tuple.name && isvatuple(t))
457+
error("type does not have a definite number of fields")
458+
end
459+
return length(t.types)
460+
end
461+
443462
# return all instances, for types that can be enumerated
444463

445464
"""

base/replutil.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ end
153153

154154
function show(io::IO, ::MIME"text/plain", opt::JLOptions)
155155
println(io, "JLOptions(")
156-
fields = fieldnames(opt)
156+
fields = fieldnames(JLOptions)
157157
nfields = length(fields)
158158
for (i, f) in enumerate(fields)
159159
v = getfield(opt, i)

base/serialize.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ function serialize_any(s::AbstractSerializer, x::ANY)
608608
return write_as_tag(s.io, tag)
609609
end
610610
t = typeof(x)::DataType
611-
nf = nfields(t)
611+
nf = nfields(x)
612612
if nf == 0 && t.size > 0
613613
serialize_type(s, t)
614614
write(s.io, x)
@@ -721,7 +721,7 @@ function handle_deserialize(s::AbstractSerializer, b::Int32)
721721
return deserialize_symbol(s, Int(read(s.io, Int32)::Int32))
722722
end
723723
t = desertag(b)
724-
if t.mutable && nfields(t) > 0
724+
if t.mutable && length(t.types) > 0 # manual specialization of fieldcount
725725
slot = s.counter; s.counter += 1
726726
push!(s.pending_refs, slot)
727727
end
@@ -1050,7 +1050,7 @@ end
10501050

10511051
# default DataType deserializer
10521052
function deserialize(s::AbstractSerializer, t::DataType)
1053-
nf = nfields(t)
1053+
nf = length(t.types)
10541054
if nf == 0 && t.size > 0
10551055
# bits type
10561056
return read(s.io, t)

base/show.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ function show_default(io::IO, x::ANY)
127127
t = typeof(x)::DataType
128128
show(io, t)
129129
print(io, '(')
130-
nf = nfields(t)
130+
nf = nfields(x)
131131
nb = sizeof(x)
132132
if nf != 0 || nb == 0
133133
if !show_circular(io, x)
@@ -1186,7 +1186,7 @@ function dump(io::IO, x::ANY, n::Int, indent)
11861186
else
11871187
print(io, T)
11881188
end
1189-
if nfields(T) > 0
1189+
if nfields(x) > 0
11901190
if n > 0
11911191
for field in (isa(x,Tuple) ? (1:length(x)) : fieldnames(T))
11921192
println(io)
@@ -1250,7 +1250,7 @@ function dump(io::IO, x::DataType, n::Int, indent)
12501250
if x !== Any
12511251
print(io, " <: ", supertype(x))
12521252
end
1253-
if n > 0 && !(x <: Tuple)
1253+
if n > 0 && !(x <: Tuple) && !x.abstract
12541254
tvar_io::IOContext = io
12551255
for tparam in x.parameters
12561256
# approximately recapture the list of tvar parameterization

base/summarysize.jl

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ struct SummarySize
88
chargeall::Any
99
end
1010

11+
_nfields(x::ANY) = length(typeof(x).types)
1112

1213
"""
1314
Base.summarysize(obj; exclude=Union{...}, chargeall=Union{...}) -> Int
@@ -41,7 +42,7 @@ function summarysize(obj::ANY;
4142
val = x[i]
4243
end
4344
else
44-
nf = nfields(x)
45+
nf = _nfields(x)
4546
ft = typeof(x).types
4647
if !isbits(ft[i]) && isdefined(x, i)
4748
val = getfield(x, i)
@@ -65,11 +66,11 @@ end
6566
@noinline function _summarysize(ss::SummarySize, obj::ANY)
6667
key = pointer_from_objref(obj)
6768
haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true)
68-
if nfields(obj) > 0
69+
if _nfields(obj) > 0
6970
push!(ss.frontier_x, obj)
7071
push!(ss.frontier_i, 1)
7172
end
72-
if isa(obj, UnionAll)
73+
if isa(obj, UnionAll) || isa(obj, Union)
7374
# black-list of items that don't have a Core.sizeof
7475
return 2 * sizeof(Int)
7576
end
@@ -84,7 +85,7 @@ function (ss::SummarySize)(obj::DataType)
8485
key = pointer_from_objref(obj)
8586
haskey(ss.seen, key) ? (return 0) : (ss.seen[key] = true)
8687
size::Int = 7 * Core.sizeof(Int) + 6 * Core.sizeof(Int32)
87-
size += 4 * nfields(obj) + ifelse(Sys.WORD_SIZE == 64, 4, 0)
88+
size += 4 * _nfields(obj) + ifelse(Sys.WORD_SIZE == 64, 4, 0)
8889
size += ss(obj.parameters)::Int
8990
size += ss(obj.types)::Int
9091
return size

doc/src/manual/types.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ Stacktrace:
352352
You may find a list of field names using the `fieldnames` function.
353353

354354
```jldoctest footype
355-
julia> fieldnames(foo)
355+
julia> fieldnames(Foo)
356356
3-element Array{Symbol,1}:
357357
:bar
358358
:baz

doc/src/stdlib/base.md

+1
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ Base.names
257257
Core.nfields
258258
Base.fieldnames
259259
Base.fieldname
260+
Base.fieldcount
260261
Base.datatype_module
261262
Base.datatype_name
262263
Base.isconst

0 commit comments

Comments
 (0)