Exclamation Does Not Assert Nullability

Unlike Kotlin where object.someValueThatCouldBeNull!! would assert that someValueThatCouldBeNull is NOT null (and throw if it is), in TypeScript the object.someValueThatCouldBeNull! operator tells the compiler:

"Don't worry about this, look away, even if you know this is null, I don't care, let me be".

In other words, even if object.someValueThatCouldBeNull is in fact NULL, object.someValueThatCouldBeNull! makes things compile as if it wasn't NULL - provides NO runtime protection.

Hence, instead of ! operator, it's better to use a helper function like:

export function ensure<T>(value: T | null | undefined, name?: string): T {
  if (value === undefined) {
    throw new Error(`${name ?? 'Value'} is required but was undefined`);
  }
  if (value === null) {
    throw new Error(`${name ?? 'Value'} is required but was null`);
  }
  return value;
}
GT-Sandbox-Snapshot

Code

import { describe, it, expect, beforeEach, beforeAll } from 'vitest';

describe('Non-null assertion operator (!)', () => {
  it('does NOT throw when value is null - crashes at property access instead', () => {
    const data: { value: string | null } = {value: null};

    // This does NOT throw - the ! operator is compile-time only
    const assertedValue = data.value!;

    // assertedValue is still null at runtime
    expect(assertedValue).toBe(null);

    // The error happens when you try to USE the null value
    expect(() => {
      // This is where it actually crashes
      const length = data.value!.length;
    }).toThrow('Cannot read properties of null');
  });

  it('does NOT throw when value is undefined either', () => {
    const data: { value?: string } = {};

    // Again, this doesn't throw
    const assertedValue = data.value!;

    // It's still undefined
    expect(assertedValue).toBe(undefined);

    // Crashes when accessing properties
    expect(() => {
      const length = data.value!.length;
    }).toThrow('Cannot read properties of undefined');
  });

  it('ensure() helper DOES throw immediately', () => {
    function ensure<T>(value: T | null | undefined, name?: string): T {
      if (value == null) throw new Error(`${name ?? 'Value'} is required`);
      return value;
    }

    const data: { value: string | null } = {value: null};

    // This throws immediately, before any property access
    expect(() => {
      const safeValue = ensure(data.value, 'data.value');
    }).toThrow('data.value is required');
  });
});

Command to reproduce:

gt.sandbox.checkout.commit 9f402a4c847a161d5d6a \
&& cd "${GT_SANDBOX_REPO}" \
&& cmd.run.announce "./run.sh"

Recorded output of command:


up to date, audited 179 packages in 1s

33 packages are looking for funding
  run `npm fund` for details

2 moderate severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.

> glassthought-sandbox@1.0.0 test
> vitest run


 RUN  v0.34.6 /home/nickolaykondratyev/git_repos/glassthought-sandbox

 ✓ src/main.test.ts  (3 tests) 2ms

 Test Files  1 passed (1)
      Tests  3 passed (3)
   Start at  11:56:55
   Duration  484ms (transform 34ms, setup 0ms, collect 11ms, tests 2ms, environment 0ms, prepare 68ms)