Get an error when calling a method that does not exist?

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
jhertel
Posts: 18
Joined: 13 Feb 2015, 08:24

Get an error when calling a method that does not exist?

04 Mar 2015, 08:08

Hi,

I am trying to figure out how to make AutoHotkey report an error if I am calling a non-existing method on an object.

Here is an example:

Code: Select all

#Warn All ; Included to show that this flag does not help.

class Test
{
}

a:= new Test()
a.Hello()  ; Does nothing and does not report an error.
Hello()  ; Reports an error "Call to nonexistent function".
I would really like a.Hello() to report an error too, something like "Call to nonexistent method".

Is there a way to do that? Is there a special flag similar to the #Warn flag?
Coco-guest

Re: Get an error when calling a method that does not exist?

04 Mar 2015, 08:41

Code: Select all

class Test
{
	__Call(method, args*)
	{
		if !ObjHasKey(this.base, method)
			throw Exception("Non-existent method", -1, method)
	}

	Foo()
	{
		MsgBox Foo
	}
}

t := new Test
t.Foo()
t.Bar()
return
Coco-guest

Re: Get an error when calling a method that does not exist?

04 Mar 2015, 09:14

If you're planning to use it again for other classes, you can use the following function:

Code: Select all

Assert_IsMethod(obj, method, lvl:=-1)
{
	if !ObjHaskey(obj, method) || !IsFunc(obj[method])
		throw Exception("Call to nonexistent method", lvl, method)
	return true
}
Usage:

Code: Select all

class Test
{
	__Call(method, args*)
	{
		if (method = "Hello") ; dynamic method
			MsgBox Hello
		else ; put it last if you're planning to implement some dynamic method(s)
			Assert_IsMethod(this.base, method, -2)
	}

	Foo()
	{
		MsgBox Foo
	}

	Bar { ; this is a Property, so a 'call' such as obj.Bar() will still throw an error
		get {
			return "Bar"
		}
	}
}

t := new Test
t.Foo()
t.Hello()
MsgBox % t.Bar
t.Bar()
return

Assert_IsMethod(obj, method, lvl:=-1)
{
	if !ObjHaskey(obj, method) || !IsFunc(obj[method])
		throw Exception("Call to nonexistent method", lvl, method)
	return true
}
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Get an error when calling a method that does not exist?

04 Mar 2015, 20:22

This is simpler and also works for static methods:

Code: Select all

class ThrowsNonexistentMethod {
	__Call(method:="") {
		throw Exception("Non-existent method", -1, method)
	}
	; Must not define any methods in this class or any super-class.
}
class Test extends ThrowsNonexistentMethod {
	ExistentMethod() {
		MsgBox Hello.
	}
}

Test.ExistentMethod()
Test.NonexistentMethod()
Btw, this is the default behaviour in v2.

Coco's method won't work if methods are defined in this.base.base or this.base.base.base, etc.
jhertel
Posts: 18
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

04 Mar 2015, 23:10

Thanks a lot both of you!

Your solution, Lexikos, is one of those simple and beautiful solutions that just make me go "well, why didn't I think of that?". :-) I guess that solution shows the hallmark of a master - coming up with those simple but ingenious solutions. What a joy! And good to hear by the way that that behavior is/will be standard in version 2.

I also want to thank you, Coco, for taking the time to make the solution you came up with. Even though Lexikos' solution was simpler and more complete, I really appreciate your helpfulness. Thank you! :-)
jhertel
Posts: 18
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 13:12

Hm, Lexikos' solution seems to kill the builtin Object() methods of classes inheriting from ThrowsNonexistentMethod. This includes the method _NewEnum(), which means that you cannot iterate over the object any more with a for loop:

for key, value in object


This 'for' statement triggers the __Call() method, giving the error:

"Non-existent method, specifically _NewEnum"

How can I make __Call() not override the default methods, or at least pass any calls to the default methods along to the actual default methods?

