blob: 0616ac6e3454a52c3353ae3ec086f002a43c41bb [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of dart.core;
/**
* Error objects thrown in the case of a program failure.
*
* An `Error` object represents a program failure that the programmer
* should have avoided.
*
* Examples include calling a function with invalid arguments,
* or even with the wrong number of arguments,
* or calling it at a time when it is not allowed.
*
* These are not errors that a caller should expect or catch -
* if they occur, the program is erroneous,
* and terminating the program may be the safest response.
*
* When deciding that a function throws an error,
* the conditions where it happens should be clearly described,
* and they should be detectable and predictable,
* so the programmer using the function can avoid triggering the error.
*
* Such descriptions often uses words like
* "must" or "must not" to describe the condition,
* and if you see words like that in a function's documentation,
* then not satisfying the requirement
* is very likely to cause an error to be thrown.
*
* Example (from [String.contains]):
*
* `startIndex` must not be negative or greater than `length`.
*
* In this case, an error will be thrown if `startIndex` is negative
* or too large.
*
* If the conditions are not detectable before calling a function,
* the called function should not throw an `Error`.
* It may still throw a value,
* but the caller will have to catch the thrown value,
* effectively making it an alternative result rather than an error.
* The thrown object can choose to implement [Exception]
* to document that it represents an exceptional, but not erroneous, occurrence,
* but it has no other effect than documentation.
*
* All non-`null` values can be thrown in Dart.
* Objects extending `Error` are handled specially:
* The first time they are thrown,
* the stack trace at the throw point is recorded
* and stored in the error object.
* It can be retrieved using the [stackTrace] getter.
* An error object that merely implements `Error`, and doesn't extend it,
* will not store the stack trace automatically.
*
* Error objects are also used for system wide failures
* like stack overflow or an out-of-memory situation.
*
* Since errors are not created to be caught,
* there is no need for subclasses to distinguish the errors.
* Instead subclasses have been created in order to make groups
* of related errors easy to create with consistent error messages.
* For example, the [String.contains] method will use a [RangeError]
* if its `startIndex` isn't in the range `0..length`,
* which is easily created by `new RangeError.range(startIndex, 0, length)`.
*/
class Error {
Error(); // Prevent use as mixin.
/**
* Safely convert a value to a [String] description.
*
* The conversion is guaranteed to not throw, so it won't use the object's
* toString method.
*/
static String safeToString(Object object) {
if (object == null) {
return 'null or unkown';
}
if (object is String) {
return _stringToSafeString(object);
}
return _objectToString(object);
}
/** Convert string to a valid string literal with no control characters. */
static String _stringToSafeString(String string) {
return string;
}
static String _objectToString(Object object) {
return Object._toString(object);
}
StackTrace get stackTrace => _stackTrace;
StackTrace _stackTrace;
}
/**
* Error thrown by the runtime system when an assert statement fails.
*/
class AssertionError extends Error {
/** Message describing the assertion error. */
final Object message;
AssertionError([this.message]);
String toString() => "Assertion failed";
}
/**
* Error thrown by the runtime system when a type assertion fails.
*/
class TypeError extends AssertionError {}
/**
* Error thrown by the runtime system when a cast operation fails.
*/
class CastError extends Error {}
/**
* Error thrown when attempting to throw [:null:].
*/
class NullThrownError extends Error {
String toString() => "Throw of null.";
}
/**
* Error thrown when a function is passed an unacceptable argument.
*/
class ArgumentError extends Error {
/** Whether value was provided. */
final bool _hasValue;
/** The invalid value. */
final Object invalidValue;
/** Name of the invalid argument, if available. */
final String name;
/** Message describing the problem. */
final Object message;
/**
* The [message] describes the erroneous argument.
*
* Existing code may be using `message` to hold the invalid value.
* If the `message` is not a [String], it is assumed to be a value instead
* of a message.
*/
ArgumentError([this.message])
: invalidValue = null,
_hasValue = false,
name = null;
/**
* Creates error containing the invalid [value].
*
* A message is built by suffixing the [message] argument with
* the [name] argument (if provided) and the value. Example
*
* "Invalid argument (foo): null"
*
* The `name` should match the argument name of the function, but if
* the function is a method implementing an interface, and its argument
* names differ from the interface, it might be more useful to use the
* interface method's argument name (or just rename arguments to match).
*/
ArgumentError.value(Object value, [this.name, this.message])
: invalidValue = value,
_hasValue = true;
/**
* Create an argument error for a `null` argument that must not be `null`.
*/
ArgumentError.notNull([this.name])
: _hasValue = false,
message = "Must not be null",
invalidValue = null;
// Helper functions for toString overridden in subclasses.
String get _errorName => "Invalid argument${!_hasValue ? "(s)" : ""}";
String get _errorExplanation => "";
String toString() {
String nameString = "";
if (name != null) {
nameString = " ($name)";
}
var message = (this.message == null) ? "" : ": ${this.message}";
String prefix = "$_errorName$nameString$message";
if (!_hasValue) return prefix;
// If we know the invalid value, we can try to describe the problem.
String explanation = _errorExplanation;
String errorValue = Error.safeToString(invalidValue);
return "$prefix$explanation: $errorValue";
}
}
/**
* Error thrown due to an index being outside a valid range.
*/
class RangeError extends ArgumentError {
/** The minimum value that [value] is allowed to assume. */
final int start;
/** The maximum value that [value] is allowed to assume. */
final int end;
// TODO(lrn): This constructor should be called only with string values.
// It currently isn't in all cases.
/**
* Create a new [RangeError] with the given [message].
*/
RangeError(var message)
: super(message), start = -1, end = -1;
/**
* Create a new [RangeError] with a message for the given [value].
*
* An optional [name] can specify the argument name that has the
* invalid value, and the [message] can override the default error
* description.
*/
RangeError.value(int value, [String name, String message])
: super.value(
"${value}", name, (message != null) ? message : "Value not in range"), start = -1, end = -1;
/**
* Create a new [RangeError] for a value being outside the valid range.
*
* The allowed range is from [minValue] to [maxValue], inclusive.
* If `minValue` or `maxValue` are `null`, the range is infinite in
* that direction.
*
* For a range from 0 to the length of something, end exclusive, use
* [RangeError.index].
*
* An optional [name] can specify the argument name that has the
* invalid value, and the [message] can override the default error
* description.
*/
RangeError.range(int invalidValue, int minValue, int maxValue,
[String name, String message])
: start = minValue,
end = maxValue,
super.value(
"$invalidValue", name, (message != null) ? message : "Invalid value");
/**
* Creates a new [RangeError] stating that [index] is not a valid index
* into [indexable].
*
* An optional [name] can specify the argument name that has the
* invalid value, and the [message] can override the default error
* description.
*
* The [length] is the length of [indexable] at the time of the error.
* If `length` is omitted, it defaults to `indexable.length`.
*/
factory RangeError.index(int index, indexable,
[String name, String message, int length]) = IndexError;
/**
* Check that a [value] lies in a specific interval.
*
* Throws if [value] is not in the interval.
* The interval is from [minValue] to [maxValue], both inclusive.
*/
static void checkValueInInterval(int value, int minValue, int maxValue,
[String name, String message]) {
if (value < minValue || value > maxValue) {
throw new RangeError.range(value, minValue, maxValue, name, message);
}
}
/**
* Check that a value is a valid index into an indexable object.
*
* Throws if [index] is not a valid index into [indexable].
*
* An indexable object is one that has a `length` and a and index-operator
* `[]` that accepts an index if `0 <= index < length`.
*
* If [length] is provided, it is used as the length of the indexable object,
* otherwise the length is found as `indexable.length`.
*/
static void checkValidIndex(int index, var indexable,
[String name, int length = -1, String message]) {
if (length == -1) throw "Unimplemented"; /* length = indexable.length; */
// Comparing with `0` as receiver produces better dart2js type inference.
if (0 > index || index >= length) {
if (name == null) name = "index";
throw new RangeError.index(index, indexable, name, message, length);
}
}
/**
* Check that a range represents a slice of an indexable object.
*
* Throws if the range is not valid for an indexable object with
* the given [length].
* A range is valid for an indexable object with a given [length]
*
* if `0 <= [start] <= [end] <= [length]`.
* An `end` of `null` is considered equivalent to `length`.
*
* The [startName] and [endName] defaults to `"start"` and `"end"`,
* respectively.
*
* Returns the actual `end` value, which is `length` if `end` is `null`,
* and `end` otherwise.
*/
static int checkValidRange(int start, int end, int length,
[String startName, String endName, String message]) {
// Comparing with `0` as receiver produces better dart2js type inference.
// Ditto `start > end` below.
if (0 > start || start > length) {
if (startName == null) startName = "start";
throw new RangeError.range(start, 0, length, startName, message);
}
if (end != -1) {
if (start > end || end > length) {
if (endName == null) endName = "end";
throw new RangeError.range(end, start, length, endName, message);
}
return end;
}
return length;
}
/**
* Check that an integer value isn't negative.
*
* Throws if the value is negative.
*/
static void checkNotNegative(int value, [String name, String message]) {
if (value < 0) throw new RangeError.range(value, 0, -1, name, message);
}
String get _errorName => "RangeError";
String get _errorExplanation {
assert(_hasValue);
String explanation = "";
if (start == -1) {
if (end != -1) {
explanation = ": Not less than or equal to $end";
}
// If both are null, we don't add a description of the limits.
} else if (end == -1) {
explanation = ": Not greater than or equal to $start";
} else if (end > start) {
explanation = ": Not in range $start..$end, inclusive";
} else if (end < start) {
explanation = ": Valid value range is empty";
} else {
// end == start.
explanation = ": Only valid value is $start";
}
return explanation;
}
}
/**
* A specialized [RangeError] used when an index is not in the range
* `0..indexable.length-1`.
*
* Also contains the indexable object, its length at the time of the error,
* and the invalid index itself.
*/
class IndexError extends ArgumentError implements RangeError {
/** The indexable object that [index] was not a valid index into. */
final Iterable indexable;
/** The length of [indexable] at the time of the error. */
final int length;
final int invalidIntValue;
/**
* Creates a new [IndexError] stating that [invalidValue] is not a valid index
* into [indexable].
*
* The [length] is the length of [indexable] at the time of the error.
* If `length` is omitted, it defaults to `indexable.length`.
*
* The message is used as part of the string representation of the error.
*/
IndexError(this.invalidIntValue, Iterable indexable,
[String name, String message, int length = -1])
: this.indexable = indexable,
this.length = (length != -1) ? length : indexable.length,
super.value('$invalidIntValue', name,
(message != null) ? message : "Index out of range");
// Getters inherited from RangeError.
int get start => 0;
int get end => length - 1;
String get _errorName => "RangeError";
String get _errorExplanation {
assert(_hasValue);
if (invalidIntValue < 0) {
return ": index must not be negative";
}
if (length == 0) {
return ": no indices are valid";
}
return ": index should be less than $length";
}
}
/**
* Error thrown when control reaches the end of a switch case.
*
* The Dart specification requires this error to be thrown when
* control reaches the end of a switch case (except the last case
* of a switch) without meeting a break or similar end of the control
* flow.
*/
class FallThroughError extends Error {
FallThroughError();
String toString() {
return "'$_url': Switch case fall-through at line $_line.";
}
FallThroughError._create(this._url, this._line);
static _throwNew(int case_clause_pos) native "FallThroughError_throwNew";
String _url;
int _line;
}
/**
* Error thrown when trying to instantiate an abstract class.
*/
class AbstractClassInstantiationError extends Error {
final String _className;
AbstractClassInstantiationError(String className) : _className = className;
String toString() {
return "Cannot instantiate abstract class $_className: "
"_url '$_url' line $_line";
}
AbstractClassInstantiationError._create(
this._className, this._url, this._line);
static _throwNew(int case_clause_pos, String className)
native "AbstractClassInstantiationError_throwNew";
String _url;
int _line;
}
/**
* Error thrown by the default implementation of [:noSuchMethod:] on [Object].
*/
class NoSuchMethodError extends Error {
final Object _receiver;
final Symbol _memberName;
final List _arguments;
final Map<Symbol, dynamic> _namedArguments;
final List _existingArgumentNames;
/**
* Create a [NoSuchMethodError] corresponding to a failed method call.
*
* The [receiver] is the receiver of the method call.
* That is, the object on which the method was attempted called.
* If the receiver is `null`, it is interpreted as a call to a top-level
* function of a library.
*
* The [memberName] is a [Symbol] representing the name of the called method
* or accessor. It should not be `null`.
*
* The [positionalArguments] is a list of the positional arguments that the
* method was called with. If `null`, it is considered equivalent to the
* empty list.
*
* The [namedArguments] is a map from [Symbol]s to the values of named
* arguments that the method was called with.
*
* The optional [existingArgumentNames] is the expected parameters of a
* method with the same name on the receiver, if available. This is
* the signature of the method that would have been called if the parameters
* had matched.
*/
NoSuchMethodError(Object receiver, Symbol memberName,
List positionalArguments, Map<Symbol, dynamic> namedArguments,
[List existingArgumentNames = null])
: _receiver = receiver,
_memberName = memberName,
_arguments = positionalArguments,
_namedArguments = namedArguments,
_existingArgumentNames = existingArgumentNames,
_invocation_type = -1;
String toString() {
var level = (_invocation_type >> _InvocationMirror._CALL_SHIFT) &
_InvocationMirror._CALL_MASK;
var type = _invocation_type & _InvocationMirror._TYPE_MASK;
String memberName = (_memberName == null)
? ""
: internal.Symbol.getUnmangledName(_memberName);
if (type == _InvocationMirror._LOCAL_VAR) {
return "NoSuchMethodError: Cannot assign to final variable '$memberName'";
}
StringBuffer arguments = new StringBuffer();
int argumentCount = 0;
if (_arguments != null) {
for (; argumentCount < _arguments.length; argumentCount++) {
if (argumentCount > 0) {
arguments.write(", ");
}
arguments.write(Error.safeToString(_arguments[argumentCount]));
}
}
if (_namedArguments != null) {
_namedArguments.forEach((Symbol key, var value) {
if (argumentCount > 0) {
arguments.write(", ");
}
arguments.write(internal.Symbol.getUnmangledName(key));
arguments.write(": ");
arguments.write(Error.safeToString(value));
argumentCount++;
});
}
bool args_mismatch = _existingArgumentNames != null;
String args_message = args_mismatch ? " with matching arguments" : "";
String type_str;
if (type >= 0 && type < 5) {
type_str = (const [
"method",
"getter",
"setter",
"getter or setter",
"variable"
])[type];
}
StringBuffer msg_buf = new StringBuffer("NoSuchMethodError: ");
bool is_type_call = false;
switch (level) {
case _InvocationMirror._DYNAMIC:
{
if (_receiver == null) {
if (args_mismatch) {
msg_buf.writeln("The null object does not have a $type_str "
"'$memberName'$args_message.");
} else {
msg_buf
.writeln("The $type_str '$memberName' was called on null.");
}
} else {
if (_receiver is _Closure) {
msg_buf.writeln("Closure call with mismatched arguments: "
"function '$memberName'");
} else if (_receiver is _Type && memberName == "call") {
is_type_call = true;
String name = _receiver.toString();
msg_buf.writeln("Attempted to use type '$name' as a function. "
"Since types do not define a method 'call', this is not "
"possible. Did you intend to call the $name constructor and "
"forget the 'new' operator?");
} else {
msg_buf
.writeln("Class '${_receiver.runtimeType}' has no instance "
"$type_str '$memberName'$args_message.");
}
}
break;
}
case _InvocationMirror._SUPER:
{
msg_buf.writeln("Super class of class '${_receiver.runtimeType}' has "
"no instance $type_str '$memberName'$args_message.");
memberName = "super.$memberName";
break;
}
case _InvocationMirror._STATIC:
{
msg_buf.writeln("No static $type_str '$memberName'$args_message "
"declared in class '$_receiver'.");
break;
}
case _InvocationMirror._CONSTRUCTOR:
{
msg_buf.writeln("No constructor '$memberName'$args_message declared "
"in class '$_receiver'.");
memberName = "new $memberName";
break;
}
case _InvocationMirror._TOP_LEVEL:
{
msg_buf.writeln("No top-level $type_str '$memberName'$args_message "
"declared.");
break;
}
}
if (level == _InvocationMirror._TOP_LEVEL) {
msg_buf.writeln("Receiver: top-level");
} else {
msg_buf.writeln("Receiver: ${Error.safeToString(_receiver)}");
}
if (type == _InvocationMirror._METHOD) {
String m = is_type_call ? "$_receiver" : "$memberName";
msg_buf.write("Tried calling: $m($arguments)");
} else if (argumentCount == 0) {
msg_buf.write("Tried calling: $memberName");
} else if (type == _InvocationMirror._SETTER) {
msg_buf.write("Tried calling: $memberName$arguments");
} else {
msg_buf.write("Tried calling: $memberName = $arguments");
}
if (args_mismatch) {
StringBuffer formalParameters = new StringBuffer();
for (int i = 0; i < _existingArgumentNames.length; i++) {
if (i > 0) {
formalParameters.write(", ");
}
formalParameters.write(_existingArgumentNames[i]);
}
msg_buf.write("\nFound: $memberName($formalParameters)");
}
return msg_buf.toString();
}
static void _throwNew(Object receiver, String memberName, int invocation_type,
List arguments, List argumentNames, List existingArgumentNames) {
int numNamedArguments = argumentNames == null ? 0 : argumentNames.length;
int numPositionalArguments = arguments == null ? 0 : arguments.length;
numPositionalArguments -= numNamedArguments;
List positionalArguments;
if (numPositionalArguments > 0) {
// TODO(srdjan): Unresolvable static methods sometimes do not provide the
// arguments, because the arguments are evaluated but not passed to the
// throwing stub (see EffectGraphVisitor::BuildThrowNoSuchMethodError and
// Parser::ThrowNoSuchMethodError)). There is no way to distinguish the
// case of no arguments from the case of the arguments not being passed
// in here, though. See https://github.com/dart-lang/sdk/issues/27572
positionalArguments = arguments.sublist(0, numPositionalArguments);
}
Map<Symbol, dynamic> namedArguments = new Map<Symbol, dynamic>();
for (int i = 0; i < numNamedArguments; i++) {
var arg_value = arguments[numPositionalArguments + i];
namedArguments[new Symbol(argumentNames[i])] = arg_value;
}
throw new NoSuchMethodError._withType(
receiver,
new Symbol(memberName),
invocation_type,
positionalArguments,
namedArguments,
existingArgumentNames);
}
/*
static void _throwNewIfNotLoaded(
_LibraryPrefix prefix,
Object receiver,
String memberName,
int invocation_type,
List arguments,
List argumentNames,
List existingArgumentNames) {
if (!prefix.isLoaded()) {
_throwNew(receiver, memberName, invocation_type, arguments, argumentNames,
existingArgumentNames);
}
}*/
final int _invocation_type;
NoSuchMethodError._withType(
this._receiver,
/*String|Symbol*/ memberName,
this._invocation_type,
this._arguments,
Map<dynamic, dynamic> namedArguments,
[List existingArgumentNames = null])
: this._memberName =
(memberName is String) ? new Symbol(memberName) : memberName,
this._namedArguments = (namedArguments == null)
? null
: new Map<Symbol, dynamic>.fromIterable(namedArguments.keys,
key: (k) => (k is String) ? new Symbol(k) : k,
value: (k) => namedArguments[k]),
this._existingArgumentNames = existingArgumentNames;
}
/**
* The operation was not allowed by the object.
*
* This [Error] is thrown when an instance cannot implement one of the methods
* in its signature.
*/
class UnsupportedError extends Error {
final String message;
UnsupportedError(this.message);
String toString() => "Unsupported operation: $message";
static _throwNew(String msg) {
throw new UnsupportedError(msg);
}
}
/**
* Thrown by operations that have not been implemented yet.
*
* This [Error] is thrown by unfinished code that hasn't yet implemented
* all the features it needs.
*
* If a class is not intending to implement the feature, it should throw
* an [UnsupportedError] instead. This error is only intended for
* use during development.
*/
class UnimplementedError extends Error implements UnsupportedError {
final String message;
UnimplementedError([this.message]);
String toString() => (this.message != null
? "UnimplementedError: $message"
: "UnimplementedError");
}
/**
* The operation was not allowed by the current state of the object.
*
* This is a generic error used for a variety of different erroneous
* actions. The message should be descriptive.
*/
class StateError extends Error {
final String message;
StateError(this.message);
String toString() => "Bad state: $message";
}
/**
* Error occurring when a collection is modified during iteration.
*
* Some modifications may be allowed for some collections, so each collection
* ([Iterable] or similar collection of values) should declare which operations
* are allowed during an iteration.
*/
class ConcurrentModificationError extends Error {
/** The object that was modified in an incompatible way. */
final Object modifiedObject;
ConcurrentModificationError([this.modifiedObject]);
String toString() {
if (modifiedObject == null) {
return "Concurrent modification during iteration.";
}
return "Concurrent modification during iteration: "
"${Error.safeToString(modifiedObject)}.";
}
}
class OutOfMemoryError implements Error {
const OutOfMemoryError();
String toString() => "Out of Memory";
StackTrace get stackTrace => null;
}
class StackOverflowError implements Error {
const StackOverflowError();
String toString() => "Stack Overflow";
StackTrace get stackTrace => null;
}
/**
* Error thrown when a lazily initialized variable cannot be initialized.
*
* A static/library variable with an initializer expression is initialized
* the first time it is read. If evaluating the initializer expression causes
* another read of the variable, this error is thrown.
*/
class CyclicInitializationError extends Error {
final String variableName;
CyclicInitializationError([this.variableName]);
String toString() => variableName == null
? "Reading static variable during its initialization"
: "Reading static variable '$variableName' during its initialization";
static _throwNew(String variableName) {
throw new CyclicInitializationError(variableName);
}
}