Hi there, I am new to the Julia community, but since a while I’ve been working on a package to enable painless interaction with COMSOL (commercial finite element software). It relies heavily on java’s reflection API to minimize boilerplate through getproperty()
, setproperty()
, etc. Off-topic, but wanted to say and compliment that I am amazed how much you can do with just a few lines of generic julia code.
Currently I am trying to get TAB completion to work to traverse the rather deep COMSOL data structures, but I run into the fact that propertynames()
only gets called on variables and not on expressions including argument calls, constructors or array indices. In the latter cases, fieldnames()
is run on the type of the expression.
This makes the functionallity in the use-case I imagine, less powerful. On the other hand, getproperty()
does get hit as I expected, so the written code works fine, it is just the tab-completion to help write that code which is off.
This code shows the problem:
using REPL
struct Pet
name
end
Base.propertynames(x::Pet) = (:surprise, fieldnames(typeof(x))...)
Base.getproperty(x::Pet, s::Symbol) = s == :surprise ? "You got it." : getfield(x, s)
# Use-cases
propertynames(Pet("Fluffy")) # returns (:surprise, :name)
mypet = Pet("Fluffy") # tab-completion on "mypet.", works as I hoped
Pet("Fluffy") # tab-completion on "Pet("Fluffy")." only returns .name (does not even call propertynames)
kennel = [mypet] # tab-completion on "kennel[1]." only returns .name (does not even call propertynames)
mypet.surprise # returns "You got it.", works as I hoped
Pet("Fluffy").surprise # returns "You got it.", works as I hoped
kennel[1].surprise # returns "You got it.", works as I hoped
# This helps to dive in deeper:
REPL.REPLCompletions.completions("mypet.",6) # returns tuple including PropertyCompletion-s for :surprise and :name
REPL.REPLCompletions.completions("Pet(\"Fluffy\").",14) # returns tuple including a FieldCompletion for :name
REPL.REPLCompletions.completions("kennel[1].",10) # returns tuple including a FieldCompletion for :name
What is the proper way to get around this and bring the tab-completions in line with the completed expressions?
The ideas I am currently playing with are:
- Do my stuff in a
Base.fieldnames(::Type{<:Pet})
definition and useBase._fieldnames(Pet)
to find the “real” julia fields when needed? So basically, try to get around things. - Write a PR for
REPL.REPLCompletions
, that calls onBase.propertynames(::DataType)
for types instead ofBase.fieldnames(::DataType)
and a PR for Base which just addspropertynames(x::DataType) = fieldnames(x)
to bring it in line with the variable version. - Implement (and PR) some more advanced handling code in
REPL.REPLCompletions
, for instance to handle an element from a container, which seems quite natural extension to me that should lead topropertynames(x[1])
instead offieldnames(typeof(x[1]))
. Usingeval()
it can even be made very general, but I can imagine some boundaries such as on evaluation time need to be imposed.
Looking at the implementation in Base and REPL, it looks like it was done as it is on purpose. But I do feel a bit of a mismatch between handling of variables and types through propertynames()
and fieldnames()
. I can imagine that this is done by design, so an explanation on why it needs to be as it is now, would also be very welcome.
P.S. I do realize that these tab-completions and use of data structures retrieved through an OOP-reflection can lead to OOP-style expressions that are non-idiomatic for julia such as: Pet("Fluffy").surprise
and much worse if multiple levels pile up.
I still want/need to experiment with these a bit, and see what would be a comfortable solution.