#2383 Dissapearing Private Fields

SlimerDude Sat 29 Nov 2014

If we have a private field in a superclass, then as you would expect, it is listed in Type.fields() when called on a subclass. Example:

class Example {
    private Str? myField
}

class Example2 : Example {
    Void main() {
        echo(typeof.fields)  // --> [Example.myField]
    }
}

But if the subclass has a field of the same name, then it hides the one defined in the parent:

class Example {
    private Str? myField
}

class Example2 : Example {
    private Str? myField

    Void main() {
        echo(typeof.fields)  // --> [Example2.myField]
    }
}

Whereas I would have expected: [Example.myField, Example2.myField]

As I'm not overriding myField, this is a bug right?

I came across this when using IoC and I wanted to inject the same private service (with the same name) into both the super and sub class.

brian Sat 29 Nov 2014

No that isn't a bug, by design private fields are not inherited into subclasses. From docs:

  1. Constructors are never inherited
  2. Private slots are never inherited
  3. Internal slots are inherited only by types within the same pod
  4. All other slots are inherited

SlimerDude Sun 30 Nov 2014

Hi Brian, I think you're missing my point for your answer agrees with what I said! I'll try to be clearer...

class Example {
            private Str? priField1
            private Str? priField2
    virtual public  Str? pubField
}

class Example2 : Example {
            private Str? priField1
   override public  Str? pubField
}

Type.fields() lists all the fields on that Type, both inherited and non-inherited, so Example2#.fields lists:

  • Example2.priField1 (obviously)
  • Example2.pubField because it overrides pubField from Example
  • Example.priField2 because it is on the parent

But I would have also expected Example.priField1 because it is not inherited or overriden in Example2

The private field Example2.priField1 hides Example.priField1 because they happen to have the same name.

Better Example

As far as reflection is concerned, it looks like private fields are inherited in some way. Take these classes:

class Parent {
    private Str field1 := "1"
    private Str field2 := "2"
}

class Child : Parent {
    private Str field1 := "3"
}

An instance of Child should hold the the values 1, 2 and 3 and (despite them being private) I should be able to access them all using reflection. But it appears not to be the case:

impl := Child()
Parent#.field("field1").get(impl)  // --> 3 (!)
Parent#.field("field2").get(impl)  // --> 2
 Child#.field("field1").get(impl)  // --> 3

Looking at fan.sys.JavaType.java and the initSlots() method, it seems that slots are mapped purely based on name, which is where the overriding / hiding behaviour comes from.

And I can confirm it's the same behaviour for methods too.

class Parent {
    private Str method() { "parent" }
}

class Child : Parent {
    private Str method() { "child" }
}

impl := Child()
Parent#.method("method").call(impl)  // --> child
 Child#.method("method").call(impl)  // --> child

brian Sun 30 Nov 2014

What are describing is exactly how it is designed to work. Slots are mapped by name only, so it is impossible to have to have two slots in your slots list with the same name. If anything the argument is whether private slots should show up in the reflection APIs, but in the end the current design was picked for compiler support so that reflection could be used during compile time checks. I played with a couple different designs, and ended up with the current one as the best set of trade-offs.

As a practical matter if what you are trying to do is reflect private slots and/or constructors, then you should really only use the declared fields of each type and walk the type inheritance list.

SlimerDude Sun 14 Dec 2014

Slots are mapped by name only

This is, of course, how the compiler works. But I would have imagined reflection to be a little more selective given that private slots should not be overridden.

reflection (is) used during compile time checks.

Oh, okay. Well, that explains everything then! As we're just talking about private slots, I concede that all this is a grey area anyway.

the argument is whether private slots should show up in the reflection APIs

I would vote for yes! It is really really handy, and if Fantom were to go down this route then selectively returning internal and protected slots would get rather tricky!

use the declared fields of each type and walk the type inheritance list.

Cheers. This is what I am now doing.

SlimerDude Wed 17 Dec 2014

Hi, I don't mean to bleat on, but is it possible to set overridden private fields by reflection? If so, which reflection methods do I need to use? I ask because I'm having trouble:

class Parent {
    private Str? field
    Void printParent() { echo(field) }
}

class Child : Parent {
    private Str? field
    Void printChild() { echo(field) }
}

child := Child()
Child# .fields.find { it.name == "field" }.set(child, "child")
Parent#.fields.find { it.name == "field" }.set(child, "parent")

child.printChild  // --> parent, should be child
child.printParent // --> null,   should be parent

brian Thu 18 Dec 2014

Hey Steve,

I looked into this problem. Everything is correct all the way down the lowest levels of the Java runtime. The problem is that reflectively we call the setter which in Java is treated as an virtual override. That has always been a problem in Java in that we can't use true private fields without a ton of extra boiler plate code for closures, etc. And without using true private methods we have no support for invoking methods non-virtually.

For this specific problem, we can hack it for the common case where there isn't a user defined setter. But there is no suitable solution if those private fields had non-synthetic setters. I pushed a fix for that

SlimerDude Fri 19 Dec 2014

Hi Brian,

Thanks for looking into that, and for giving an explanation as to what's going on.

As all field setters / getters (and methods) map to non-private Java methods (which is the obvious way to go) the behaviour all makes sense.

Login or Signup to reply.