smarr/SOMns

Initialisation expressions inherited by object literals behave unexpectedly

Opened this issue · 5 comments

Recently I've noticed that a superclass's initialisation expressions can behave unexpectedly when inherited by an object literal. In particular, the problem I've found is when an initialization expression attempts to read the value of an argument; and the problem is that when this expression is executed, the argument is read in the activation enclosing the object literal rather than the activation enclosing the class.

To illustrate the problem, say we have the following program:

class Example usingPlatform: platform = Value (
  | private ClassMirror = platform mirrors ClassMirror. |
)(
  class A with: np = ( | public name = np. | )()

  public getDirectly = (
    ^ A with: 'Alice'
  )

  public getSuper = (
    ^ (ClassMirror reflecting: (A with: 'Bob')) classObject
  )
   
  public main: args = (
    getDirectly name println.
    (objL getSuper ()() name) println.
    ^ 0
  )
)

When main executes, it first invokes getDirectly to obtain an instance of A and then prints the value of its name slot. The program outputs Alice, which is expected. Next, an object literal is declared who inherits from the result of the getSuper method, which first creates an instance of A and then uses a class mirror to return A's class. When printing the inherited name field, the program outputs nil, which is unexpected.

Upon further investigation, I can see that public name = np is executed inside of getSuper rather than inside of the classes primary factory. Consequently, the value was nil because the initialization expression public name = np does not close over the scope in which it is declared. After a brief discussion surrounding object literals, I have confirmed that the expected behaviour for Newspeak would be for the object literal's name to be set to Bob.

In order to achieve the expected behaviour, SOMns should enable one of the following:

  • for initialization expressions to enclose the activation in which they are defined,
  • or for a class to enclose the activation from which it is created

Let's discuss the possible ways to correct this problem and, once we have something in mind, I'd be happy to do the implementation.

smarr commented

Thanks for documenting this here.

or for a class to enclose the activation from which it is created

this doesn't make any sense to me. How can a class enclose its own creation point?
But your other statement is phrased in the same way. Do we have a different understanding of 'enclose'?
A circle encloses its center, no?

I think the initialization expression needs to be enclosed by the correct scope.
This is currently only the case for normal classes, but not for object literals, because the initialization expression is getting the wrong scope.

A circle encloses its center, no?

Sorry, using the word "enclose" was a poor choice. I had used the word "enclose" only to suggest that a class should close over the activation from which it is called. Thinking about it now, it makes more sense for the initialization expressions to close over the activation rather than the class.

for object literals, [...] the initialization expression is getting the wrong scope.

Right. So how could we go about getting the correct scope?

smarr commented

What's wrong with what you prosed on the Newspeak mailing list?

One possible fix might be to change the implementation of the superclass resolution so that it is a closure literal instead of a method on the enclosing object.

This seems sensible to me.

smarr commented

Likely related to this seems to be the following issue:

class CausesNPE usingPlatform: platform = Value (
| private ClassMirror = platform mirrors ClassMirror. |
)(
  public class A = (
  | public name ::= nil.
  |
    myInit.
    ('A initialized with name: ' + name) println.
  ) (
    public myInit = (
    )
  )

  public class B = getSuper ()()

  public test = (
    | baz = 'Bob'. |
    ^ objL A () (
      public myInit = (
        self name: baz.
      )
    )
  )

  public getSuper = (
    ^ (ClassMirror reflecting: test) classObject.
  )

  public main: args = (
    | cls |
    test println.
    test name println.

    cls:: (ClassMirror reflecting: test) classObject.
    cls new println.
    cls new name println.

    '\n\nTest B' println.
    B new println.
    B new name println.
    ^ 0
  )
)

This code causes the following NPE:

java.lang.NullPointerException
        at som.interpreter.nodes.NonLocalVariableNode$NonLocalVariableReadNode.isObject(NonLocalVariableNode.java:79)
        at som.interpreter.nodes.NonLocalVariableNodeFactory$NonLocalVariableReadNodeGen.executeGeneric(NonLocalVariableNodeFactory.java:95)
        at som.interpreter.nodes.MessageSendNode$AbstractMessageSendNode.evaluateArguments(MessageSendNode.java:135)
        at som.interpreter.nodes.MessageSendNode$AbstractMessageSendNode.executeGeneric(MessageSendNode.java:127)
        at som.interpreter.nodes.SequenceNode.executeAllButLast(SequenceNode.java:50)
        at som.interpreter.nodes.SequenceNode.executeGeneric(SequenceNode.java:43)
        at som.interpreter.Invokable.execute(Invokable.java:51)
smarr commented

More test programs that should be related to this issue, but need to be analyzed more (and I don't have complete notes on those anymore):

class Richard usingPlatform: platform = Value (
| private ClassMirror = platform mirrors ClassMirror. |
)(
  class AA new: np = (
    | namePrime = np. |
  ) (
     public class Nested = (| public name = namePrime value. |)()
  )

  public AA: namePrime = (
    ^ (AA new: [namePrime]) Nested new
  )

  public a: x = (
    'Running the method named A' println.
    ^ [ objL ( | public value = x. |)() ] value
  )

  private getClass: obj = (
    ^ (ClassMirror reflecting: obj) classObject
  )

  private getSuper = (
    ^ getClass: (AA: 1)
  )

  public main: args = (
    | b = objL getSuper ()(). anA |
    'basic, get a right' println.

    anA:: a: 1.
    anA println.
    anA value println.
    'done with A' println.

    'have a b' println.
    b value println. (* wtf *)
    b value do: [:e | e println ].


    'more funky stuff:

    ' println.

    getSuper new println.
    getSuper new value println.
    ^ 0
  )
)

A version of CauseNPE slightly simplified, I think:

class Richard3 usingPlatform: platform = Value (
| private ClassMirror = platform mirrors ClassMirror. |
)(
  public class A with: n = (
  | public name = n.
  |
    ('A initialized with n: ' + n) println.
  ) ()

  public class B = getSuper ()()

  public test = (
    | baz = 'Bob'. |
    ^ objL A with: baz () ()
  )

  public getSuper = (
    ^ (ClassMirror reflecting: test) classObject.
  )

  public main: args = (
    | cls |
    test println.
    test name println.

    cls:: (ClassMirror reflecting: test) classObject.
    cls new println.
    cls new name println.

    '\n\nTest B' println.
    B new println.
    B new name println.
    ^ 0
  )
)