Description
Developers don't typically write code that depends on the distinction between CastError
and TypeError
. I suspect many developers don't have a clear model of which one is thrown when. Either the code crashed or it didn't. Errors tend to be handled uniformly, for example, they are logged and then the program tries to recover from a general failure.
Having two almost identical Errors creates a burden in code size and library complexity.
dart2js has many small 'helper' functions to implement the checks efficiently.
The following code from sdk/lib/_internal/js_runtime/lib/rti.dart
illustrates the duplication:
/// Specialization for 'as String?'.
/// Called from generated code.
String /*?*/ _asStringNullable(object) {
if (_isString(object)) return _Utils.asString(object);
if (object == null) return object;
throw _CastError.forType(object, 'String');
}
/// Specialization for check on 'String?'.
/// Called from generated code.
String /*?*/ _checkStringNullable(object) {
if (_isString(object)) return _Utils.asString(object);
if (object == null) return object;
throw _TypeError.forType(object, 'String');
}
Note that the only difference is in the error object _CastError
vs _TypeError
.
These methods are called from many places in the program, either indirectly through type objects representing String*
and String?
, or directly in optimized code.
The whole system of specialization is replicated for two exception types. Simplification via passing in an 'isTypeError' flag has an impact proportional to the application size that is unacceptable to our customers.
I propose that CastError
becomes an interface that is implemented by TypeError
, and removed at a future major revision.