Properties on expressions including call arguments, constructors or array indices

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:

  1. Do my stuff in a Base.fieldnames(::Type{<:Pet}) definition and use Base._fieldnames(Pet) to find the “real” julia fields when needed? So basically, try to get around things.
  2. Write a PR for REPL.REPLCompletions, that calls on Base.propertynames(::DataType) for types instead of Base.fieldnames(::DataType) and a PR for Base which just adds propertynames(x::DataType) = fieldnames(x) to bring it in line with the variable version.
  3. 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 to propertynames(x[1]) instead of fieldnames(typeof(x[1])). Using eval() 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.

The basic issue is the REPL tab completion currently doesn’t want to execute arbitrary user code. So evaluating variables and struct fields is okay, but calling getindex and getproperty may be problematic.

I can understand the hesitation because it seems quite involved. What are the prime reasons for not wanting to execute user code here? Is it performance or are there deeper reasons/consequences?

How would you recommend me to work around this?