blob: c632d43e18ca71702b31a6aa72e71b0f40362002 [file] [edit]
// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Assertion utilities for runtime validation and TypeScript type narrowing.
//
// These functions provide fail-fast semantics: if an assertion fails, an
// exception is thrown immediately. This makes bugs easier to catch and debug
// by surfacing issues at the point of failure rather than propagating invalid
// state.
//
// In addition to runtime checks, these assertions help TypeScript narrow types.
// For example, after calling assertExists(x), TypeScript knows x is non-null.
type Nullable<T> = T | undefined | null; // T or nullish (null or undefined)
type Maybe<T> = T | undefined; // T or undefined
type Falsy = false | 0 | -0 | 0n | '' | null | undefined; // All falsy types
// Asserts that x is truthy, throwing at runtime otherwise. The `asserts x`
// return annotation narrows x in the caller, stripping falsy members from its
// type (e.g. `string | undefined` becomes `string`) for the rest of the scope.
export function assertTrue(x: unknown, msg?: string): asserts x {
if (!Boolean(x)) {
throw new Error(msg ?? 'Failed assertion');
}
}
// Asserts that x is falsy, throwing at runtime otherwise. The `asserts x is
// Extract<T, Falsy>` annotation narrows x in the caller to just the falsy
// members of its type for the rest of the scope.
export function assertFalse<T>(
x: T,
msg?: string,
): asserts x is Extract<T, Falsy> {
if (Boolean(x)) {
throw new Error(msg ?? 'Failed assertion');
}
}
// Throws at runtime if x is null or undefined, otherwise returns x with its
// type narrowed to non-nullable. Use this when you need the value as an
// expression (e.g. `const y = ensureExists(maybeY)`).
export function ensureExists<T>(x: Nullable<T>, msg?: string): T {
if (x === null || x === undefined) {
throw new Error(msg ?? 'Value is null or undefined');
}
return x;
}
// Asserts that x is neither null nor undefined, throwing at runtime otherwise.
// The `asserts x is T` annotation narrows x to non-nullable in the caller for
// the rest of the scope. Use this when x is already a variable you can keep
// using; prefer ensureExists when you need the value as an expression.
export function assertExists<T>(x: Nullable<T>, msg?: string): asserts x is T {
if (x === null || x === undefined) {
throw new Error(msg ?? 'Value is null or undefined');
}
}
// Like ensureExists, but only undefined is rejected; null is allowed through.
// ensureExists/assertExists trip over nulls, but in many contexts null is a
// valid SQL value we have to work with.
export function ensureDefined<T>(x: Maybe<T>, msg?: string): T {
if (x === undefined) {
throw new Error(msg ?? 'Value is undefined');
}
return x;
}
// Like assertExists, but only undefined is rejected; null is allowed through.
// Narrows x to exclude undefined in the caller for the rest of the scope.
export function assertDefined<T>(x: Maybe<T>, msg?: string): asserts x is T {
if (x === undefined) {
throw new Error(msg ?? 'Value is undefined');
}
}
// Throws at runtime unless x is an instance of clazz, otherwise returns x typed
// as that class. Use this when you need the value as an expression; prefer
// assertIsInstance when x is already a variable you can keep using.
export function ensureIsInstance<T>(
x: unknown,
clazz: abstract new (...args: never[]) => T,
msg?: string,
): T {
if (!(x instanceof clazz)) {
throw new Error(msg ?? `Value is not an instance of ${clazz.name}`);
}
return x;
}
// Asserts that x is an instance of clazz, throwing at runtime otherwise. The
// `asserts x is T` annotation narrows x to that class in the caller for the
// rest of the scope.
export function assertIsInstance<T>(
x: unknown,
clazz: abstract new (...args: never[]) => T,
msg?: string,
): asserts x is T {
if (!(x instanceof clazz)) {
throw new Error(msg ?? `Value is not an instance of ${clazz.name}`);
}
}
// This function serves two purposes.
// 1) A runtime check - if we are ever called, we throw an exception.
// This is useful for checking that code we suspect should never be reached is
// actually never reached.
// 2) A compile time check where typescript asserts that the value passed can be
// cast to the "never" type.
// This is useful for ensuring we exhaustively check union types.
export function assertUnreachable(value: never, optMsg?: string): never {
throw new Error(
optMsg ?? `This code should not be reachable ${value as unknown}`,
);
}
/**
* In TS 5.7, the TypedArrays and other array view and wrapper types were made
* to be generic, in order to distinguish those backed by ArrayBuffers vs
* SharedArrayBuffers, defaulting to a union of these two if the type is lefe
* unspecified (ArrayBufferLike). This is a good thing in general as the two
* buffer types have differing interfaces, however the main problem with this is
* that a lot of libraries have not been updated to reflect which buffer type
* they actually use and return.
*
* @see https://github.com/microsoft/TypeScript/issues/60579
*/
export function assertIsArrayBufferView(
view: ArrayBufferView,
): asserts view is ArrayBufferView<ArrayBuffer> {
if (view.buffer instanceof SharedArrayBuffer) {
// Copy the underlying buffer into the array, trimming to the byte bounds of the view
throw new Error(
"Underlying view is a SAB. If this is a problem we could convert it but we're being defensive as this could be expensive",
);
}
}