blob: 61aa5e3b5c2f5f99165eb282b66e2b39c5e8045a [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,
/// 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 being an [Exception] 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 `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 except for specific known and trusted types.
static String safeToString(Object? object) {
if (object is num || object is bool || null == object) {
return object.toString();
}
if (object is String) {
return _stringToSafeString(object);
}
return _objectToString(object);
}
/// Convert string to a valid string literal with no control characters.
external static String _stringToSafeString(String string);
external static String _objectToString(Object object);
/// The stack trace at the point where this error was first thrown.
///
/// Classes which *extend* `Error` will automatically have a stack
/// trace filled in the first time they are thrown by a `throw`
/// expression.
external StackTrace? get stackTrace;
}
/// Error thrown by the runtime system when an assert statement fails.
class AssertionError extends Error {
/// Message describing the assertion error.
final Object? message;
/// Creates an assertion error with the provided [message].
AssertionError([this.message]);
String toString() {
if (message != null) {
return "Assertion failed: ${Error.safeToString(message)}";
}
return "Assertion failed";
}
}
/// Error thrown by the runtime system when a dynamic type error happens.
class TypeError extends Error {}
/// Error thrown by the runtime system when a cast operation fails.
@Deprecated("Use TypeError instead")
class CastError extends Error {}
/// Error thrown when attempting to throw `null`.
///
/// In null safe code, you are statically disallowed from throwing `null`,
/// so this error will go away when non-null safe code stops being supported.
class NullThrownError extends Error {
@pragma("vm:entry-point")
NullThrownError();
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 dynamic invalidValue;
/// Name of the invalid argument, if available.
final String? name;
/// Message describing the problem.
final dynamic message;
/// Creates an error with [message] describing the problem with an 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.
@pragma("vm:entry-point")
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).
@pragma("vm:entry-point")
ArgumentError.value(value, [this.name, this.message])
: invalidValue = value,
_hasValue = true;
/// Creates 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;
/// Throws if [argument] is `null`.
///
/// If [name] is supplied, it is used as the parameter name
/// in the error message.
///
/// Returns the [argument] if it is not null.
@Since("2.1")
static T checkNotNull<@Since("2.8") T>(T? argument, [String? name]) {
if (argument == null) throw ArgumentError.notNull(name);
return argument;
}
// Helper functions for toString overridden in subclasses.
String get _errorName => "Invalid argument${!_hasValue ? "(s)" : ""}";
String get _errorExplanation => "";
String toString() {
String? name = this.name;
String nameString = (name == null) ? "" : " ($name)";
Object? message = this.message;
var messageString = (message == null) ? "" : ": ${message}";
String prefix = "$_errorName$nameString$messageString";
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 a value being outside a valid range.
class RangeError extends ArgumentError {
/// The minimum value that [value] is allowed to assume.
final num? start;
/// The maximum value that [value] is allowed to assume.
final num? 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].
@pragma("vm:entry-point")
RangeError(var message)
: start = null,
end = null,
super(message);
/// 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(num value, [String? name, String? message])
: start = null,
end = null,
super.value(value, name, message ?? "Value not in range");
/// 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.
@pragma("vm:entry-point")
RangeError.range(num invalidValue, int? minValue, int? maxValue,
[String? name, String? message])
: start = minValue,
end = maxValue,
super.value(invalidValue, name, 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, dynamic indexable,
[String? name, String? message, int? length]) = IndexError;
/// Check that an integer [value] lies in a specific interval.
///
/// Throws if [value] is not in the interval.
/// The interval is from [minValue] to [maxValue], both inclusive.
///
/// If [name] or [message] are provided, they are used as the parameter
/// name and message text of the thrown error.
///
/// Returns [value] if it is in the interval.
static int checkValueInInterval(int value, int minValue, int maxValue,
[String? name, String? message]) {
if (value < minValue || value > maxValue) {
throw RangeError.range(value, minValue, maxValue, name, message);
}
return value;
}
/// Check that [index] 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 [name] or [message] are provided, they are used as the parameter
/// name and message text of the thrown error. If [name] is omitted, it
/// defaults to `"index"`.
///
/// If [length] is provided, it is used as the length of the indexable object,
/// otherwise the length is found as `indexable.length`.
///
/// Returns [index] if it is a valid index.
static int checkValidIndex(int index, dynamic indexable,
[String? name, int? length, String? message]) {
length ??= (indexable.length as int);
// Comparing with `0` as receiver produces better dart2js type inference.
if (0 > index || index >= length) {
name ??= "index";
throw RangeError.index(index, indexable, name, message, length);
}
return index;
}
/// 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) {
startName ??= "start";
throw RangeError.range(start, 0, length, startName, message);
}
if (end != null) {
if (start > end || end > length) {
endName ??= "end";
throw RangeError.range(end, start, length, endName, message);
}
return end;
}
return length;
}
/// Check that an integer value is non-negative.
///
/// Throws if the value is negative.
///
/// If [name] or [message] are provided, they are used as the parameter
/// name and message text of the thrown error. If [name] is omitted, it
/// defaults to `index`.
///
/// Returns [value] if it is not negative.
static int checkNotNegative(int value, [String? name, String? message]) {
if (value < 0) {
throw RangeError.range(value, 0, null, name ?? "index", message);
}
return value;
}
String get _errorName => "RangeError";
String get _errorExplanation {
assert(_hasValue);
String explanation = "";
num? start = this.start;
num? end = this.end;
if (start == null) {
if (end != null) {
explanation = ": Not less than or equal to $end";
}
// If both are null, we don't add a description of the limits.
} else if (end == null) {
explanation = ": Not greater than or equal to $start";
} else if (end > start) {
explanation = ": Not in inclusive range $start..$end";
} 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 [invalidValue] was not a valid index into.
final indexable;
/// The length of [indexable] at the time of the error.
final int length;
/// 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(int invalidValue, dynamic indexable,
[String? name, String? message, int? length])
: this.indexable = indexable,
this.length = length ?? indexable.length,
super.value(invalidValue, name, 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);
int invalidValue = this.invalidValue;
if (invalidValue < 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();
@pragma("vm:entry-point")
external FallThroughError._create(String url, int line);
external String toString();
}
/// Error thrown when trying to instantiate an abstract class.
///
/// No longer used in Dart 2 where it has become a compile-time error
/// to call the constructor of an abstract class.
class AbstractClassInstantiationError extends Error {
final String _className;
AbstractClassInstantiationError(String className) : _className = className;
external String toString();
}
/// Error thrown by the default implementation of `noSuchMethod` on [Object].
class NoSuchMethodError extends Error {
/// 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.
///
/// The [invocation] represents the method call that failed.
external NoSuchMethodError.withInvocation(
Object? receiver, Invocation invocation);
// Deprecated constructor to be removed after dart2js updates to the above.
/// 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.
///
/// 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. If `null`, it is considered
/// equivalent to the empty map.
///
/// This constructor does not handle type arguments.
/// To include type variables, create an [Invocation] and use
/// [NoSuchMethodError.withInvocation].
@Deprecated("Use NoSuchMethod.withInvocation instead")
external NoSuchMethodError(Object? receiver, Symbol memberName,
List? positionalArguments, Map<Symbol, dynamic>? namedArguments);
external String toString();
}
/// The operation was not allowed by the object.
///
/// This [Error] is thrown when an instance cannot implement one of the methods
/// in its signature.
@pragma("vm:entry-point")
class UnsupportedError extends Error {
final String? message;
@pragma("vm:entry-point")
UnsupportedError(String this.message);
String toString() => "Unsupported operation: $message";
}
/// 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 the class does not intend 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() {
var message = this.message;
return (message != null)
? "UnimplementedError: $message"
: "UnimplementedError";
}
}
/// The operation was not allowed by the current state of the object.
///
/// Should be used when this particular object is currenty in a state
/// which doesn't support the requested operation, but other similar
/// objects might, or the object might change its state to one which
/// supports the operation.
/// Example: Asking for `list.first` on a currently empty list.
/// If the operation is never supported, consider using
/// [UnsupportedError] instead.
///
/// 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)}.";
}
}
/// Error that the platform can use in case of memory shortage.
class OutOfMemoryError implements Error {
@pragma("vm:entry-point")
const OutOfMemoryError();
String toString() => "Out of Memory";
StackTrace? get stackTrace => null;
}
/// Error that the platform can use in case of stack overflow.
class StackOverflowError implements Error {
@pragma("vm:entry-point")
const StackOverflowError();
String toString() => "Stack Overflow";
StackTrace? get stackTrace => null;
}
/// Error thrown when a lazily initialized variable cannot be initialized.
///
/// This is no longer used in null safe Dart code and is replaced by late
/// variables and `LateInitializationError`.
// TODO: Deprecate?
class CyclicInitializationError extends Error {
final String? variableName;
@pragma("vm:entry-point")
CyclicInitializationError([this.variableName]);
String toString() {
var variableName = this.variableName;
return variableName == null
? "Reading static variable during its initialization"
: "Reading static variable '$variableName' during its initialization";
}
}