Or is there a version of __Call() that is only called if a method with a given name does not exist at all in the given object?
jhertel
Posts: 18
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 13:37

I think I might have solved it with this:

Code: Select all

class ThrowsNonexistentMethod {

	__Call(methodName, arguments*)
	{
		; Allow builtin methods.
		if methodName in Insert,Remove,MinIndex,MaxIndex,SetCapacity,GetCapacity,GetAddress,_NewEnum,HasKey,Clone
		{
			functionName:= "Obj" methodName
			return %functionName%(this, arguments*)
		}
		else
			throw Exception("Non-existent method", -1, methodName)
	}

	; Must not define any other methods in this class or any super-class.

}
I am using the fact that all builtin methods can also be called as Obj<methodname>, e.g. Obj_NewEnum for the _NewEnum method etc.

EDIT: Corrected the code. Now it seems to work.
jhertel
Posts: 18
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 14:13

Only problem now is that if I call an existing method with too few arguments, the call does nothing and doesn't report an error:

Code: Select all

class Test extends ThrowsNonexistentMethod {
    ExistentMethod(x) {
        MsgBox Hello %x%.
    }
}

Test.ExistentMethod()  ; Does nothing and does not report an error.
Test.ExistentMethod(1)  ; Shows a message box with the text "Hello 1."
Is there any way to catch this situation as well?


EDIT: I thought the problem was there for too many arguments as well, but I was wrong about that.
Coco-guest

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 15:23

Code: Select all

class ThrowsNonexistentMethod {
    __Call(method:="") {
        throw Exception("Non-existent method", -1, method)
    }
    ; Must not define any methods in this class or any super-class.
}

class Test extends ThrowsNonexistentMethod {
	; do the check in this __Call
	__Call(method, args*) {
		if IsFunc(FuncObj := this[method]) {
			count := Round(args.MaxIndex()) + 1 ; add one for hidden 'this'
			if (count < FuncObj.MinParams)
				throw Exception("Too few parameters passed.", -1, count)
			else if (count > FuncObj.MaxParams) && !FuncObj.IsVariadic
				throw Exception("Too many parameters passed.", -1, count)
		}
	}
    
    ExistentMethod(x) {
        MsgBox Hello %x%.
    }
}

t := new Test
t.ExistentMethod()
; t.ExistentMethod("Hello", "World") ; uncomment to test
return
jhertel
Posts: 18
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 15:55

Oh wow, cool, it works! :dance: Thanks Coco! :bravo:

And now I also understand better how the __Call method works. I didn't realize it was also called when a method was called with too many or too few parameters.

Thanks again! :thumbup:
User avatar
trismarck
Posts: 506
Joined: 30 Sep 2013, 01:48
Location: Poland

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 16:19

Code: Select all

Class cx {
	__Call(aFnName, aP*) {
		;o_fun := this[aFnName]	; will call meta-functions, so avoid that?
		ObjHasKey(cx, aFnName) ? (o_fun := cx[aFnName]) : (o_fun := base[aFnName])
		max_index := aP.MaxIndex() = "" ? 0 : aP.MaxIndex()
		
		if(o_fun = "")
			throw, Exception("Method not found", -1)
		if(max_index < o_fun.MinParams - 1)
			throw, Exception("Too few params.", -1)
		if(max_index > o_fun.MaxParams - 1)
			if(not o_fun.isVariadic)
				throw, Exception("Too many params.", -1)
		
	}
	fun(a) {
		msgbox fun
	}
}
x := new cx()
x.a()
;x.fun()
;x.fun(1)
x.fun(1, 2)
But this won't catch stuff like cx.fun() (just accessing the key of the object vs using the inheritance algorithm to locate the key in one of bases of object).
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 16:33

I forgot about built-in methods. You don't need to call ObjMethod(); it's more efficient to not call them:

Code: Select all

