First of all some definitions,
- a local name is a name of a variable which is no field or property or class and has been declared before a write or read.
- an unbound name is a name which origin is unknown during compile time. A unbound name can never be a local name.
- a static name is a name whose origin is determined during compile time. All local names are static names. But we will use the term "static name" for names which are not local names.
- a markup closure is a closure that can be used as part of a builder
- a block closure. This closure can also be used in a builder, but is not part of it.
because of the lack of an syntax to define markup closures different from block closures I will write what type will be used.
Name scoping rules:
- a local name can only defined once
- unbound names cannot hide a local name
- unbound and local names are only visible in the scope they are defined in
- if an unbound name is used, there can't be a local name with the same name
- a static name can be hidden by local names
- only markup closures can contain unbound names.
- markup closures don't contain static names
A local name can only defined once
i is defined three times here and with a different types. It surely is a mistake in most cases. The i in the closure can not define a new local name, because a i from outside is still visible
Unbound names cannot hide a local name
b is unbound here. This is valid if and only if cl is a markup closure. a is refereing to the a outside the closure and does not define a new name then.
Unbound and local names are only visible in the scope they are defined in
j is defined in the for loop, so it is only visible inside that loop. A println j out side is invalid, as j is not defined here.
it's allowed to define j outside the for loop, because the visibility for the j defined inside the for loop is limited to the loop.
If an unbound name is used, there can't be a local name with the same name
(c is a markup closure)
the first _i_in c is unbound, it should be handled as if there was an definition of that name as local name somewhere outside, which means this is a little modification of the first rule.
Static names can be hidden by local names
static names are fields or properties of the current class or parent class.
outside foo a is a static name, inside foo a is hidden by a local name
Only markup closures can contain unbound names.
due to a missing syntax difference between markup closure and block closures it is not possible to give here an example.
Markup closures don't contain static names
let us assume the closure for the builder is a markup closure, then the name a is always handled like an unbound name. The closure's delegate may rsolve the name against a class, but it don't has to.
static name is private
the handling here should be equal to the handling of private methods.
all method calls with implicit "this" in a markup closure are resolved at runtime against the delegate. But a explicit "this" does always mean the class not the closure.
The General Idea
The basic idea is to let static names be resolved by the compiler. If we are in a markup closure then the compiler has finished its work since all non static names are resolved against the delegate. In a block closure we have no delegate. That means the compiler has to throw an error for unbound variables, and has to resolve static names.
as you might see, dynamic properties are generally more like unbound names than like static names. To still be able to have them I think we can go the way the current groovy is going and require them to use a "this" before. In a markup closure the "this" can be removed if the delegate decides to resolve a name first against the class. But I would not require that.
Nesting of Builders
the most inner call f(a:1) is resolved against the closure's delegate. That delegate is what subbuilder thinks the closure should have. This means f(a:1) notifies the subbuilder about the method call, but not the builder outside. f(a:2) notifies the outer builder, but not the subbuilder. All Closures here are markup closures.
Calling Methods Outside a Builder
the first foo call is with implicit "this" and so it is resolved against the closures delegate. If the delegate decides not to resolve against the class then the call gets to the builder or throws an exception. The call this.foo() uses an explicit "this", which means the dlegate is never used to resolve the name.
Combining Builders and Block Closures
the closure following the build call is a markup closure, the closure c is a block closure. the println command inside c is never directed to the builder. It surely has an implicit "this", but we are not directly inside a markup closure.
How To Differentiate Block Closures and Markup Closures
this is the biggest problem.
If we use the with-syntax to define the start of a markup structure, then we can't have block closures in there - not until we define some kind of escaping. Additionally closures defined outside can't be used for the builder or have to be marked somehow... possibly again with the with-syntax.
all three closures here would be markup closures. Even d would be such a markup closure
Then maybe mark the block Closure somehow? I think that is a bad idea as the block closure is one of the most used types of closures in Groovy. If there is no other solution, then the closure d in the example above really has to be marked somehow... for example using "->"
This means any Closure using "->" does define a block closure and can't be a markup closure. As it is not that common to use "->" for markup closures this seems to be an easy possibility. But of course any other idea is welcome. I would still keep the with-keywod as I don't want to have a markup closure for such a program:
Calling overloaded methods with implicit or explicit this
Calling a method by name is not enough, using the runtime types to choose the method is not enough.
It should be possible to select a method using casts and private methods shouldn't be overwritten through MetaClass.
- a static method call is a method call where the possible set of methods to be called is known at compile time.
- a strong static method call is a method call where an explicit method is choosing using compile time types
- a dynamic method call is a method call using the runtime type information.
- every call to invokeMethod for GroovyObject types is dynamic
in Java, method selection can be enforced by casting. I think we too should support that to make a call a strong static call. This means a strong static call can never be dynamic. The compiler has to choose at compile time which method to call and can make the call without using the MetaClass infrastructure.
the call of foo is strong static as every parameter is casted before.
foo is always a dynamic call here
against the strong static method call not all parameters have to be casted.
Rules with handling the method call types
- strong static and static method calls can't be overwritten by the MetaClass or any use-block.
- a strong static method call results never in a dynamic method call.
Only with these two rules it is possible to choose a method at compile time.
Cases for converting Dynamic to Strong Static
Not only is there only 1 method, that method is private too. It should be safe to make a call strong static here. This means MetaClass is not used to make the call.
It's the same example as before, but this time the method is public. This means MetaClass is able to overwrite the definition of that method and because of that no (strong) static method call is possible. Additionally MetaClass may overload that method.
in this case, the method foo is overloaded. Again no automatic conversion to a strong static call is possible. But MetaClass has to guarantee that if the runtime type of x is Integer, then the private method foo(Integer) is called instead of a possibly defined method inside MetaClass. For this case, the call may bypass MetaClass completly.
Overloading and Inheritance
Because the method call of foo in bar in class A is strong static it is not possible for class B to overload/overwrite that method
the call to foo in bar is not strong static, but bar has to call foo(Integer) if the parameter to bar is of type Integer. And even if the class A does not define a method foo(String) a call to bar with a String on a instance of B has to call the method B#foo(String). Note: This is very different from Java!
Calling overloaded methods from outside the class
as the class on which I do the call is only known at runtime the compiler can't make strong static calls even if all parameters are casted. for the same reason such a call can't be static too. So any such method call is dynamic. But still it should be possible to select a method
strong dynamic means that not the runtime type is used for arguments, instead the compile time types are used given through explicit casts as it would be done for static or strong static.
Usage with Overloaded Methods
Even if there is a method foo(String) a cast to Object let us choose foo(Object) instead
Meta Object Protocoll
No method foo is defined in A, but as any Groovy class does implicitly implement the interface GroovyObject and any such class can use a invokeMethod method, invokeMethod is called here and return 1.
Of course this means we are loosing the information for the compiletime typing. we could extend the protocoll so that we have an additional method invokeMethod(String,Object,Class), but that is no needed feature and can be delayed until post 1.0
For this the caller class is important. The method selection should include the caller class here since we may have to do a special handling then.
Foo is the class containing this script, I will use the term sender for it. Object is the class of x, I will use the term receiver for it.
Sender is parent class of Receiver
as x in bar(x) could have the type A, and it does have that type here, we can't just simply call the method foo on x. If the receiver is of a subtype of the sender and the method to be called is defined in the sender, then the sender's method should be used even if the receiver does overwrite that method. This means as long as x has a subtype of A I can be sure the assert in bar will succeed!
Sender is not parent class of Receiver
as x has now the type C and that is no subtype of A we will call the method C#foo and not B#foo(). The assert succeedes because C#foo returns 1. Note: compare this example with the example before. There was x of type B, but even there B#foo wasn't called.