It's a matter of where do you want to draw the line. You can design a language that will detect validity of implicit downcast:
public static void main(String args[]) {
// An implicit upcast
Fruit parent = new Apple();
// An implicit downcast to Apple
Apple child = parent;
}
Now, let's extract a method:
public static void main(String args[]) {
// An implicit upcast
Fruit parent = new Apple();
eat(parent);
}
public static void eat(Fruit parent) {
// An implicit downcast to Apple
Apple child = parent;
}
We're still good. Static analysis is much harder but still possible.
But the problem pops up the very second somebody adds:
public static void causeTrouble() {
// An implicit upcast
Fruit trouble = new Orange();
eat(trouble);
}
Now, where do you want to raise the error? This creates a dilemma, one can say that the problem is in Apple child = parent;
, but this can be rebutted by "But it worked before". From the other hand, adding eat(trouble);
caused the problem", but the whole point of polymorphism is to allow exactly that.
In this case, you can do some of the programmer's job, but you can't do it all the way. The further you take it before giving up, the harder it will be to explain what went wrong. So, it's better to halt as soon as possible, according to the principle of reporting errors early.
BTW, in Java the downcast you've described is not actually a downcast. This is a general cast, which can cast apples to oranges just as well. So, technically speaking @chi's idea is here already, there are no downcasts in Java, only "everycasts". It would make sense to design a specialized downcast operator which will throw compilation error when it's result type can't be found downstream from it's argument type. It would be a good idea to make "everycast" much more difficult to use in order to discourage programmers from using it without a very good reason. C++'s XXXXX_cast<type>(argument)
syntax comes to mind.