Description
I want to be able to easily delegate an object's function call to another object, like a proxy. A way I think this could be nicely implemented and safe for static analysis, is the one described here.
I believe that this functionality would provide a solution to an important part of the users that for are requesting mirrors for flutter. Many people want mirrors just to proxy function calls. It would also provide a much more general solution to a previous request about object wrappers/proxies dart-lang/sdk#414.
The idea is to introduce a keyword 'delegate' that repeats the original call made to a class method to another object. Some positive things about introducing this would be:
- Syntactic sugar for some common patterns, like calling super.methodName(args...)
- Introduces a subset of mirrors functionality, but static type safe
- Easy creation of proxy/wrapper classes
Details are clarified in the examples below. What do you think?
(is it more clear write simpler example with class A, B, bar, foo, etc?)
class House {
bool setEncompassingLimits(northLimit, soutLimit, westLimit, eastLimit) { /* ... */ }
// ...
}
class TreeHouse extends House {
@override
bool setEncompassingLimits(northLimit, southLimit, westLimit, eastLimit) {
// ...
delegate super; // equivalent to super.setEncompassingLimitss(args...) with original function call args
// ...
}
cleanTreeBranches(tool) {}
}
class HouseManager {
House house;
bool setEncompassingLimits(args) => delegate house;
//....
}
TreeHouse treeHouse;
HouseManager houseManager;
treeHouse.setEncompassingLimits(args...); // will call super.setEncompassingLimits(args...)
houseManager.setEncompassingLimits(args...); // will call houseManager.house.setEncompassingLimits(args...)
Essentially 'delegate' means, repeat the exact same call that was done in the current object on another object (or super). The object must therefore implement the exact same function otherwise it is a an undefined_method error. This is syntactic sugar for a common pattern that is calling super.methodName(args...), but is more versatile than that by allowing to repeat the call to another object, which is also used sometimes, specially in "proxy" classes.
Special Case noSuchMethod
A special case is calling 'delegate' on noSuchMethod. When delegate is called inside a noSuchMethod function declaration, the call of the function to super or another object should be of the original function that was called and not to noSuchMethod. For example:
class HouseHolder implements TreeHouse {
House house;
TreeHouse treeHouse;
noSuchMethod(Invocation invocation) => {
delegate house;
delegate treeHouse;
};
// error in this class definition, HouseHolder must implement cleanTreeBranches as explained below
}
HouseHolder houseHolder;
houseHolder.setEncompassingLimits(args...) // allowed, makes same function call in house and treeHouse
houseHolder.cleanTreeBranches(args...) // static type check error, house does not implement cleanTreeBranches
If a method is on the interface of the class but is not implemented, the static type checks should assume a call of methodName(args...) to each object where the call is being delegated inside noSuchMethod. In other words, the implemented interface reached by noSuchMethod is equal to the intersection of the interfaces of the delegated class instances, in the above example it would be House and TreeHouse interfaces intersection.
Small optimizations
It could also be allowed to skip the function body and do something like this:
methodName(args...): delegate another_object;
Which the compiler can immediately interpret as a call another_object.methodName(args...), without passing through the method that delegates. Like this:
class HouseProxy implements TreeHouse {
House house;
TreeHouse treeHouse;
cleanTreeBranches(tool): delegate treeHouse; // compiler send call to this method directly to treeHouse
noSuchMethod(Invocation invocation): delegate house; // the compiler forwards any call of the House interface directly to house
}
HouseProxy proxy;
proxy.cleanTreeBranches(tool) // compiler understands this is equivalent to proxy.treeHouse.cleanTreeBranches(args...)
proxy.setEncompassingLimis(args...) // compiler understands this is equivalent to proxy.house.setEncompaasingLimits(args...)