From 4d93b6625bddfac1b489b059637df116ba3ac709 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Wed, 17 Jan 2018 21:14:19 -0500 Subject: [PATCH] RFC: Base.propertynames(x), analogous to fieldnames(typeof(x)) (#25311) --- NEWS.md | 3 ++- base/exports.jl | 1 + base/linalg/bunchkaufman.jl | 2 ++ base/linalg/cholesky.jl | 3 +++ base/linalg/hessenberg.jl | 2 ++ base/linalg/lq.jl | 2 ++ base/linalg/lu.jl | 2 ++ base/linalg/qr.jl | 2 ++ base/linalg/schur.jl | 4 ++++ base/linalg/svd.jl | 4 ++++ base/reflection.jl | 17 +++++++++++++++++ base/repl/REPLCompletions.jl | 11 ++++++++++- doc/src/base/base.md | 1 + test/linalg/lu.jl | 7 +++++++ test/reflection.jl | 2 +- 15 files changed, 60 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index ad4482e1fd6e6..70f01d7952774 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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]). diff --git a/base/exports.jl b/base/exports.jl index 0bafae38c9349..f557ca0117a26 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -851,6 +851,7 @@ export fieldname, fieldnames, fieldcount, + # propertynames, isconcrete, oftype, promote, diff --git a/base/linalg/bunchkaufman.jl b/base/linalg/bunchkaufman.jl index fc36a1e888c25..90d7bb49a07d5 100644 --- a/base/linalg/bunchkaufman.jl +++ b/base/linalg/bunchkaufman.jl @@ -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) diff --git a/base/linalg/cholesky.jl b/base/linalg/cholesky.jl index 6ed5cdf6b2fc4..c06cd7031d003 100644 --- a/base/linalg/cholesky.jl +++ b/base/linalg/cholesky.jl @@ -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) @@ -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 diff --git a/base/linalg/hessenberg.jl b/base/linalg/hessenberg.jl index 5b4ea46f5fedc..0d2c0795efd08 100644 --- a/base/linalg/hessenberg.jl +++ b/base/linalg/hessenberg.jl @@ -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 diff --git a/base/linalg/lq.jl b/base/linalg/lq.jl index 77c3d7d11e232..254edae9d6036 100644 --- a/base/linalg/lq.jl +++ b/base/linalg/lq.jl @@ -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] diff --git a/base/linalg/lu.jl b/base/linalg/lu.jl index c38340afe5066..3895ce9489098 100644 --- a/base/linalg/lu.jl +++ b/base/linalg/lu.jl @@ -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) diff --git a/base/linalg/qr.jl b/base/linalg/qr.jl index 376bdefba6940..6c8a138a1c559 100644 --- a/base/linalg/qr.jl +++ b/base/linalg/qr.jl @@ -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 @@ -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 diff --git a/base/linalg/schur.jl b/base/linalg/schur.jl index 479c9639d9e19..1696d22bb72f1 100644 --- a/base/linalg/schur.jl +++ b/base/linalg/schur.jl @@ -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) @@ -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 diff --git a/base/linalg/svd.jl b/base/linalg/svd.jl index ceaab76430cfe..339c7ac03ef61 100644 --- a/base/linalg/svd.jl +++ b/base/linalg/svd.jl @@ -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) @@ -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) diff --git a/base/reflection.jl b/base/reflection.jl index 97e6ba7eca6ff..53409d71f8d4c 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1105,3 +1105,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 diff --git a/base/repl/REPLCompletions.jl b/base/repl/REPLCompletions.jl index 2e487428f12a7..3d152b66e3621 100644 --- a/base/repl/REPLCompletions.jl +++ b/base/repl/REPLCompletions.jl @@ -5,7 +5,7 @@ module REPLCompletions export completions, shell_completions, bslash_completions using Base.Meta -using Base: coalesce +using Base: propertynames, coalesce function completes_global(x, name) return startswith(x, name) && !('#' in x) @@ -41,6 +41,7 @@ function complete_symbol(sym, ffunc) lookup_module = true t = Union{} + val = nothing if coalesce(findlast(occursin(non_identifier_chars), sym), 0) < coalesce(findlast(equalto('.'), sym), 0) # Find module lookup_name, name = rsplit(sym, ".", limit=2) @@ -49,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 @@ -82,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 diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 6d60c33900e94..0dab0a5bbccad 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -317,6 +317,7 @@ Core.nfields Base.fieldnames Base.fieldname Base.fieldcount +Base.propertynames Base.datatype_module Base.datatype_name Base.isconst diff --git a/test/linalg/lu.jl b/test/linalg/lu.jl index e25fa73d4be64..8cc29e43e8fdb 100644 --- a/test/linalg/lu.jl +++ b/test/linalg/lu.jl @@ -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 diff --git a/test/reflection.jl b/test/reflection.jl index 1b159565d5b87..1acd7ef38a1c3 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -339,7 +339,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)