Search code examples
autohotkeyinstantiationinstance-variablesbase-class

Why is my instance variable acting like a class variable in AHK?


Here is the basic structure of what I am trying to do:

OutputDebug % "~~~~~ START ~~~~~"

tempA := new ClassA()
tempB := new ClassB()
tempC := new ClassC()

tempA := 0
tempB := 0
tempC := 0

OutputDebug % "~~~~~ END ~~~~~"

return 

class Base {
    ivar := 0

    __New(inValue) {
        this.ivar := new Debug(inValue)
    }

    __Delete() {
        this.ivar := 0
    }
}


class Debug {
    value := 0

    __New(inValue) {
        this.value := inValue
        OutputDebug % "Create Debug [" this.value "]"
    }

    __Delete() {
        OutputDebug % "Delete Debug [" this.value "]"
    }
}


class ClassA extends Base {
    __New() {
        base.__New("A")
        OutputDebug % "Create ClassA"
    }

    __Delete() {
        base.__Delete()
        OutputDebug % "Delete ClassA"
    }
}


class ClassB extends Base {
    __New() {
        base.__New("B")
        OutputDebug % "Create ClassB"
    }

    __Delete() {
        base.__Delete()
        OutputDebug % "Delete ClassB"
    }
}


class ClassC extends Base {
    __New() {
        base.__New("C")
        OutputDebug % "Create ClassC"
    }

    __Delete() {
        base.__Delete()
        OutputDebug % "Delete ClassC"
    }
}

When this is run I get the following output in the debug log:

Create Debug [A]
Create ClassA
Create Debug [B]
Delete Debug [A]
Create ClassB
Create Debug [C]
Delete Debug [B]
Create ClassC
Delete Debug [C]
Delete ClassA
Delete ClassB
Delete ClassC

However, this is NOT what I would expect... this is the output I am looking for:

Create Debug [A]
Create ClassA
Create Debug [B]
Create ClassB
Create Debug [C]
Create ClassC
Delete Debug [A]
Delete ClassA
Delete Debug [B]
Delete ClassB
Delete Debug [C]
Delete ClassC

I am using Notepad++ and I have installed the DBGp plugin, it works and I can step through the script... the issues seems to be related to when the class 'Base' is instantiated as the base for Class[ABC]. When it hits the 'ivar := 0' line it is essentially deleting the previous object... which would make sense if it was a class variable, but NOT as an instance variable!

What am I fundamentally not understanding about how AHK is doing classes? :(


Solution

  • The solution to this problem was so very simple.

    DO NOT name your classes after keywords! Variables are NOT case-sensitive in AutoHotkey, so 'Base' is the same as 'base'.

    Therefore when I was using 'base' it was treating it as 'Base', which is why my observation of 'ivars' behaving like a class variable was true... because it was being used that way inadvertently!

    If you rename the class 'Base' in the above sample code to 'BaseX' everything works as expected!


    Over on the AHK Forums the creator of AHK also replied to my query there:

    base only has special meaning if followed by a dot . or brackets [], so
    code like obj := base, obj.Method() will not work. Scripts can disable the
    special behaviour of base by assigning it a non-empty value; however, this
    is not recommended. Since the variable base must be empty, performance may
    be reduced if the script omits #NoEnv.
    

    Source: Objects

    This is partly for backward-compatibility with scripts which used "base" as a variable name (before it became a "keyword"), and partly due to the way it is implemented. You may notice that ListVars shows base[0 of 0]: - it is implemented by the default base object, on the condition that the target is an empty variable named base. Consequently, it is possible to do this:

    class Base  {
        M() {
            MsgBox % A_ThisFunc
        }
    }
    class C extends Base {
        M() {
            local base
            MsgBox % A_ThisFunc
            base.M()
        }
    }
    C.M()
    

    You can hook base.Method() (where base is not an object) yourself by adding a __call meta-function to the default base object, but in script you can't get the name of the target variable.