Reputation: 3995
I anticipated a compile-time error when overriding a method in Dart without the correct (to my knowledge so far) return type. However, things seem to be different when the return type of the method is void
:
abstract class AbstractClass {
void methodDoesNotReallyReturnVoid();
}
class ConcreteClass extends AbstractClass {
// NO ERROR here!
@override
int methodDoesNotReallyReturnVoid() => 12;
}
void main() {
print(ConcreteClass().methodDoesNotReallyReturnVoid()); // hm...?
}
The code above compiles without complaints and prints the integer as expected though I did not properly override that method like, for example, in Java.
May I know what is distinct about "void" here?
Upvotes: 1
Views: 1399
Reputation: 89995
@Ardent Coder's answer cites specific details, but a short version is:
Can the int methodDoesNotReallyReturnVoid()
override satisfy the contract of the base void methodDoesNotReallyReturnVoid()
method? Yes, because the returned value can simply be ignored.
Upvotes: 2
Reputation: 3995
Here is the answer after combining certain parts of the Formal specification (Dart 2.2).
Firstly, there are conditions defined in the language specification for a method to be a correct override. In the scope of the confusion described in the question, here is the section which matters:
If m and m' are both methods or both setters: Let F be the function type of m except that the parameter type is the built-in class Object for each parameter of m which has the modifier covariant. Let F' be the function type of m'. F must then be a subtype of F'.
That means the return type under consideration can be a subtype as well. The following code snippet shall make it clear:
abstract class AbstractClass {
num methodReturnsNum();
}
class ConcreteClass extends AbstractClass {
// No error here because int is a subtype of num
@override
int methodReturnsNum() => 12;
}
void main() {
// Compiles and prints the integer.
print(ConcreteClass().methodReturnsNum());
}
So going by the original problem, does it mean that int
is a subtype of void
? It was new to me, and the answer is "Yes":
Every type is a subtype of Object, every type is a subtype of dynamic, and every type is a subtype of void. Note that this implies that these types are equivalent according to the subtype relation.
Hence, the code in the question is similar to:
abstract class AbstractClass {
Object methodReturnsObject();
}
class ConcreteClass extends AbstractClass {
// No error here because int is a subtype of Object
@override
int methodReturnsObject() => 12;
}
void main() {
// Compiles and prints the integer.
print(ConcreteClass().methodReturnsObject());
}
Addressing the element of surprise, a segment under Void Soundness has a note:
In particular, we could require that method overrides should never override return type Object by return type void, or parameter types in the opposite direction; parameterized types with type argument void could not be assigned to variables where the corresponding type argument is anything other than void, etc. etc. But this would be quite impractical. In particular, the need to either prevent a large number of type variables from ever having the value void, or preventing certain usages of values whose type is such a type variable, or whose type contains such a type variable, that would be severely constraining on a very large part of all Dart code. So we have chosen to help developers maintain this self-imposed discipline in simple and direct cases, and leave it to ad-hoc reasoning or separate tools to ensure that the indirect cases are covered as closely as needed in practice.
Upvotes: 1