Home > database >  Assertion function that returns a value?
Assertion function that returns a value?

Time:10-05

I am trying to create a function like this:

function assert<T>(condition: T, msg?: string): T & asserts condition {
  if (!condition) {
    throw new Error(msg)
  }
  return condition
}

The goal is to return condition unmodified, preserving its type, but also informing the compiler that the returned value is truthy. This would allow it to be used as a standalone statement, but also unobtrusively inside an expression:

const success: bool = true
// ... some code that might set success = false
assert(success)

const element: HTMLElement = assert(document.getElementById('foo'))

Unfortunately, the above syntax does not work (playground). Is there a way to express this in TypeScript?

I can't find any official documentation on assertion functions except the Release notes for TypeScript 3.7, and those only demonstrate assertion functions that implicitly return void.

The best I can come up with is a workaround for object types only, because those can never be falsy:

function assert<T extends object>(condition: T | null | undefined, msg?: string): T {
  if (!condition) {
    throw new Error(msg)
  }
  return condition
}

CodePudding user response:

You can use NonNullable<T>:

function assert<T>(condition: T, msg?: string): NonNullable<T> {
  return condition ?? error(msg);
}

// workaround for https://github.com/microsoft/TypeScript/issues/18535
function error(msg?: string): never {
  throw new Error(msg);
}

const s: string | null = null;
const n: string = assert(s);

Playground

CodePudding user response:

There is a fixed, finite set of falsy values in Javascript. Some of them are not writable as literal types in Typescript, but the rest are 0, '', false, null and undefined. You can use Exclude to remove them from the type parameter T:

type Truthy<T> = Exclude<T, 0 | '' | false | null | undefined>

function checkTruthy<T>(x: T): Truthy<T> {
    // ...
}

Or as an assertion function:

function assertTruthy<T>(x: T): asserts x is Truthy<T> {
    // ...
}

Unfortunately, as the error message in your original code says, you can't write a function type annotation that does both of these. That said, there should be no need to; if the assertion function would just return its argument unchanged, you can always refactor a call to it so that it's not in an expression:

// change this
let y = assertTruthy(x)   'foo';

// to this
assertTruthy(x);
let y = x   'foo';

Note that Typescript isn't able to represent types like "a non-empty string" or "a non-zero number", so e.g. Truthy<string> is just the same as string and you get no benefit regardless of whether you return or assert. So using Exclude on strings or numbers only really helps if you have a union of literals like 'foo' | 'bar' | ''.

  • Related