class ThrowsNonexistentMethod {
    __Call(method:="") {
        ;if !(fn := Func("Obj" method)) || !fn.IsBuiltIn  ; Incorrect result for AddRef/Release/ect.
        if method not in Insert,Remove,MinIndex,MaxIndex,SetCapacity
            ,GetCapacity,GetAddress,_NewEnum,HasKey,Clone
            throw Exception("Non-existent method", -1, method)
    }
}
Alternatively, you can define the built-in methods in the class (in which case you don't need the if not in line).

Code: Select all

class HasBuiltInMethods extends ThrowsNonexistentMethod {
    ; You can also do this directly in Test (but not Throws..).
    static Insert := Func("ObjInsert")
    static Remove := Func("ObjRemove")
    static MinIndex := Func("ObjMinIndex")
    static MaxIndex := Func("ObjMaxIndex")
    static SetCapacity := Func("ObjSetCapacity")
    static GetCapacity := Func("ObjGetCapacity")
    static GetAddress := Func("ObjGetAddress")
    static _NewEnum := Func("ObjNewEnum")
    static HasKey := Func("ObjHasKey")
    static Clone := Func("ObjClone")
}

class Test extends HasBuiltInMethods {
    ;...
}
This also makes Coco's validation code work for those methods.
jhertel wrote:I didn't realize it was also called when a method was called with too many or too few parameters.
It's not. Like __Get and __Set, it's called if the key isn't defined in this. The methods are defined in this.base. The reason Coco added a second __Call rather than using the first one is that the __Call in the super-class is not called for methods defined in Test, even if the methods themselves can't be called. If you subclass Test, any methods you define in the subclass will not be validated unless you redefine __Call, like so:

Code: Select all

class Test2 extends Test {
    static __Call := Test.__Call
    ;...
}
[trismarck beat me to it, but I'm leaving my explanation:]
Also note that because __Call is only called for keys which don't exist in the target object, it isn't called for static methods. That is, it is called for t.ExistentMethod() but not Test.ExistentMethod(), because ObjHasKey(t, "ExistentMethod") is false while ObjHasKey(Test, "ExistentMethod") is true.


Btw, if you pass more parameters than needed, it will normally not prevent the method from being called. Only passing too few parameters is considered an error when dynamically calling a function or method. In v2, generally an exception is thrown if a required parameter is missing a value.
Coco
Posts: 771
Joined: 29 Sep 2013, 20:37
Contact:

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 16:49

trismarck wrote:o_fun := this[aFnName] ; will call meta-functions, so avoid that?
Unless required by functionality, a well-designed __Get should not tamper with or override the result when the caller tries to access a key which happens to be a method(declared in the class) or a Property. IMO, devs should always allow meta-functions to finish/cast normally, it makes classes/objects easier to extend/subclass, maintain, debug(subjective), etc.
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Get an error when calling a method that does not exist?

05 Mar 2015, 17:10

Since the addition of properties, I think the appropriate uses of __Get are less normal classes and more proxy objects, wrappers and such.

this[aFnName] will also call a property if aFnName is a property name.
jhertel
Posts: 18
Joined: 13 Feb 2015, 08:24

Re: Get an error when calling a method that does not exist?

06 Mar 2015, 04:45

Wow, you guys are so helpful! Thank you all!

I did notice just before going to bed last night that what I thought worked in my last comment above did not work in subclasses, which is what I want it to do.

I can see that all this and all your comments requires of me to take some time to sit down with a nice cup of coffee and try to really wrap my head around how objects and classes work in AutoHotkey. Especially the intricate details of when and in what order the meta functions are called. I think I tried at first to partly use my intuition and experience from other languages I know (Python probably being the closest) without completely understanding how it works in AutoHotkey. I did try to read the docs of course, but I failed to understand all the details. Now I will try again, combined with you very helpful comments above, to see if I can get a clear understanding.

Thank you again for all your help!
User avatar
trismarck
Posts: 506
Joined: 30 Sep 2013, 01:48
Location: Poland

Re: Get an error when calling a method that does not exist?

06 Mar 2015, 05:26

Coco wrote:Unless required by functionality, a well-designed __Get should not tamper with or override the result when the caller tries to access a key which happens to be a method(declared in the class) or a Property.
Coco, I agree with that (~well-designed meta-functions / classes should work in every case, even if meta-functions are called in the scenario above). Actually, this could be the point of why my example with base[aFnName] is broken.
Coco wrote:IMO, devs should always allow meta-functions to finish/cast normally, it makes classes/objects easier to extend/subclass, maintain, debug(subjective), etc.
To clarify, by "finish/cast normally" I understand that meta-functions should never explicitly return, unless its explicitly needed.
Lexikos wrote:this[aFnName] will also call a property if aFnName is a property name.
Thanks. So in the example I posted, if there was custom behaviour defined for the key aFnName somewhere upstream in the class hierarchy (i.e. a behaviour that forbids to _call_ (vs get) a Property), then base[aFnName] would just skip that behaviour (undesired). So that example is actually crap. Another note is that this[aFnName] really does __Get, not __Call. Yet another note is that even doing this[aFnName]() (__Call) will _still_ invoke 'get' of the Property; fortunately, call invocation of getter of Property can be forbidden i.e. inside of a meta-function of the class that (the class) defines the Property.

So it looks like the best practice is this:
  • for the existence of the method, at the top of the class hierarchy, define a class that reports non-existence
  • for the number of parameters, every class should act on its own and report errors about too few/too many params / uncallable Properties _only_ for keys of that class object (vs for base classes). Checking can be implemented inside of meta-functions - i.e. inside of __Call.

Code: Select all

class non_existent {
	__Call(aFnName) {
		throw, Exception("Method not found: " aFnName, -1)
	}
}
class dx extends non_existent {
	fun2 {
		get {
			msgbox dx_fun2_get
		}
		set {
			msgbox dx_fun2_set
		}
	}
	__Call(aFnName, aP*) {
		; we're sure we have the key 'fun2' in this class (dx)
		; 
		; forbid calling the key
		; 
		if(aFnName = "fun2")
			throw, Exception("Property can't be called: " aFnName, -1)
	}
}
Class cx extends dx {
    __Call(aFnName, aP*) {
		; we're sure we have the key 'fun' in this class (cx)
		; 
		; if key called, allow calling but check number of params
		; 
		if(aFnName = "fun")
		{
			max_index := Round(aP.MaxIndex() )
			o_fun := cx.fun
			if(max_index < o_fun.MinParams - 1)
				throw, Exception("Too few params.", -1)
			if(max_index > o_fun.MaxParams - 1)
				if(not o_fun.isVariadic)
					throw, Exception("Too many params.", -1)
        }
    }
	fun(a) {
		msgbox cx_fun
	}
}
x := new cx()
; x.funa()	; method not found
; x.fun()		; too few parameters
; x.fun()		; ok
; x.fun(1,2)	; too many parameters
;x.fun2()	; property can't be called
It also looks like Functors can't be used as meta-functions in the example above (i.e. as a replacement of __Call if there were multiple keys that we would like to handle and each key would require a different behaviour). Using Functors would change the target of the invocation, which (preserving the target) is needed to detect the existence of the method in the inheritance chain the target is part of.
lexikos
Posts: 9592
Joined: 30 Sep 2013, 04:07
Contact:

Re: Get an error when calling a method that does not exist?

06 Mar 2015, 06:46

trismarck wrote:Using Functors would change the target of the invocation, which (preserving the target) is needed to detect the existence of the method in the inheritance chain the target is part of.
Huh? The target of invocation is this, which is passed as an additional parameter to the functor in place of the method name. If you meant whichever class defined the method, you know that at load time because you know where you wrote the method. Likewise, for functors you just need to include some information in the functor to identify which class it belongs to.

Another option is to derive your object not from the class, but from another object which merely defines meta-functions. The meta-functions can then implement inheritance in whatever way you want.
User avatar
trismarck
Posts: 506
Joined: 30 Sep 2013, 01:48
Location: Poland

Re: Get an error when calling a method that does not exist?

08 Mar 2015, 10:30

Lexikos wrote:
trismarck wrote:Using Functors would change the target of the invocation, which (preserving the target) is needed to detect the existence of the method in the inheritance chain the target is part of.
Huh? The target of invocation is this, which is passed as an additional parameter to the functor in place of the method name. If you meant whichever class defined the method, you know that at load time because you know where you wrote the method.
Yes, I didn't actually think when I wrote this. What I've meant was this: lets suppose we're invoking x.fun3() and there is a key "fun3" at the end of the inheritance tree of x, in the third base. In the first or second base, there is a functor instance defined as the __Call meta-function. When that functor instance is invoked, the original invocation can't detect the existence of the method in the rest of inheritance tree of 'x' anymore, because that invocation has just ended (by 'has just ended' I actually mean that the invocation won't traverse automatically through the rest of the inheritance tree of x) (actually, I'd have to check if the original invocation really 'ends' at that point - probably not - the original invocation call is I guess still on the callstack during the whole time the new invocation was spawned). What I've meant was sort of described in this thread and just mentioned it again here, w/o providing the link (should have done that really). But lets leave that aside.
Lexikos wrote:Likewise, for functors you just need to include some information in the functor to identify which class it belongs to.
Didn't realize that it's possible to pass 'parameters' to the functor handler by just putting the parameters inside of the functor. Thanks.

Ok, so to implement checking of too few/too many params with functors: lets suppose we have the following classes:

Code: Select all

class dx {
	fun3(a) {
		msgbox fun3
	}
}

class cx extends dx {
	__Call(aName, aP*) {
		msgbox __Call
	}
	fun1(a) {
		msgbox fun1
	}
	fun2(a) {
		
	}
}
We include the following:

Code: Select all

class Properties__Call {
	__Call(aTarget, aName, aP*) {
		this.__CallstackDepthFix := this.__CallstackDepthFix = "" ? 0 : this.__CallstackDepthFix
		
		aCurrentClass := this.__CurrentClass
		if(not isObject(aCurrentClass) )
			throw, Exception("Functor instance lacks current class specification.", -1)
		
		; call the user-made __Call, if it's there
		if(ObjHasKey(aCurrentClass, "__Call_") )
			aCurrentClass.__Call_.Call(aTarget, aName, aP*)	; for now, skip the return value
		
		; if the class object has the requested key
		if(ObjHasKey(aCurrentClass, aName) ) {
			
			; call the custom behaviour for a method, if it's there
			if(ObjHasKey(this, aName) )
				this[aName].Call(this, aTarget, aCurrentClass, aP*)	; discard return value
			
			; call the default behaviour for a method
			Properties__Call.DefaultBehaviourForExistentFunction.Call(this, aTarget, aCurrentClass, aName, aP*) ; discard return value
		
			; call the method of the class
			return aCurrentClass[aName].Call(aTarget, aP*)
		}
		
		
		; if the class object lacks the requested key
		
		; change the base of the target
		target_base := aTarget.base			; relies on correct implementation of __Get of bases of aTarget
		aTarget.base := aCurrentClass.base	; relies on correct implementation of __Set in bases of aCurrentClass
		
		; 'continue' the invocation the functor instance has interrupted, from the point of interuption
		ret := aTarget[aName](aP*)
		
		; restore the base of the target
		aTarget.base := target_base
		
		; return the return value of the method
		return ret
	}
	DefaultBehaviourForExistentFunction(aTarget, aCurrentClass, aName, aP*) {
		Properties__Call.CheckIfValueCallable.Call(this, aTarget, aCurrentClass, aName, aP*)
		Properties__Call.CheckParamCount.Call(this, aTarget, aCurrentClass, aName, aP*)
		
	}
	CheckParamCount(aTarget, aCurrentClass, aName, aP*) {
		; suppose value callable
		o_fun := aCurrentClass[aName]
		max_index := Round(aP.MaxIndex() )
		if(max_index < o_fun.MinParams - 1)
			throw, Exception("Too few parameters passed to function: " aName, -3 - this.__CallstackDepthFix)
		if(max_index > o_fun.MaxParams - 1)
		{
			if(not o_fun.IsVariadic)
				throw, Exception("Too many parameters passed to function: " aName, -3 - this.__CallstackDepthFix)
		}
		
	}
	CheckIfValueCallable(aTarget, aCurrentClass, aName, aP*) {
		o_fun := aCurrentClass[aName]
		if(isObject(o_fun) )
			if(o_fun.IsBuiltIn = 0 or o_fun.IsBuiltIn = 1)
				return	; ok
		throw, Exception("Uncallable value: " o_fun, -3 - this.__CallstackDepthFix)
		
	}
}
Lets suppose we want to have parameter checking in class dx. We modify the class like this:

Code: Select all

class dx {
	class __Call extends Properties__Call {
		static __CurrentClass := dx
		static __CallstackDepthFix := 1
		fun3(aTarget, aCurrentClass, aP*) {
			msgbox behaviour_fun3
		}
	}
	fun3(a) {
		msgbox fun3
		return 5555
	}
}

class cx extends dx {
	__Call(aName, aP*) {
		msgbox __Call_
	}
	fun1(a) {
		msgbox fun1
		return 4444
	}
	fun2(a) {
		
	}
}
And now the script should throw an exception for fun3() for too few / too many / uncallable value for fun3(). The functor could have as well be attached to cx. Complete example:

Code: Select all

class dx {
	class __Call extends Properties__Call {
		static __CurrentClass := dx
		static __CallstackDepthFix := 1
		fun3(aTarget, aCurrentClass, aP*) {
			msgbox behaviour_fun3
		}
	}
	fun3(a) {
		msgbox fun3
		return 5555
	}
}

class cx extends dx {
	static __Call := {base: Properties__Call, __CurrentClass: cx, __CallstackDepthFix: 0} ; one-liner
	; class __Call extends Properties__Call {
	; 	static __CurrentClass := cx
	; 	static __CallstackDepthFix := 0
	; 	fun1(aTarget, aCurrentClass, aP*) {
	; 		msgbox behaviour_fun1
	; 	}
	; }

	; supposing __Call was defined in a class, if we want to call 
	; that __Call even if functor specified for the meta-function, 
	; append '_' at end of __Call. The functor will call __Call_.
	__Call_(aName, aP*) {
		msgbox __Call_
	}
	fun1(a) {
		msgbox fun1
		return 4444
	}
	fun2(a) {
		
	}
}
x := new cx()
; x.fun1()					; too few
; x.fun1("aaa")				; ok
; x.fun1("aaa", "bbb")		; too many

; x.base.fun1 := 3
; x.fun1("aaa")				; uncallable value

;msgbox % x.fun1("aaa")		; 4444 - return value correct

;msgbox % x.fun3("aaa")		; 5555 - continuation of original invocation works

;msgbox % x.fun1("aaa")		; behaviour for fun1() called
x.fun3("aaa", "bbb")
The above is untested. Perhaps it could be used for debugging purposes.
__CallstackDepthFix is specified manually because I didn't know how to fix it. The callstack depth changes whenever the functor handler 'continues' the previous invocation and because the base of target is temporarily changed for that resumed invocation, the functor handler can't scan the original inheritance tree of the target to determine the proper callstack depth fix (or at least it looks like it).

//edit: the example requires >=1.1.20 (because of this) or at least >=1.1.19 because of .Call.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot], mebelantikjaya, Rohwedder and 333 guests