Skip to content
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

RFC: Base.propertynames(x), analogous to fieldnames(typeof(x)) #25311

Merged
merged 10 commits into from
Jan 18, 2018
3 changes: 2 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ New language features
and implements three-valued logic, similar to SQLs `NULL` and R's `NA`.

* Field access via dot-syntax can now be overloaded by adding methods to
`Base.getproperty` and `Base.setproperty!` ([#1974]).
`Base.getproperty` and `Base.setproperty!` ([#1974]), optionally along with
a corresponding `Base.propertynames` method for reflection ([#25311]).

* Values for `Enum`s can now be specified inside of a `begin` block when using the
`@enum` macro ([#25424]).
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,7 @@ export
fieldname,
fieldnames,
fieldcount,
# propertynames,
isconcrete,
oftype,
promote,
Expand Down
2 changes: 2 additions & 0 deletions base/linalg/bunchkaufman.jl
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ function getproperty(B::BunchKaufman{T}, d::Symbol) where {T<:BlasFloat}
end
end

Base.propertynames(B::BunchKaufman, private::Bool=false) = append!([:p,:P,:L,:U,:D], private ? fieldnames(typeof(B)) : Symbol[])

issuccess(B::BunchKaufman) = B.info == 0

function Base.show(io::IO, mime::MIME{Symbol("text/plain")}, B::BunchKaufman)
Expand Down
3 changes: 3 additions & 0 deletions base/linalg/cholesky.jl
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,8 @@ function getproperty(C::Cholesky, d::Symbol)
return getfield(C, d)
end
end
Base.propertynames(F::Cholesky, private::Bool=false) = append!([:U,:L,:UL], private ? fieldnames(typeof(F)) : Symbol[])

function getproperty(C::CholeskyPivoted{T}, d::Symbol) where T<:BlasFloat
Cfactors = getfield(C, :factors)
Cuplo = getfield(C, :uplo)
Expand All @@ -413,6 +415,7 @@ function getproperty(C::CholeskyPivoted{T}, d::Symbol) where T<:BlasFloat
return getfield(C, d)
end
end
Base.propertynames(F::CholeskyPivoted, private::Bool=false) = append!([:U,:L,:p,:P], private ? fieldnames(typeof(F)) : Symbol[])

issuccess(C::Cholesky) = C.info == 0

Expand Down
2 changes: 2 additions & 0 deletions base/linalg/hessenberg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ function getproperty(F::Hessenberg, d::Symbol)
return getfield(F, d)
end

Base.propertynames(F::Hessenberg, private::Bool=false) = append!([:Q,:H], private ? fieldnames(typeof(F)) : Symbol[])

function getindex(A::HessenbergQ, i::Integer, j::Integer)
x = zeros(eltype(A), size(A, 1))
x[i] = 1
Expand Down
2 changes: 2 additions & 0 deletions base/linalg/lq.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ function getproperty(F::LQ, d::Symbol)
end
end

Base.propertynames(F::LQ, private::Bool=false) = append!([:L,:Q], private ? fieldnames(typeof(F)) : Symbol[])

getindex(A::LQPackedQ, i::Integer, j::Integer) =
mul!(A, setindex!(zeros(eltype(A), size(A, 2)), 1, j))[i]

Expand Down
2 changes: 2 additions & 0 deletions base/linalg/lu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ function getproperty(F::LU{T,<:StridedMatrix}, d::Symbol) where T
end
end

Base.propertynames(F::LU, private::Bool=false) = append!([:L,:U,:p,:P], private ? fieldnames(typeof(F)) : Symbol[])

issuccess(F::LU) = F.info == 0

function show(io::IO, mime::MIME{Symbol("text/plain")}, F::LU)
Expand Down
2 changes: 2 additions & 0 deletions base/linalg/qr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ function getproperty(F::QRCompactWY, d::Symbol)
getfield(F, d)
end
end
Base.propertynames(F::Union{QR,QRCompactWY}, private::Bool=false) = append!([:R,:Q], private ? fieldnames(typeof(F)) : Symbol[])
function getproperty(F::QRPivoted{T}, d::Symbol) where T
m, n = size(F)
if d == :R
Expand All @@ -469,6 +470,7 @@ function getproperty(F::QRPivoted{T}, d::Symbol) where T
getfield(F, d)
end
end
Base.propertynames(F::QRPivoted, private::Bool=false) = append!([:R,:Q,:p,:P], private ? fieldnames(typeof(F)) : Symbol[])

abstract type AbstractQ{T} <: AbstractMatrix{T} end

Expand Down
4 changes: 4 additions & 0 deletions base/linalg/schur.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ function getproperty(F::Schur, d::Symbol)
end
end

Base.propertynames(F::Schur) = append!([:Schur,:vectors], fieldnames(typeof(F)))

function show(io::IO, F::Schur)
println(io, "$(typeof(F)) with factors T and Z:")
show(io, F.T)
Expand Down Expand Up @@ -274,6 +276,8 @@ function getproperty(F::GeneralizedSchur, d::Symbol)
end
end

Base.propertynames(F::GeneralizedSchur) = append!([:values,:left,:right], fieldnames(typeof(F)))

"""
schur(A::StridedMatrix, B::StridedMatrix) -> S::StridedMatrix, T::StridedMatrix, Q::StridedMatrix, Z::StridedMatrix, α::Vector, β::Vector

Expand Down
4 changes: 4 additions & 0 deletions base/linalg/svd.jl
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ function getproperty(F::SVD, d::Symbol)
end
end

Base.propertynames(F::SVD, private::Bool=false) = private ? append!([:V], fieldnames(typeof(F))) : [:U,:S,:V,:Vt]

"""
svdvals!(A)

Expand Down Expand Up @@ -461,6 +463,8 @@ svd(x::Number, y::Number) = first.(svd(fill(x, 1, 1), fill(y, 1, 1)))
end
end

Base.propertynames(F::GeneralizedSVD) = append!([:alpha,:beta,:vals,:S,:D1,:D2,:R0], fieldnames(typeof(F)))

"""
svdvals!(A, B)

Expand Down
17 changes: 17 additions & 0 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1119,3 +1119,20 @@ min_world(m::Method) = reinterpret(UInt, m.min_world)
max_world(m::Method) = typemax(UInt)
min_world(m::Core.MethodInstance) = reinterpret(UInt, m.min_world)
max_world(m::Core.MethodInstance) = reinterpret(UInt, m.max_world)

"""
propertynames(x, private=false)

Get an array of the properties (`x.property`) of an object `x`. This
is typically the same as [`fieldnames(typeof(x))`](@ref), but types
that overload [`getproperty`](@ref) should generally overload `propertynames`
as well to get the properties of an instance of the type.

`propertynames(x)` may return only "public" property names that are part
of the documented interface of `x`. If you want it to also return "private"
fieldnames intended for internal use, pass `true` for the optional second argument.
REPL tab completion on `x.` shows only the `private=false` properties.
"""
propertynames(x) = fieldnames(typeof(x))
propertynames(m::Module) = names(m)
propertynames(x, private) = propertynames(x) # ignore private flag by default
10 changes: 10 additions & 0 deletions base/repl/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module REPLCompletions
export completions, shell_completions, bslash_completions

using Base.Meta
using Base: propertynames

function completes_global(x, name)
return startswith(x, name) && !('#' in x)
Expand Down Expand Up @@ -40,6 +41,7 @@ function complete_symbol(sym, ffunc)

lookup_module = true
t = Union{}
val = nothing
if findlast(occursin(non_identifier_chars), sym) < findlast(equalto('.'), sym)
# Find module
lookup_name, name = rsplit(sym, ".", limit=2)
Expand All @@ -48,6 +50,7 @@ function complete_symbol(sym, ffunc)

b, found = get_value(ex, context_module)
if found
val = b
if isa(b, Module)
mod = b
lookup_module = true
Expand Down Expand Up @@ -81,6 +84,13 @@ function complete_symbol(sym, ffunc)
else
append!(suggestions, filtered_mod_names(p, mod, name, true, false))
end
elseif val !== nothing # looking for a property of an instance
for property in propertynames(val, false)
s = string(property)
if startswith(s, name)
push!(suggestions, s)
end
end
else
# Looking for a member of a type
if t isa DataType && t != Any
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ Core.nfields
Base.fieldnames
Base.fieldname
Base.fieldcount
Base.propertynames
Base.datatype_module
Base.datatype_name
Base.isconst
Expand Down
7 changes: 7 additions & 0 deletions test/linalg/lu.jl
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,10 @@ U factor:
0.0 0.0 1.0 0.0
0.0 0.0 0.0 1.0"""
end

@testset "propertynames" begin
names = sort!(string.(Base.propertynames(lufact(rand(3,3)))))
@test names == ["L", "P", "U", "p"]
allnames = sort!(string.(Base.propertynames(lufact(rand(3,3)), true)))
@test allnames == ["L", "P", "U", "factors", "info", "ipiv", "p"]
end
2 changes: 1 addition & 1 deletion test/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ mutable struct TLayout
z::Int32
end
tlayout = TLayout(5,7,11)
@test fieldnames(TLayout) == [:x, :y, :z]
@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_throws BoundsError fieldtype(TLayout, 0)
Expand Down